Spring-cleaning Unused CSS With Grunt, Gulp, Broccoli or Brunch
March 27, 2014
[caption id="attachment_6609" align="aligncenter" width="640"] The tough economic times on Tatooine hit everyone hard, including the Jawas.[/caption]
Delivering a fast experience on the web usually involves reducing server response time, minification of CSS/JS/HTML and an optimisation of images and above-the-fold content. We can further minimize the latency caused by stylesheet loading by removing unused CSS rules delivered to the client.
In this write-up, we'll take a look at build tasks you can use to remove unused CSS in your pages. Before we begin, I thought it would be useful to share some success stories just to demonstrate that the tools here can be used to make a difference.
Added grunt-uncss at lunch time to my sites GruntFile, CSS file went from 115kb to 3kb! That's -97.4% smaller!
— Ville Hellman (FxN) (@efexen)
Weekend dev - tried grunt-uncss on a bootstrap.css rails landing page. All in ~15 minutes for ~100K savings. Diff: https://t.co/MaJTiXrtUd
— Tom Fuertes (@thisbetom)
Unused CSS is a particular problem when working with a heavy modern UI framework such as Twitter Bootstrap, Zurb Foundation or Adobe TopCoat. In the below Bootstrap test, the results from running an audit of a project through the Chrome DevTools Audits panel indicates that ~ 90% of the CSS rules are unused.
This is a problem that’s been previously highlighted by the PageSpeed team, who include removing unused CSS as part of their speed recommendations for the web:
"Before a browser can begin to render a web page, it must download and parse any stylesheets that are required to lay out the page. Even if a stylesheet is in an external file that is cached, rendering is blocked until the browser loads the stylesheet from disk. Often, many web sites reuse the same external CSS file for all of their pages, even if many of the rules defined in it don't apply to the current page."
If, only after measuring, you find that you have a lot of CSS bloat, you may want to remove this CSS at build time to reduce your overall page-weight.
PageSpeed solve this problem via mod_pagespeed, an excellent Apache module that tries to auto-optimize a number of best practices for you, however this may not be something you can feasibly add to your site or project.
UnCSS
Giacomo Martino’s excellent UnCSS provides us with an excellent solution for removing unused CSS in our pages. The process by which UnCSS removes the unused rules is as follows:
-
The HTML files are loaded by PhantomJS and JavaScript is executed.
-
Used stylesheets are extracted from the resulting HTML.
-
The stylesheets are concatenated and the rules are parsed by css-parse.
-
document.querySelector filters out selectors that are not found in the HTML files.
-
The remaining rules are converted back to CSS.
Using grunt-uncss
A Grunt task I wrote called grunt-uncss, builds on top of UnCSS and can be dropped into your build process in just a few minutes.
If you haven't used Grunt before, be sure to check out the Getting Started guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
npm install grunt-uncss --save-dev
Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
grunt.loadNpmTasks('grunt-uncss');
Use the grunt-uncss task by specifying a target destination (file) for your cleaned CSS. Below this is dist/css/tidy.css.
uncss: { dist: { src: ['app/about.html', 'app/index.html'], dest: 'dist/css/tidy.css' options: { report: 'min' // optional: include to report savings } } }
Next, specify the input HTML files you would like scanned for used selectors. In this case we use an array, stating that app/index.html and app/about.html are the two files we would like checked.
You can then use this task alongside with a processor like processhtml to rewrite the location of your stylesheet to tidy.css using a block like:
<!-- Stylesheets we would like cleaned --> <!-- build:css css/tidy.css --> <!-- just here to make sure we reference css/tidy.css --> <link rel="stylesheet" href="css/bootstrap.css"> <!-- /build --> <!-- Stylesheets we want to minify as usual --> <!-- build:css css/other.css --> <link rel="stylesheet" href="css/main.css"> <link rel="stylesheet" href="css/normalize.css"> <!-- /build -->
and some configuration to let Grunt know where you’d like your final files written:
processhtml: { dist: { files: { 'dist/index.html': ['app/index.html'], 'dist/about.html': ['app/about.html'] } } }
A number of other configuration options are supported, including CSS minification, which you may find in the project readme.
Testing
To test the task works, we can run the demo project found in the project repository with the ‘grunt’ command:
This takes our original multi-page site that looks like this:
and removes the unused Bootstrap CSS so that it looks like:
As you can see, both sites look identical. We’re only using the CSS needed by our pages and nothing more.
More importantly, the final production-ready Bootstrap CSS has dropped in size from 120KB (minified) to just 11KB - a 91% reduction in filesize.
Using gulp-uncss
A similar build task for Gulp by Ben Briggs called gulp-uncss also exists and could be setup to work with our multi-page example as follows:
var gulp = require('gulp'); var uncss = require('gulp-uncss'); gulp.task('default', function() { gulp.src('site.css') .pipe(uncss({ html: ['index.html', 'about.html'] })) .pipe(gulp.dest('./out')); });
Using broccoli-uncss
Sindre Sorhus wrote a Broccoli equivalent called broccoli-uncss which could be used as follows:
var uncss = require('broccoli-uncss'); tree = uncss(tree, {html: ['./index.html', './about.html']});
Using brunch-uncss
An UnCSS plugin for Brunch by Jakub Burkiewicz is similarly available. Options can be specified using config.plugins.uncss
in your Brunch configuration file and a multi-file setup per our earlier examples could be setup as follows:
plugins: uncss: options: csspath: '../styles' htmlroot: 'build' files: ['index.html', 'about.html']
What about CSS pre-processors?
I've put together a small example of one way you can approach working with UnCSS (grunt-uncss) and a CSS pre-processor like Sass. In short, consider it a post-process build step which is run once all of the other core tasks in your setup have completed. This ensures that your Sass stylesheets are correctly built and we're just cleaning what isn't being used in there. This allows UnCSS to not require knowledge of the nuances of your pre-processor whilst still being useful.
Conclusions
Remember: if you think you might have a CSS bloat issue, don’t guess it, test it. Use the DevTools Audits panel to determine how much unused CSS you might have and don’t forget to check this across multiple pages.
My stylesheet file size was around 120K, after grunt-uncss it was around 25K. That's pretty neat, thanks @addyosmani https://t.co/e8Btu0seFO
— Matija Marohnić (@silvenon)
There are still many edge-cases where UnCSS has yet to be improved, such as working with templates depending on the setup of your project and when you're pulling them in. If you find such an edge-case, please feel free to tell us upstream.
If your project does contain a lot of unused CSS, tools like UnCSS can help you trim down your page-weight at build time and deliver faster pages to your users.
Further reading
- Automating the removal of unused CSS - VelocityConf
- Use Grunt and UnCSS to speed up the load time of your site
- Automating Front-end Workflow (slides)
- Automatically removing unused CSS - Windows
Prior art
For reference, a number of other developers have explored tooling around detecting unused CSS in the past. Some of the other projects worth mentioning are:
-
Opera’s ucss
-
Brian Le Roux’ CSS Slap Chop
-
Python mincss