An attempt to explain different ways of combining CSS Modules with other CSS processing tools.
Files using CSS Modules syntax need to go through a few transformation steps before they become browser-friendly:
- Compilation to ICSS
- ICSS value resolution
- CSS Bundling (optional)
All input files with the programmer-friendly syntax like @value
or composes
are compiled to Interoperable CSS (ICSS).
All :import
statements in ICSS are resolved and imported symbols usages are replaced in the file with the actual values.
In most common setups the resolved CSS modules are finally merged into a single CSS file (bundle).
Usually CSS Modules are not the only CSS transformation used in the project - many other processors can be used to enhance the styling capabilities even more.
The often overlooked opportunity is that the additional processing can be done in any of the steps in the above chain.
In particular 3 spots can be defined:
- before any CSS Modules transformations
- after value resolution
- after bundling
The important thing is - processing (even with the same tool) may bring different results depending on when it's applied. Some tools may work in any case, some may require to be run in a specific step.
Any transformation that might result in changing the exported class names must happen before any CSS Modules processing (before compilation to ICSS).
This category contains all scripting extensions to CSS, like postcss-each.
Playground: https://codesandbox.io/s/0mkrv3jrqn
@each $color in red, green, blue {
.background-$(color) {
background: $(color);
}
}
._input_css_406__background-red {
background: red;
}
._input_css_406__background-green {
background: green;
}
._input_css_406__background-blue {
background: blue;
}
:export {
background-red: _input_css_406__background-red;
background-green: _input_css_406__background-green;
background-blue: _input_css_406__background-blue;
}
Following ouput will be generated if the transformation of @each
will happen on CSS already compiled to ICSS.
._input_css_410__background-red {
background: red;
}
._input_css_410__background-green {
background: green;
}
._input_css_410__background-blue {
background: blue;
}
:export {
background-$(color): _input_css_410__background-$(color);
}
Any transformation that relies on the imported values must be done after the value resolution.
Commonly used examples are postcss-calc and postcss-color-function.
Note: postcss-modules-values-replace was created specifically to solve this problem. It's a good, working solution, but it results in unnecessary duplication of the bundler/resolver logic.
/* sizes.css */
@value size_m: 8px;
/* index.css */
@value size_m from './sizes.css';
.foo {
width: calc(2 * size_m);
}
.foo {
width: 16px;
}
Following ouput will be generated if the transformation of calc
will happen on unresolved ICSS values.
.foo {
width: calc(2 * 8px);
}
Some processors may require to be run not only after the value resolution, but on the bundled output.
Example: postcss-modules-extend-rule.
CSS processing before and after bundling can be easily achieved with one of the most common setups: webpack + PostCSS.
The CSS Modules resolution and bundling is done in webpack by combining css-loader and MiniCssExtractPlugin:
// in webpack.config.js
{
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: { modules: true }
},
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
})
]
}
The input CSS files can be processed before the CSS Modules resolution and bundling by applying postcss-loader before the css-loader
:
// in webpack.config.js
{
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: { modules: true }
},
{
loader: "postcss-loader",
options: { plugins: [/* anything you want */]}
}
]
}
]
},
}
The output CSS bundle can be processed after the CSS Modules bundling by using PostCSSAssetsPlugin after the MiniCssExtractPlugin
:
// in webpack.config.js
{
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
new PostCSSAssetsPlugin({
test: /\.css$/,
plugins: [
/* anything you want */
],
}),
];
}
A fully working example can be found in postcss-modules-extend-rule
test webpack.config.js