How to @import external SCSS properly with webpack

2020-05-26 02:29发布

问题:

As in Material Component Web's example, I want to be able to import SCSS from my node_modules like this:

@import '@material/elevation/mdc-elevation';

However, I'm getting this error message when trying to run the webpack build:

File to import not found or unreadable: @material/elevation/mdc-elevation.

@import './~/@material/elevation/mdc-elevation.scss'; doesn't work either.

I'm pretty sure the issue is somewhere in my webpack config, but I can't figure out where.

What did they do in Material Components Web's Vue.js example in order to make it work?

Here's my npm-debug.log in case you need it. And here's the corresponding Git repository: sk22/spg-tinf-sem03/proj01

Thanks in advance!

Edit: I want to be able to import the scss files, not the compiled css.

回答1:

Got it.

here's a part of my webpack 2 config's module.rules:

{
  test: /\.(sass|scss)$/,
  use: [
    'style-loader',
    'css-loader',
    {
      loader: 'sass-loader',
      options: {
        includePaths: [path.resolve(__dirname, 'node_modules')],
      },
    },
  ],
},

So what did I do wrong? My options object was placed in the rule directly, not the loader.

The old webpack config rule looked like this:

{
  test: /\.(sass|scss)$/,
  use: ['style-loader', 'css-loader', 'sass-loader'],
  options: { includePaths: [path.resolve(__dirname, './node_modules')] },
},

See the difference? Instead of the 'sass-loader' string, I extended it to an object, containing the loader name and the options object, because the options only apply to the sass-loader.

(You could also drop the path.resolve and only write 'node_modules', but it might be safer to leave it.)

Check out this documentation page for further information. https://webpack.js.org/configuration/module/#rule-use

Without that loader, you must prefix each import with a ~, which webpack converts to the node_modules folder, at least with my previous configuration. But this will break 3rd party SCSS frameworks like Material Components Web, because they use @import statements without a leading ~ themselves, for example here.

Inside .vue files

This will not work in .vue files, as vue-loader just uses sass-loader without any options by default. So if you want that to work, you probably need to make use of vue-loader's own options, as described in its documentation.

(I'm unable to get it to work for some reason I don't know...)



回答2:

EDIT: Webpack has a section on sass-loader now: https://webpack.js.org/loaders/sass-loader/ also mentioning includepaths.

I had the same issue with @material and Vue. I managed to resolve the problem without adjusting the use property directly.

Solution

Step 1: First create a default Vue 2.1 project using the CLI. Your file structure will have a ./build directory.

Step 2: Open the file 'utils' you will see a cssLoaders() function which returns an object/map for the languages vue-loader supports.

You will see both sass and scss in that map.

Step 3: Change the values of sass and scss to:

  sass:    generateLoaders('sass', {
    indentedSyntax: true,
    includePaths:   [path.resolve(__dirname, '../node_modules')]
  }),
    scss:    generateLoaders('sass', {
  includePaths: [path.resolve(__dirname, '../node_modules')]
}),

Step 4: Go to the .vue file you're using and change the lang attribute in your <style> element to either sass or scss.

Step 5: After you've done that go to the terminal/console and install sass-loader with:

npm install sass-loader node-sass webpack --save-dev

Step 6: Then run npm run dev and it should work.

Why does this work?

Libraries

I dug around a bit and it turns out sass-loader uses node-sass which has some options such asincludePaths one mentioned by @22samuelk. IncludePaths tells node-sass or rather the underlying library LibSass to include sass files from that directory/path.

Vue

Sass-loader options

By default Vue expects your assets to be in your projects src/assets folder (correct me if I'm wrong). You can however use ~ to indicat you want to start at your projects root which would look like `~/node_modules/@material/smth/mdc-smth.scss.

Now if you want your sass-loader to use something other than those options you need to explicitly tell them.

Hence path.resolve(__dirname, '../node_modules' since the utils file is in ./build and you need to use an absolute path for sass-loader to understand where to look.

Vue-loader config

This is not really specific to the question but the vue-loader config defined in vue-loader.conf.js works as follows:

It uses the map returned by cssLoaders() to build the loaders expected by webpack. The returned map ({key:value}) is then used by providing key as a file extension used in test: for a loader object. The value is used as the loader object. Which would like like this:

{
  test: /\.(key)$/,
    use: [
  {
    loader: '//ld//-loader',
    options: {
      /*Options passed to generateLoaders('//ld//', options)*/
    },
  },
],
}

Where key is the file extention. In this case that would be either sass or scss. And //ld//is the loader you which to use. Which is shown in Step 3 as 'sass'.

Hopefully this clears up some stuff. Took me a while because I just started using Vue.