Sass with CSS Modules & Webpack

barndog picture barndog · Dec 23, 2015 · Viewed 17k times · Source

I've been building a project for a while using Webpack, Sass, and CSS modules. Normally in my .scss files, I define a class like:

:local(.button) {
    color: white;
}

and in my React components, in the render method, I require the styles:

render = () => {
    const styles = require('./MyStyles.scss');
    <div className={ styles.button } />
}

and all is good with the world. Everything works as expected.

Now today I was reading through the CSS Modules page and noticed that none of the selectors were encompassed by :local() like mine and furthermore that they were importing the styles like:

import styles from './MyStyles.scss';

And I thought "Wow that looks much nicer, it's easier to see where it's imported, ect. And I'd love not to use :local() and just have things local by default." So I tried that and immediately ran into several problems.

1) `import styles from './MyStyles.scss';

Because I'm using ESLint on my React files, I immediately get an error thrown that MyStyles.scss doesn't have a default export which would normally make sense but the CSS Modules page stated:

When importing the CSS Module from a JS Module, it exports an object with all mappings from local names to global names.

so I naturally expected the default export of the stylesheet to be the object they're referring too.

2) I tried import { button } from './MyStyles.scss';

This passes linting but button logs as undefined.

3) If I revert to the require method of importing my styles, anything not specified with :local is undefined.

For reference, my webpack loader (I'm also including Node-Neat and Node-Bourbon, two awesome libraries):

{ test: /.(scss|css)$/, loader: 'style!css?sourceMap&localIdentName=[local]___[hash:base64:5]!resolve-url!sass?outputStyle=expanded&sourceMap&includePaths[]=' + encodeURIComponent(require('node-bourbon').includePaths) +
'&includePaths[]=' + encodeURIComponent(require('node-neat').includePaths[1]) + '&includePaths[]=' + path.resolve(__dirname, '..', 'src/client/') }

My questions, following all of this, are:

1) When using CSS Modules with Sass, am I confined to using either :local or :global?

2) Since I'm using webpack, does that also mean I can only require my styles?

Answer

barndog picture barndog · Dec 23, 2015

Soon after posting, I figured out the solution. The problem, which I thought was quite confusing, was in my Webpack config. Originally my loader looked like:

loader: 'style!css?sourceMap&localIdentName=[local]___[hash:base64:5]!resolve-url!sass?outputStyle=expanded&sourceMap

which enabled to me to 1) require my Sass and 2) wrap my styles in :local.

However, the css loader was missing the modules option so that it looked like:

loader: 'style!css?modules&sourceMap&localIdentName=[local]___[hash:base64:5]!resolve-url!sass?outputStyle=expanded&sourceMap

Now I can import my styles and I don't have to wrap them in :local (although I presume I still can if I want to).

What I found most interesting about all this is that without the modules option, one can still use CSS Modules-esque features, although somewhat limiting.

EDIT:

Something I noticed, a future warning to whomever looks at this answer, is if you're using the eslint-plugin-import to lint the imports in your javascript code, it will throw an error upon importing styles like:

import styles from './MyStyles.scss';

because of the way CSS Modules exports the resulting styles object. That does mean you'll be required to do require('./MyStyles.scss') to bypass any warnings or errors.