Use Grunt and unCSS to speed up the load time of your site

More often than not, our phones have a slow, unreliable and high latency connectivity. Yet most common websites are not optimized to make the best use of the limited bandwidth. Let's see how we can optimize that.

Article page # Requests Download size Load time (3G connection)

Techcrunch

103 requests

2.6MB

35s

Medium

78 requests

2.7MB

37s

TheNextWeb

245 requests

2.2MB

46s

NYTIMES.com

38 requests

2.5MB

31s

CNN.com

59 requests

0.6MB

14s

I've used Network Link Conditioner, the tool provided by Apple to simulate a 3G connection.

There are 3 main strategies to address this:

Strategy Description Time gain Efficiency

Concatenate

Reduce the number of requests by concatenating javascript and css files

~100ms x number of files concatenated

Really efficient strategy, you can save 1s by concatenating 10 files. Ideally you should only have 1 CSS file and 1 JavaScript file.

Minimize

Reduce the file size by minimizing javascript and css files

100ms per 10KB saved

Minimizing bootstrap.css saves 20KB (16%). Javascript can be further compressed. jQuery minimized is 82KB instead of 236KB. Total saving: 174KB.

Reduce

Reduce the file size of your CSS by removing unused rules

100ms per 10KB saved

a typical bootstrap template only uses a fraction of all the CSS rules. You can save 85% of the file size by removing unused rules. That's a 80KB reduction.

There are plenty of documentation about concatenating and minimizing javascript and CSS files (grunt-contrib-uglify is your friend). So here I will mainly focus on the Reduce strategy. If you are using CSS libraries like Bootstrap or if your CSS files are old and full of legacy code, then you probably have a lot of unused rules that take bandwidth for no reason.

What follows assumes a very simple HTML site with the following directory structure:

src/
 |- js/
    |- bootstrap.min.js
 |- css/
     |- bootstrap.css
     |- carousel.css // custom css file            
 |- index.html

Automatically remove unused CSS rules

Grunt is a Javascript task runner. It's very helpful to manage installation process and builds. That way you can keep your development environment easy to manage and only compile/concatenate/minimize before deploying to production.

You can install Grunt with npm

npm install -g grunt-cli

There are a bunch of common tasks that have already been developed for Grunt (see the Grunt Plugin Directory). We'll install 4 of them: grunt-contrib-copy, grunt-processhtml, grunt-contrib-uglify and grunt-uncss.

npm install grunt-contrib-copy grunt-processhtml grunt-uncss grunt-contrib-uglify --save-dev

Now let's add a Gruntfile.js to configure those plugins:

module.exports = function(grunt) {
  grunt.initConfig({
    copy: {
      dist: {
        cwd: 'src/', expand: true, src: '**', dest: 'dist/'
      }
    },
    // Remove unused CSS across multiple files, compressing the final output
    uncss: {
    dist: {
      files: [
        { src: 'src/*.html', dest: 'dist/css/compiled.min.css'}
      ]
      },
      options: {
        compress:true
      }
    },
    processhtml: {
      dist: {
        files: {
        'dist/index.html': ['src/index.html']
        }
      }
    },
    uglify: {
      dist: {
        files: {
          'dist/js/compiled.min.js': ['src/js/jquery.js','src/js/*.js'] // make sure we load jQuery first
        }
      }
    }
  });

  // Load the plugins
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-uncss');
  grunt.loadNpmTasks('grunt-processhtml');
  grunt.loadNpmTasks('grunt-contrib-uglify');

  // Default tasks.
  grunt.registerTask('default', ['copy', 'uglify', 'uncss','processhtml']);

};

By running grunt, it will

  1. copy the entire src/ directory into the dist/ directory
  2. concatenate and minimize all the javascript files in one file dist/js/compiled.min.js
  3. scan all your html files in src/, extract all the stylesheets, dom elements and classes and create one single CSS file with only the rules that match your dom elements
  4. minimize the resulting CSS and save it in dist/css/compiled.min.css

The result built version will look something like that:

package.json // npm package configuration file
Gruntfile.js // Grunt configuration file
dist/
 |- js/
    |- bootstrap.min.js
 |- css/
     |- compiled.min.css       
 |- index.html
src/
 |- ...

The last thing to do is to make sure that your index.html file in the dist/ directory loads the new compiled CSS instead of the two former CSS files. To do so, we'll use the grunt-processhtml plugin. In your src/index.html file just add the following HTML comments:

<!-- build:css css/compiled.min.css -->
  <link href="css/bootstrap.css" rel="stylesheet">
  <link href="css/carousel.css" rel="stylesheet">
<!-- /build -->

and same thing for the javascript scripts at the bottom of the file:

<!-- build:js js/compiled.min.js -->
  <script src="js/jquery.js"></script>
  <script src="js/bootstrap.js"></script>
  <script src="js/holder.js"></script>
<!-- /build -->

Before

After

File Size Gain

src/css/compiled.min.css

13KB

86.6KB (87%)

Try for yourself

This is the list of commands that will create the directory structure to test this method with the Bootstrap Carousel Template Example. This assumes that you already have node and npm installed.

# Skip this if you already have grunt installed
npm install -g grunt-cli

mkdir src
mkdir src/css
mkdir src/js

# Downloading the different files
curl "http://getbootstrap.com/examples/carousel/" > src/index.html
curl "http://getbootstrap.com/examples/carousel/carousel.css" > src/css/carousel.css
curl "http://getbootstrap.com/docs-assets/js/holder.js" > src/js/holder.js
curl "http://getbootstrap.com/dist/css/bootstrap.css" > src/css/bootstrap.css
curl "http://getbootstrap.com/dist/js/bootstrap.js" > src/js/bootstrap.js
curl "https://code.jquery.com/jquery-1.10.2.js" > src/js/jquery.js

# /!\ You'll need to manually edit `src/index.html` to link to the correct css and js files

npm install grunt-uncss grunt-processhtml grunt-contrib-copy grunt-contrib-uglify

# Create a Gruntfile.js as described above and then run:
grunt

Limitations

The uncss library only works with static html files. That means that if you add any html using Javascript, it won't include the CSS rules that match any new html tag or class. A quick solution is to add in the Gruntfile.js the ignore option for the grunt-uncss task. You can pass an array of CSS rules to always include. That works but it's not ideal. It would be better to make use of PhantomJS to automatically generate the page on the fly.

Xavier Damman

About the author

I'm Xavier Damman and I'm the cofounder and former CEO of Storify (acquired by Livefyre in 2013). I'm now the general manager of Storify at Livefyre. I write about startups, politics and random things. I like hacking and talking. You can follow me on Twitter @xdamman.