Enables treeshaking of modular CSS produced by PostCSS
npm install rollup-plugin-postcss-treeshakeablerollup-plugin-postcss-treeshakeableEnables treeshaking of modular CSS produced by postcss
Install the package
```
npm install rollup-plugin-postcss-treeshakeable --save-dev
Add it to your rollup.config.js
`js
import postcss from "rollup-plugin-postcss";
import postcssTreeshakeable from "rollup-plugin-postcss-treeshakeable";
export default {
// ...
plugins: [
postcss({
modules: true,
plugins: []
}),
postcssTreeshakeable()
]
};
`
The postcssTreeshakeable must appear after the postcss plugin.
This package is experimental. A first test showed that the technique works, but it has not been tested on any big project yet.
This plugins transforms this code (which gets produced by postcss when importing a css module):
`js`
var css = ".foo { background: red; }";
export default { foo: "foo__classname_1 " };
import styleInject from "
styleInject(css);
into this code
`js
import styleInject from "
var cssMap = { foo: "foo__classname_1 " };
var hasRun;
var styles = function styles() {
if (!hasRun) {
hasRun = true;
styleInject(".foo { background: red; }");
}
return cssMap;
};
export default styles;
`
š This allows consumers to get rid of CSS of unused modules when treeshaking.
You are writing a cool library which lets people render a Butler on screen. The Butler is provided as a React component and uses CSS modules to have some styles applied. The library also comes with a bunch of other components.
Whoever uses your library should only bundle the code and styles of the components they are actually using. The unused component code and styles should be excluded from the bundle.
A technique called Treeshaking lets us do this. However, it was so far only able to omit the code of unused components. The styles of unused components would have still been lingering around in your bundle, even though they were never used!
This plugin enables library authors which use PostCSS and bundle with Rollup to create components whose styles can be omitted through treeshaking. This creates smaller bundles and thus makes the application the library consumers are building slimmer.
So far you've probably been importing CSS modules like this:
`jsx
import styles from "./Butler.mod.css";
const Butler = props =>
Given that
Butler.mod.css has this content:`css
.alfred {
background: red;
}
`The
Butler.mod.css gets turned into this by postcss:`js
var css = ".Butler-mod_alfred__3zrhW {\n background: red;\n}\n";
export default { alfred: "Butler-mod_alfred__3zrhW" };
import styleInject from "/style-inject/dist/style-inject.es.js";
styleInject(css);
`When you bundle this with with rollup, you end up with the following in your generated bundle:
`js
function styleInject(css, ref) {
/ redacted /
}var css = ".Butler-mod_alfred__3zrhW {\n background: red;\n}\n";
var styles = { alfred: "Butler-mod_alfred__3zrhW" };
styleInject(css);
var Butler = function Butler(props) {
return React.createElement(
"div",
{
className: styles.alfred
},
"I am Butler"
);
};
export { Butler / other components / };
`$3
If the consumer of your library doesn't import
Butler, but only imports the other components, then we don't need to keep Butler around.Treeshaking does exactly this and marks the
Butler export as unused. Once consumers of the library you're building run dead code elimination (for example by running Uglify), Butler won't end up in their application bundle anymore.This is pretty great, however the styles of
Butler would currently still end up in the produced bundle.$3
This plugin transforms the contents of
Butler.mod.css into a representation which can be removed through treeshaking.It transforms the contents of modular css files generated by postcss from
`js
var css = ".Butler-mod_alfred__3zrhW {\n background: red;\n}\n";
var styles = { alfred: "Butler-mod_alfred__3zrhW" };
styleInject(css);
`to this
`js
var cssMap = { alfred: "Butler-mod_alfred__3zrhW" };
var hasRun;
var styles = function styles() {
if (!hasRun) {
hasRun = true;
styleInject(".Butler-mod_alfred__3zrhW {\n background: red;\n}\n");
}
return cssMap;
};
`$3
We are not exporting an object of classnames anymore, but we're rather exporting a function returning that object. The function will add inject the styles the first time it gets called.
The consumers of the CSS modules have to be adapted, as we're now exporting a function instead of an object:
`diff
import styles from "./Butler.mod.css";-const Butler = props =>
I am Butler;
+const Butler = props => I am Butler;
`After applying this plugin and after rewriting the usage of imported CSS (by turning
styles into styles()), your library consumers will benefit from treeshaking it!$3
Notice that we're not calling
styleInject directly, but we're rather waiting until the styles are actually being used. At that point, we add the styles to the document.When treeshaking now removes unused components, it can also remove their unused CSS, as the styles are not referred to anymore after the components have been omitted.
And thus, no more CSS of unused components in the bundles of your library users.
Caveats
All styles of one modular CSS file get injected the first time any of its styles are used. So, if you want to apply the styles of some files right away, importing them is no longer good enough.
You have to call the function as well:
`js
import someStyles from "./somewhere.css";// This call injects them into the document and thus applies them.
// It further returns the classnames contained in
somewhere.css
someStyles();
``