Support Tree shaking of CSS
Opened this issue ยท 21 comments
Do you want to request a feature or report a bug?
Feature
What is the current behavior?
Webpack 2 support tree shaking and removal of dead code but this does not work for CSS. If we have a javascript file which exports different CSS files and we only import some of then, then css loader does not tree shake and remove the unused CSS.
Example if we had the code below, css-loader currently is including CSS for both Checkbox and Button but it should only include code for Checkbox since Button is never imported:
// componentcss.js
export const Checkbox = require('./Checkbox.css');
export const Button = require('./Button.css');
// index.js
import { Checkbox } from './componentcss';What is the expected behavior?
css-loader should tree shake and remove unused CSS import when used webpack 2 with tree shaking feature.
If this is a feature request, what is motivation or use case for changing the behavior?
This will help out reduce the CSS bundle size out of box when using webpack 2
Please mention other relevant information such as your webpack version, Node.js version and Operating System.
This will work with webpack 2.
Send PR for discussion how this would/could look like, but tbh I have no idea how css-loader should do that, since it works on a per file (chunk) basis like loaders do in general ๐. There are future plans to natively support CSS in webpack without requiring style-loader/css-loader etc. anymore, but that's the sound of the intermediate feature and I guess it will require changes to webpack core itself, mainly bc only the whole compilation with all modules (chunks) available could tree shake (dedupe) repetitive parts. In short css-loader has no context about your dependency graph it solely resolves @import/url() paths within a file, minimize && scopes the CSS locally to the file via CSSModules. Based on your example css-loader would run twice without any knowledge about the contents and no way to dedupe that within the loader. It already exports a format to dedupe [[ css.id, css.mediaQuery, css.content, css.map, css.locals ]] which style-loader || ETWP currently use to remove/skip duplicates, but if this works under all circumstances isn't always guaranteed
There was a PR submitted but it did not get rebased and was closed because of no activity #402
Not sure if something similar can be implemented where css-loader uses ES6 import export statements instead of module.exports so that webpack 2 can tree shake and remove the unused modules.
The conversion to ES2015 Modules will definitely by coming soon, but this is a joined effort with changes needed in style-loader && extract-textwebpack-plugin aswell + in the PR the author referenced a webpack plugin prototypein addition, which then would do the whole 'tree shaking' part on the compilation level before the bundle get emitted ๐. That's what I was referring to in my ealier comment by 'needs changes to webpack itself' (webpack Core), bc a loader has not enough context to 'tree shake' anything, each loader run (per file/chunk) would at best be a branch of the 'tree'.
Confirm that all css-loader needs to do to support CSS treeshaking is to emit ES2015 modules. The "deadcss" plugin runs a sub-compilation and checks the used exports of css-loader.
any update on this?
in css-loader@4 we drop support css modules in favor postcss plugins, looks you need postcss plugin for these
@evilebottnawi Do you have any ideas to implement this in webpack 5? Maybe only for css modules..
@vankop i think we need postcss plugin for this
Maybe my vision is mistaken, but how you want to tree shake css without webpack plugin? You need to understand which css classes does not imported in JS.
@vankop to be honestly, i think it is impossible, i really don't have ideas how we can remove unused selectors, we don't have infromation about which selectors used, so we can't remove them, also it is not safe, because you can asynchronously load CSS file and use any logic so removing selectors is not safe
@evilebottnawi actually, such information is in css modules
i use just naruto, sasuke must be remove from bundle
@MaxmaxmaximusGitHub CSS has side effect, we don't know where you can use classes, you can just put them in index.html
@evilebottnawi If you're using CSS Modules then all class names are local and can't be referenced globally.
I had this working in 2017 (#402 and various other plugins) but it involved a lot of breaking changes and there didn't seem to be the will to get it merged
I just checked and the demo project I created still works: https://github.com/simlrh/dead-css-loader-demo
Styles all import from the semantic ui css, which is 290kb minified, and the resulting bundle.js (including the entire app, react, etc) is only 179kb
If you're using CSS Modules then all class names are local and can't be referenced globally.
And Yes and No, you still inject styles in DOM, so they still have side effects, and in theory you can use it in any file (just write hashing class ๐ ), I think we can implement plugin for this, it is not loader work
@alexander-akait Is this question even valid?
- In webpack5, we can customize the
optimizationoption inwebpack.config.js, and some options likesideEffects,usedExportsare there to achieve tree-shaking. - Since css-loader transforms CSS files into js modules, those mechanisms triggered by
optimizationwould certainly cover these CSS-in-js files as well, right? - IIRC, the execution order is "loaders first, then plugins". So why do we need to use css-loader for tree-shaking?
We still need, because we can remove unused CSS classes from a CSS file
[...] I think we can implement plugin for this, it is not loader work
So this issue can be closed?
No
@alexander-akait So is it still possible to implement tree-shaking of unused CSS classes using css-loader? Also, is it correct to say that only CSS modules are tree-shakable?
It is possible, but I don't think we will implement it here, but if someone want it - feel free to send a PR, I want to keep this open

