Automate all the things! An introduction to Grunt

One of the most important tools in my development workflow is Grunt. Grunt is a JavaScript task runner which helps you automate repetitive tasks. I honestly couldn't do without it anymore.

In this blog post I will walk you through setting up a starter project for static websites with Grunt automation built in.

The example project can be found on Github.

Installing Grunt

Since Grunt runs on Node.JS, you will first have to install Node if you haven't already. Their website offers installers and instructions.

Next, we will need the Grunt commandline interface. This is a module which we will install globally. It provides your system with the grunt command. Run the following command from a terminal window:

npm install -g grunt-cli

Note: You might need to use sudo depending on your system setup.

Next, in your project directory, add a package.json file.

package.json

{
  "name": "grunt-starter",
  "version": "0.0.0",
  "private": true,
  "dependencies": {},
  "devDependencies": {}
}

From your terminal, issue the following command:

npm install grunt --save-dev

This will persist the Grunt module to package.json and install it in our project.

Now create a Gruntfile in the root of your project. The Gruntfile is where we will configure the task runner.

Gruntfile.js

'use strict';

module.exports = function(grunt) {  
  grunt.initConfig({});
};

Defining the project structure

For the sake of this example, let's say we're developing a landing page. A static one pager. The project structure could look like this:

/dist
/images
/javascripts
  app.js
/stylesheets
  /config
  /components
  /helpers
  main.scss
index.html  

We will let Grunt generate deployment-ready files in the dist folder.

Integrating SCSS

I will be using Sass in this project because it's my preprocessor of choice. Let's see how we can integrate it in our project and automate compilation, etc. with Grunt.

Let's first install a Grunt task to deal with Sass. We will be using grunt-sass here instead of grunt-contrib-sass. The latter requires us to have Ruby installed while grunt-sass makes use of the Node.JS based compiler node-sass. It's still experimental but definitely a lot faster. I haven't had any problems with it so far. It does, however, only compile .scss files. If you work with the older syntax (.sass files), use grunt-contrib-sass instead.

In a terminal, run:

npm install grunt-sass --save-dev

Now that we have the module installed, we can set it up in our Gruntfile.

We specify two targets: dev and dist, which we will use later on.

The dev target compiles the SCSS without compression and adds source comments to ease debugging.
The dist target compresses the SCSS for deployment.

We load the module through grunt.loadNpmTasks().

Gruntfile.js

'use strict';

module.exports = function(grunt) {  
  grunt.initConfig({
    sass: {
      dev: {
        files: {
          'stylesheets/main.css': 'stylesheets/main.scss'
        },
        options: {
          outputStyle: 'expanded',
          sourceComments: 'normal'
        }
      },
      dist: {
        files: {
          'dist/stylesheets/main.css': 'stylesheets/main.scss'
        },
        options: {
          outputStyle: 'compressed'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-sass');
};

We can now run grunt sass to execute both targets, or call a specific target with grunt sass:dev or grunt sass:dist.

We do not want to run grunt sass:dev everytime we make a change to our SCSS files though. Luckily, the Grunt ecosystem provides some plugins to watch files for changes!

Watching files

Install grunt-contrib-watch by running the following command:

npm install grunt-contrib-watch --save-dev

Next, configure the watch task in Gruntfile.js. We configure a sass target on the watch task and tell it to watch our *.scss files. When it detects a change, it should run sass:dev.

Gruntfile.js

grunt.initConfig({  
  watch: {
    sass: {
      files: ['stylesheets/**/*.scss'],
      tasks: ['sass:dev'],
      options: {
        spawn: false
      }
    }
  },
  sass: {
   // ...
  });
});

You can test this by running grunt watch in your terminal. Try changing a SCSS-file. You should see something similar to this in your terminal:

$ grunt watch
Running "watch" task  
Waiting...OK  
>> File "stylesheets/main.scss" changed.

Running "sass:dev" (sass) task  
File "stylesheets/main.css" created.  

It would be awesome if our browser automatically reloaded the page when Grunt detects a change and recompiles. We can do this using livereload.

Leveraging livereload

Grunt-contrib-watch includes the option to start a livereload server with the watch task. We can enable this option by setting it to true:

Gruntfile.js

watch: {  
  sass: {
    files: ['stylesheets/**/*.scss'],
    tasks: ['sass:dev'],
    options: {
      spawn: false,
      livereload: true
    }
  }
}

However, this won't do anything as of now. We need to include a livereload script in our HTML page. We can do this manually by including:

<script src="http://localhost:35729/livereload.js?snipver=1"></script>  

in our HTML, but then we have to remember taking it out before deployment. It's far easier to let Grunt do this for us and not think about it twice.

Install grunt-contrib-connect and connect-livereload.

npm install grunt-contrib-connect connect-livereload --save-dev

Once again, we configure the plugin in Gruntfile.js. We specify that we want our development server to run on port 3000. We want to open the page in our browser when we start the server and we include the connect-livereload middleware which will include the script in our webpage.

Gruntfile.js

grunt.initConfig({  
  connect: {
    dev: {
      options: {
        port: 3000,
        open: true,
        middleware: function(connect, opts) {
          return [
            require('connect-livereload')(),
            connect.static(require('path').resolve(__dirname))
          ];
        }
      }
    }
  }
});

// ...
grunt.loadNpmTasks('grunt-contrib-connect');  

If you run grunt connect, you'll notice that the task immediately stops running. We have to run grunt connect watch to start the server, then start the watch task while the server keeps running. Because we don't want to run that command every time, we can register a custom task:

grunt.registerTask('dev', ['sass:dev', 'connect', 'watch']);

This task will first run the Sass task so we have the latest stylesheets, then it will start the server and watch for changes. We can now simply run:

grunt dev

Let's also add a default task so we can just run grunt and have it run our dev task.

grunt.registerTask('default', ['dev']);

Compressing images

To automatically compress our images, we can use grunt-contrib-imagemin.

Again, install the task:

npm install grunt-contrib-imagemin --save-dev

Configure and load the task.

Gruntfile.js

imagemin: {  
  dist: {
    files: [{
      expand: true,
      cwd: 'images/',
      src: ['**/*.{png,jpg,jpeg,gif}'],
      dest: 'dist/images/'
    }]
  }
}

// ...

grunt.loadNpmTasks('grunt-contrib-imagemin');  

We can now use Grunt to compress our images. However, I like to do this only once: when deploying. It's a task that often takes a bit of time and we don't want to be slowed down while developing. Thus, we will register another custom task specifically for deployment purposes:

grunt.registerTask('build', ['sass:dist', 'imagemin:dist']);  

Clean up

You might have noticed that we always need to load the tasks we want to use. We only have four tasks at the moment but Gruntfiles can grow big. We don't always want a whole block of grunt.loadNpmTasks() in our file.

Let's install load-grunt-tasks and solve this problem right away.

npm install load-grunt-tasks --save-dev

At the top of your Gruntfile, include this line:

require('load-grunt-tasks')(grunt);`  

And remove the loading lines:

grunt.loadNpmTasks('grunt-sass');  
grunt.loadNpmTasks('grunt-contrib-watch');  
grunt.loadNpmTasks('grunt-contrib-connect');  
grunt.loadNpmTasks('grunt-contrib-imagemin');  

Optimizing Modernizr

If you use Modernizr, you might be using a version which includes all feature tests. This can create a bit of overhead, so we automate the process of creating a customized Modernizr build using grunt-modernizr.

Configure it:

Gruntfile.js

modernizr: {  
  devFile: 'remote',
  outputFile: 'dist/javascripts/modernizr.min.js',
  files: [
    'stylesheets/**/*.scss',
    'js/**/*.js',
    '*.html',
    'images/**/*.*',
    '!./dist/javascripts/modernizr.min.js'
  ],
  extra: {
    "shiv" : true,
    "printshiv" : false,
    "load" : true,
    "mq" : false,
    "cssclasses" : true
  },
  extensibility: {
    "addtest" : false,
    "prefixed" : false,
    "teststyles" : false,
    "testprops" : false,
    "testallprops" : false,
    "hasevents" : false,
    "prefixes" : false,
    "domprefixes" : false
  }
}

We add this to our custom build task. It's another task that takes quite some time to process and we don't want to slow down our development.

grunt.registerTask('dist', ['sass:dist', 'imagemin:dist', 'modernizr']);  

Compressing JavaScript

We usually will have more than one JavaScript file in our project. How do we handle those with Grunt? Let's say our example website contains the following files:

/javascripts
    app.js
    library.A.js
    library.B.js

We will be using a couple of grunt tasks to deal with this. Our goal is to concatenate all these JavaScript files into /dist/javascripts/app.js so that we only have to include one file in our website.

Let's first install the necessary plugins:

npm install grunt-usemin grunt-contrib-concat grunt-contrib-uglify grunt-contrib-copy --save-dev

The flow of this will be as follows:

We define the source files and our target in the HTML page. We copy this file to our dist folder. We let useminPrepare generate a config for concat and uglify, and then we run usemin which will: modify our HTML, concatenate the JS files and minimize them.

Copying index.html

Include the following script block at the bottom of index.html:

<!-- build:js javascripts/app.js -->  
<script src="javascripts/library.A.js"></script>  
<script src="javascripts/library.B.js"></script>  
<script src="javascripts/app.js"></script>  
<!-- endbuild -->  

The comments surrounding the scripts tell usemin that we want to concatenate them into /dist/javascripts/app.js.

Because usemin cannot (at least I haven't been able to) copy the file into another folder for us, we have to do this manually. Let's configure the copy task:

Gruntfile.js

copy: {  
  dist: {
    src: 'index.html',
    dest: 'dist/index.html',
  }
},

Configuring usemin

Next, we configure the usemin tasks:

Gruntfile.js

useminPrepare: {  
  html: 'dist/index.html',
  options: {
    root: './'
  }
},
usemin: {  
  html: 'dist/index.html'
},

We can then update our dist task:

grunt.registerTask('dist', [  
  'sass:dist',
  'imagemin:dist',
  'copy:dist',
  'useminPrepare',
  'concat',
  'uglify',
  'usemin',
  'modernizr'
]);

If we run this, we see Grunt generate an index.html file in /dist with an updated reference to the concatenated/uglified JavaScript file.

Watching JavaScript files

When working with JavaScript files, we also want our website to automatically update when we make a change. We can easily add this to the configuration of the watch task:

Gruntfile.js

watch: {  
  sass: {
    // ...
  },
  js: {
    files: ['javascripts/**/*.js'],
    options: {
      spawn: false,
      livereload: true
    }
  }
},

Automate all the things!

We now have a decent starter project for simple, static webpages. As you have seen, the Grunt ecosystem provides us with a lot of handy plugins which are easily configurable.

Some of the other plugins I frequently use in my projects are:

The example project can be found on Github.

Contact me on Twitter.