CSS Modules

RelativeCI - In-depth bundle stats analysis and monitoring - Interview with Viorel Cojocaru

Perhaps the most significant challenge of CSS is that all rules exist within global scope, meaning that two classes with the same name will collide. The limitation is inherent to the CSS specification, but projects have workarounds for the issue. CSS Modules introduces local scope for every module by making every class declared within unique by including a hash in their name that is globally unique to the module.

CSS Modules through css-loader#

Webpack's css-loader supports CSS Modules. You can enable it through a loader definition as above while enabling the support:

{
  use: {
    loader: "css-loader",
    options: {
      modules: true,
    },
  },
},

After this change, your class definitions remain local to the files. In case you want global class definitions, you need to wrap them within :global(.redButton) { ... } kind of declarations.

In this case, the import statement gives you the local classes you can then bind to elements. Assume you had CSS as below:

app/main.css

body {
  background: cornsilk;
}

.redButton {
  background: red;
}

You could then bind the resulting class to a component:

app/component.js

import styles from "./main.css";

...

// Attach the generated class name
element.className = styles.redButton;

body remains as a global declaration still. It's that redButton that makes the difference. You can build component-specific styles that don't leak elsewhere this way.

CSS Modules allows composition to make it easier to work with your styles and you can also combine it with other loaders as long as you apply them before css-loader.

CSS Modules behavior can be modified as discussed in the official documentation. You have control over the names it generates for instance.
eslint-plugin-css-modules is handy for tracking CSS Modules related problems.

Using CSS Modules with third-party libraries and CSS#

If you are using CSS Modules in your project, you should process standard CSS through a separate loader definition without the modules option of css-loader enabled. Otherwise, all classes will be scoped to their module. In the case of third-party libraries, this is almost certainly not what you want.

You can solve the problem by processing third-party CSS differently through an include definition against node_modules. Alternately, you could use a file extension (.mcss) to tell files using CSS Modules apart from the rest and then manage this situation in a loader test.

Conclusion#

CSS Modules solve the scoping problem of CSS by defaulting to local scope per file. You can still have global styling, but it requires additional effort. Webpack can be set up to support CSS Modules easily as seen above.

Previous chapter
Hot Module Replacement

This book is available through Leanpub (digital), Amazon (paperback), and Kindle (digital). By purchasing the book you support the development of further content. A part of profit (~30%) goes to Tobias Koppers, the author of webpack.

Need help?