Eliminating Unused CSS

a-plus-forms - A+ forms. Would use again - Interview with Nikolay Nemshilov

Frameworks like Bootstrap tend to come with a lot of CSS. Often you use only a small part of it. Typically, you bundle even the unused CSS. It's possible, however, to eliminate the portions you aren't using.

PurifyCSS is a tool that can achieve this by analyzing files. It walks through your code and figures out which CSS classes are being used. Often there is enough information for it to strip unused CSS from your project. It also works with single page applications to an extent.

uncss is a good alternative to PurifyCSS. It operates through PhantomJS and performs its work in a different manner. You can use uncss itself as a PostCSS plugin.

You have to be careful if you are using CSS Modules. You have to whitelist the related classes as discussed in purifycss-webpack readme.

Setting Up Pure.css#

To make the demo more realistic, let's install Pure.css, a small CSS framework, as well and refer to it from the project so that you can see PurifyCSS in action. These two projects aren't related in any way despite the naming.

npm install purecss --save

To make the project aware of Pure.css, import it:

app/index.js

import "purecss";
...

You should also make the demo component use a Pure.css class, so there is something to work with:

app/component.js

export default (text = "Hello world") => {
  const element = document.createElement("div");

element.className = "pure-button";
element.innerHTML = text; return element; };

If you run the application (npm start), the "Hello world" should look like a button.

Styled hello

Building the application (npm run build) should yield output:

Hash: 20c680ed670eff9c69c9
Version: webpack 3.8.1
Time: 1029ms
     Asset       Size  Chunks             Chunk Names
    app.js    3.76 kB       0  [emitted]  app
   app.css    16.9 kB       0  [emitted]  app
index.html  218 bytes          [emitted]
   [0] ./app/index.js 117 bytes {0} [built]
   [2] ./app/main.css 41 bytes {0} [built]
   [3] ./app/component.js 180 bytes {0} [built]
...

As you can see, the size of the CSS file grew. This is something to fix with PurifyCSS.

Enabling PurifyCSS#

Using PurifyCSS can lead to significant savings. In the official example of the project, they purify and minify Bootstrap (140 kB) in an application using ~40% of its selectors to mere ~35 kB. That's a big difference.

purifycss-webpack allows to achieve similar results. You should use the ExtractTextPlugin with it for the best results. Install it and a glob helper first:

npm install glob purifycss-webpack purify-css --save-dev

You also need PurifyCSS configuration as below:

webpack.parts.js

...
const PurifyCSSPlugin = require("purifycss-webpack");

exports.purifyCSS = ({ paths }) => ({
  plugins: [new PurifyCSSPlugin({ paths })],
});

Next, the part has to be connected with the configuration. It's important the plugin is used after the ExtractTextPlugin; otherwise it doesn't work:

webpack.config.js

...
const glob = require("glob");
const parts = require("./webpack.parts"); ... const productionConfig = merge([ ...
parts.purifyCSS({ paths: glob.sync(`${PATHS.app}/**/*.js`, { nodir: true }), }),
]);
The order matters. CSS extraction has to happen before purifying.

If you execute npm run build now, you should see something:

Hash: 20c680ed670eff9c69c9
Version: webpack 3.8.1
Time: 1126ms
     Asset       Size  Chunks             Chunk Names
    app.js    3.76 kB       0  [emitted]  app
   app.css    2.38 kB       0  [emitted]  app
index.html  218 bytes          [emitted]
   [0] ./app/index.js 117 bytes {0} [built]
   [2] ./app/main.css 41 bytes {0} [built]
   [3] ./app/component.js 180 bytes {0} [built]
...

The size of the style has decreased noticeably. Instead of 16k, you have roughly 2k now. The difference would be even bigger for heavier CSS frameworks.

PurifyCSS supports additional options including minify. You can enable these through the purifyOptions field when instantiating the plugin. Given PurifyCSS cannot pick all of the classes you are using always, you should use purifyOptions.whitelist array to define selectors which it should leave in the result no matter what.

Using PurifyCSS loses CSS source maps even if you have enabled them through loader specific configuration due to the way it works underneath.

Critical Path Rendering#

The idea of critical path rendering takes a look at CSS performance from a different angle. Instead of optimizing for size, it optimizes for render order and puts emphasis on above-the-fold CSS. This is done through rendering the page and then figuring out which rules are required to achieve the shown result.

webpack-critical and html-critical-webpack-plugin implement the technique as a HtmlWebpackPlugin plugin. isomorphic-style-loader achieves the same using webpack and React.

critical-path-css-tools by Addy Osmani lists other related tools.

Conclusion#

Using PurifyCSS can lead to a significant decrease in file size. It's particularly valuable for static sites that rely on a heavy CSS framework. The more dynamic a site or an application becomes, the harder it becomes to analyze reliably.

To recap:

  • Eliminating unused CSS is possible using PurifyCSS. It performs static analysis against the source.
  • The functionality can be enabled through purifycss-webpack and the plugin should be applied after ExtractTextPlugin.
  • At best, PurifyCSS can eliminate most, if not all, unused CSS rules.
  • Critical path rendering is another CSS technique that puts emphasis on rendering the above-the-fold CSS first. The idea is to render something as fast as possible instead of waiting for all CSS to load.
Previous chapterAutoprefixing
Next chapterLoading Assets

This book is available through Leanpub (digital), Amazon (paperback), and Kindle (digital). By purchasing the book you support the development of further content. A part of profit (~30%) goes to Tobias Koppers, the author of webpack.

Need help?