tooling.report

feature

Module

Can CSS "Modules" be used for scoping?

Person in shorts with blue hair walking left

Introduction

Modularity in CSS is important when composing applications out of independent User Interface components. While CSS does not (yet) provide a syntax for defining modular styles consumable from JavaScript, web developers have rallied around a community-standard CSS "Module" specification.

As defined, CSS Modules have scoped class names and animation names by default, generally implemented as a transformation or prefix on the authored name. The value of a CSS Module imported into JavaScript is a object map of authored class names to their corresponding generated namespaced names:

import styles from './styles.css';
console.log(styles.someClass); // "someClass_X1y2z3"

The Test

This test builds a JavaScript module that imports a CSS file. The CSS import's resulting class name mapping object is used to construct HTML elements that use the namespaced CSS rules.

index.js

import styles from './styles.css';
document.body.insertAdjacentHTML(
  'afterend',
  `<div class="${styles.className1}"></div>`,
);
document.body.insertAdjacentHTML(
  'afterend',
  `<div class="${styles.className2}"></div>`,
);

styles.css

.class-name-1 {
  color: green;
}
.class-name-2 {
  background: green;
}

The built result should be a JavaScript bundle and a CSS file, where the CSS class names have been transformed to values that are mapped to their original values within the JavaScript bundle. The CSS must also be minified in order to pass the test.

Conclusion

browserify

This uses the Browserify plugin css-modulesify to transform the CSS and get the class names. The plugin is a wrapper around PostCSS, and allows you to add other PostCSS plugins, such as CSSNano.

parcel

Parcel supports CSS modules across a project, or per file if the file ends in .module.css.

Issues

rollup

There are plugins for postcss such as rollup-plugin-postcss, but it's also reasonable to create your own small plugin and use postcss directly.

webpack

Webpack's css-loader includes comprehensive options for compiling CSS Modules. When combined with mini-css-extract-plugin, CSS imports are preprocessed to namespace classNames, the CSS is extracted into one or more files configurable via splitChunks, and local-to-namespaced className mappings are produced as the result of the import.

CSS Modules className mapping objects often end up being included in full in bundles, rather than the prefixed classNames being inlined where they are used. With the esModules option is set to true for both css-loader and MiniCSSExtractPlugin.loader, these objects can be optimized away in certain cases by increasing the number of Terser compression passes to 2 (proposed as a revised default for Webpack's production mode).

It is also possible to achieve aggressive inlining by importing CSS Modules classNames as named imports, either using constant-locals-loader or a new proposed option for mini-css-extract-plugin.