material-components-web: Cannot @import scss

I really want to use your library, especially with Sass, however I can’t @include the .scss styles with my current configuration. That is using Webpack 2 and sass-loader. Here is an example project. Here is a comment describing the issue - basically I can’t import the .scss (of button in this case) neither with relative paths nor with the ~ alias to node_modules that node-sass / sass-loader supports. The problem in this case is @import "@material/animation/variables”;. I expect there would be many other similar imports in the library… Do you have / know of a working Webpack 2 example?

I thought Eyeglass would help, but I can’t set that up with Webpack 2. Here is an eyeglass issue and my comment on it that has a broken version of my config trying to tell sass-loader to use eyeglass for importing.

So I’m trying this with material-components-web@0.4.0 and more specifically @material/button@0.2.1 importing from its dependency @material/animation@0.1.3. It’s also worth noting that I’m using yarn which keeps a flat directory structure of my dependencies. I wonder if you assume a regular npm install and thus hierarchical dependencies?

Here is the example error:

 error  in ./pages/index.js.scss

Module build failed:
@import "@material/animation/variables";
^
      File to import not found or unreadable: @material/animation/variables.
Parent style sheet: /Users/om/Dev/mel/drmelgill.com/node_modules/@material/button/mdc-button.scss
      in /Users/om/Dev/mel/drmelgill.com/node_modules/@material/button/mdc-button.scss (line 17, column 1)

 @ ./pages?entry 43:15-41
 @ multi ./~/next/dist/client/webpack-hot-middleware-client ./pages?entry

Either of these two would cause it, and mdc-button.scss is found in both cases:

  • @import '../node_modules/@material/button/mdc-button.scss’; // valid relative path
  • @import '~@material/button/mdc-button.scss’; // ~ = node_modules

So I’m not really sure if this is about configuration help, documentation example, bug report, or a feature request…

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 44
  • Comments: 20 (6 by maintainers)

Most upvoted comments

Would it be possible to use ~ (tilde) by default for @import statements in the material-components/material-components-web project? So it will automatically work with sass-loader, node-sass-magic-importer, node-sass-package-importer, etc.

@import "~@material/textfield/mdc-text-field";

As mentioned in #981 this unfortunately is no help to @angular/cli users, who can’t configure the includePaths dynamically.

(It’s kind of impressive to see firsthand how uncoordinated the various development teams at Google are with each other.)

The crux of the problem seems to be twofold: first of all, node-sass itself doesn’t support scoped NPM packages. Second of all, sass-loader for webpack doesn’t support package imports that don’t start with tilde (~) - and users of @angular/cli can’t use @Elemecca’s workaround, either. Only eyeglass appears to support material-components as-is, but I would guess the user base of eyeglass is significantly smaller than that of node-sass and @angular/cli combined.

IMHO both of these problems (node-sass not processing scoped packages as well as sass-loader in @angular-cli not processing packages without a ~) need to be fixed in order for the current state of material-components to be considered acceptable for general consumption. Thus, it would be great if the material-components maintainers could get involved.

Here are relevant existing issues: https://github.com/sass/node-sass/issues/1596 https://github.com/webpack-contrib/sass-loader/issues/466

Not sure why this was closed, as I am still unable to use MDC web with Angular CLI.

This sass-loader configuration will work even if your dependency on the components is transitive. It adds all node_modules directories that are descendants of the directory containing the Webpack configuration and have an @material subdirectory to the Sass include path.

const path = require('path');
const glob = require('glob');

{ loader: 'sass-loader',
  options: {
    sourceMap: true,

    // mdc-web doesn't use sass-loader's normal syntax for imports
    // across modules, so we add all module directories containing
    // mdc-web components to the Sass include path
    // https://github.com/material-components/material-components-web/issues/351
    includePaths: glob.sync(
      path.join(__dirname, '**/node_modules/@material')
    ).map((dir) => path.dirname(dir)),
  },
},

Thank you, @Garbee! Yes, setting the includePaths did it. Well, at least I’m able to include the styles, which is all I need to start working. I feel kind of silly for either not spotting the “NOTE: The components’ Sass files expect that the node_modules directory containing the @material scope folder is present on the Sass include path.” or for not realizing what that means. I’ll leave this issue open in case anyone wants a reminder that the #readme could be more specific. The example though may differ between straight npm vs yarn and also depending on whether people install material-components-web or its individual modules in the case on npm, I think…

For the record, here is what sass-loader needed in my case:

          { loader: 'sass-loader',
            options: {
              includePaths: glob.sync('node_modules').map((d) => path.join(__dirname, d))
            }
          }

I guess I could be more specific, adding just the @material modules, plus a fixed list of other sass modules. Alternatively, I’m actually tempted to add node_modules/@material/*/ to the paths as well. This way instead of @import '@material/button/mdc-button'; I could just @import 'mdc-button'; - wouldn’t that be nice? I wonder if having many include paths affects performance - my guess would be that it doesn’t. Any advice about that?

Here is an improved config:

          { loader: 'sass-loader',
            options: {
              includePaths: ['node_modules', 'node_modules/@material/*']
                .map((d) => path.join(__dirname, d))
                .map((g) => glob.sync(g))
                .reduce((a, c) => a.concat(c), [])
            }
          }

With it one can just @import 'mdc-anything', using only the (.scss) filename.

I agree with @VeegaP, getting sass compilation to work with webpack (and others, apparently) in a consistent way is still very much not obvious or turn-key. @importing MDC scss files is not a closed issue.

My temporary solution: Add to your package.json:

{
    ...
+    "postinstall": "grep -rl node_modules/@material/ -e '@import \"@material' | xargs sed -i \"\" 's/@material/~@material/g'"
}

You need to make sure you include the path to the material modules in your sass-loader config.

Give that a shot and let us know if it starts working. If not, then providing a copy of your webpack config would probably help tracking things down.

@thasmo your solution was well for me, but it’s apper that sass is not be able to charge the animations.

That is my current code:

@import "~@material/checkbox/mdc-checkbox"; @import "~@material/animation/functions";

Is any of you having that the samme issue?

How do I implement this with parcel? 😦

@wzup glob is a module. You need to import it: var glob = require('glob');

@Elemecca

Thanks. The first part appeared to be enough.

['node_modules', 'node_modules/@material/*'] .map((d) => path.join(__dirname, d))

Why aren’t SCSS files just referenced by relative path?

Because with Lerna to do a monorepo setup and split into subpackages, once the packages are split the relative paths would break. Hence, importing by the package name and not path structure.

For those who uses Grunt here’re some tips. The idea is the same - specify additional folders to lookup for the compiler.

There’re a couple of grunt plugins for SASS: grunt-contrib-sass and grunt-sass. The former uses Ruby-based SASS compiler the latter C-base LibSass (node-sass is a wrapper about libsass). Both plugins have options for overriding lookup folders:

  • grunt-contrib-sass - loadPath:
    'sass' : {
        dev: {
           options: {
             loadPath: ['./node_modules']
           }
        }
    }
  • grunt-sass - includePaths:
    'sass' : {
        dev: {
           options: {
                includePaths: ['./node_modules']
           }
        }
    }

BUT Ruby-based sass failed to compile material’s SCSS-es in my case. So I choose libsas (grunt-sass) which works fine.