Secrets of CSS modules

Friday, Dec 18 2020 in frontend

With a frontend framework like Next.js or just a bundler like webpack, you can import the CSS class names from a CSS module as if importing variables from a JavaScript module.

For example, if your CSS file contains this code:

// styles.css

someClass {
  background-color: blue;
}

you can apply the someClass class to an HTML element from a JavaScript file by importing the class name:

// main.js
import { someClass } from './styles.css';

body.className = someClass; 

Did you ask yourself how the magic works? As usual, there is no magic. If CSS modules behave like JavaScript modules, it is because they are JavaScript modules.

What is the mysterious import from CSS? Since it’s used as a class name, the most likely thing is that is a string. And it is. The bundler transforms the CSS module file into a plain JavaScript module. It creates a JavaScript module that exports the CSS class names as JavaScript string constants. But browsers cannot read CSS from JavaScript files. How does the browser apply the right styles?

There’s two main steps to the process. First, the CSS loader parses the CSS, extracts the class names and builds a JavaScript module that exports the CSS class names.

Then either style-loader or mini-css-extract-plugin comes in. style-loader extracts the original CSS from the JavaScript module and inserts it in the document client-side with a dynamically created <style> tag. mini-css-extract plugin outputs the CSS into a separate file, so you can include the CSS server-side.

Either way, since the JavaScript code now references CSS class names only through variables, webpack can change the class names used in the CSS stylesheet. This lets webpack implement other useful CSS Modules features, such as limiting the scope of a class to the file where it is declared.