Blog

Grunt.js: The Basics

Most projects can benefit from a good dose of build automation. For much of the unix world "Make" is the tool of choice, but in the web and javascript world Grunt.js is emerging as the de facto standard. If you're working with a javascript project where build tasks still need to be done manually, then grunt is well worth checking out.

First I want to give you a quick intro/tour of Grunt.js. If you're already familiar with grunt look out for a future article on some tricks I've picked up using grunt, and some talk about how to write your own tasks.

Prerequisites

I'm going to assume you have node.js and npm installed and understand how to setup a package.json file and install packages using npm.

Our first step is to install the grunt-cli package globally. This package just provides a small executable that runs the grunt version installed in the current project/directory. You only need to install this once per machine.

npm install --global grunt-cli

Next inside our project we need to install grunt itself (I'm assuming you already have a package.json if not you can run npm init to walk you through creating one). For most of the interesting work grunt uses tasks that are provided by plugins. We'll go ahead and install a couple plugins while we're here.

npm install --save grunt grunt-contrib-jshint grunt-contrib-uglify grunt-contrib-less grunt-contrib-watch

The Whirlwind Tour

OK, lets start looking at grunt and what it gives us.

Let's create a file called Gruntfile.js. When we run grunt it looks for this file and loads it to get all the tasks we've defined. Here's a simple example we'll take a look at:

module.exports = function(grunt) {

    grunt.initConfig({

        jshint: {
            scripts: {
                src: ['scripts/**.js', 'lib/**.js']
            },

            tests: { // We can have more than one jshint task, this ones called `jshint:tests`
                src: 'tests/**.js'
            }
        },

        uglify: {
            scripts: {
                expand: true,
                cwd: 'scripts/',
                src: '**.js',
                dest: 'build/',
                ext: '.min.js'
            }
        },

        less: {
            styles: {
                files: {
                    'build/styles/app.css': 'styles/app.less'
                }
            }
        },

        watch: {
            scripts: {
                files: 'scripts/**.js',
                task: 'jshint:scripts'
            },

            styles: {
                files: 'styles/**.less',
                task: 'less:styles'
            }
        }

    });

    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-watch');

    grunt.registerTask('default', ['jshint', 'less']);
    grunt.registerTask('build', ['jshint', 'uglify', 'less']);

};

That's a good chunk of code, see what each chunk does.

The Wrapper

Grunt expects every Gruntfile to export a single function, it calls this function with a object (normally called grunt) that has methods for configuring grunt. Almost every Gruntfile (and grunt plugins) therefore takes the following format:

// Dependencies can go here if you want

module.exports = function(grunt) {
    // Setup configuration...
    // Load tasks...
    // Define tasks...
};

The order of things inside your function is usually unimportant. With that out of the way, let's take a look at the interesting stuff.

Plugins

Next I'm going to skip down to the grunt.loadNpmTasks lines towards the end. Grunt provides a powerful framework for automating tasks, but to do much of anything interesting we need plugins. Grunt plugins are just npm modules that expose files with code defining one or more tasks. In order to use these tasks we need to first install the npm modules into our project (which we have already done) and tell grunt which npm modules to load tasks from. That's what the grunt.loadNpmTasks method does, we pass it the name of an npm module and it will try to load any tasks from that module.

There are a lot of these plugins out there, you can find a list of them at http://gruntjs.com/plugins. By convention "official" grunt plugins (that is plugins maintained by the grunt.js team) have names beginning with "grunt-contrib-" third party plugins generally just start with "grunt-", but there is nothing requiring or enforcing this convention. It's worth looking over the list to get an idea of what you can do with grunt.js, and remember, if you don't find what you need you can always write your own tasks!

Config

Lets go back up and look at the interesting stuff! After we've got the plugin we want installed, the next step is to configure it. Most plugins define a single "Multi-Task", to use it we create a object in our config with the same key as the Multi-Tasks's name. Inside this object we can have any number of keys, each of which creates an instance of that task or a target, and we include a object telling it how to behave.

Let's take a look at an example. The grunt-contrib-jshint plugin defines a jshint multi-task. So, inside our config we have the following:

jshint: { // We're configuring the jshint multi-task
    scripts: { // This creates a instance of the jshint task called `jshint:scripts`
        src: ['scripts/**.js', 'lib/**.js'] // The task should look at all js files in the scripts directory, and the lib directory
    },

    tests: { // We can have more than one jshint task, this one is called `jshint:tests`
        src: 'tests/**.js'
    }
}

What goes inside each tasks object is largely up to the plugin, but there are some conventions you will see a lot of. Most tasks need a set of files to operate on, and destinations for the results. You can specify these source/destination files in many different ways (the docs list your choices).

The jshint task above shows one common way of listing sources when you don't need a destination, just place a string or array of strings which paths or file globs under the src key.

The uglify task shows and example of grunt's expand option, that allows us to easy build up a list of source destination pairs. The config looks like:

uglify: {
    scripts: {
        expand: true,
        cwd: 'scripts/', // Look for source files relative to the scripts directory
        src: '**.js', // Look for all the .js files
        dest: 'build/', // The destination should start with `build/` instead of `scripts/`
        ext: '.min.js' // Additionally replace the extension of the destination with `.min.js`
    }
},

So if we have some files:

  • scripts/
    • index.js
    • extras/
      • a.js
      • b.js

Grunt build a list of source -> destination pairs like:

  • scripts/index.js -> build/index.min.js
  • scripts/extras/a.js -> build/extras/a.min.js
  • scripts/extras/b.js -> build/extras/b.min.js

If you want to read up on this very useful pattern take a look at the docs.

Aliases

The last part of the file we haven't talked about is the grunt.registerTask lines at the bottom. The registerTask method can be used in several different ways, but this simplest and most common is to create an alias for a list of tasks. By default grunt will try and run a task named default but that task will only exist if we create it.

grunt.registerTask('default', ['jshint', 'less']);
grunt.registerTask('build', ['jshint', 'uglify', 'less']);

The first line creates a new task called default so if we run grunt default or just grunt it will first run ALL the jshint tasks (in our case jshint:sources, then jshint:tests); assuming nothing goes wrong it will then run the less tasks (less:styles).

Then we define another task build, which runs jshint, uglify, and less. We can run this list of tasks with grunt build. It's also worth pointing out that we can run any list of tasks right from the command line so grunt default watch will run jshint:sources, jshint:tests, less:styles, watch.

Watch Task

The watch task is a slightly unusual grunt task. It will keep grunt running, watch for changes to certain files and re-run specified tasks when those files changes. You can use this to run jshint whenever you change your js files to check for common mistakes or to run you css pre-processor whenever you changes your styles. It has several handy options so check out the readme.

Conclusion

That's a lot of stuff to absorb all at once, but hopefully you can start to see just how useful grunt can be, and feel ready to start playing around with grunt and reading the docs (which are quite good). You'll also want to familiarize yourself with the plugins you might use, some of the ones I think most javascript projects could benefit from are: