Grunt Boilerplate

What we'll cover

reading time: approx. 19mins

Please note that any additional updates have been added to the bottom of this post to ensure no cross-over between the original focus and new items that might contradict that

What is Grunt?

Grunt is a JavaScript based task runner. What this means is that it will help you spend less time manually running tasks that can be automated.

Grunt comes with many pre-built tasks available and that can automate the majority of a developer's typical work flow. You also have the facility to write your own custom tasks if you have a specific requirement that isn't already catered for.

There are approximately 700+ tasks built for Grunt (as of May 2013) and the list is growing. Some of the most popular tasks I'll be covering in this article…

…but there are many more for tasks such as: compiling CoffeeScript to JavaScript, concatenating files, connecting to a web server, copying files and folders, precompiling handlebar templates, live reload within a web browser, compiling code documentation - just to name a few.

See the Grunt Plugins page for a more comprehensive list (but also check GitHub).

Installation

Grunt uses Node.js and Node's package manager system (NPM) to handle the installation of Grunt tasks.

Essentially (as of Grunt 0.4) there are three items you need to install to get up and running with Grunt…

  1. Node.js
  2. NPM
  3. Grunt CLI (command line interface)

The easiest way to install these are using Homebrew which is an excellent package manager for OS X (if you use Windows, my apologies but you're on your own).

Your mileage may vary, but if you have homebrew installed you can run the following commands via your terminal to install Node.js and NPM: brew install node.

The next step is to install Grunt CLI which you can do using: npm install -g grunt-cli (the -g means to install it globally on your OS so that you can use the grunt command where ever you are on your system).

Now you'll also want to run: npm install -g grunt-init and once that's done you've effectively installed the basic requirements for Grunt.

Package.json

Before you can start using Grunt you'll need a package.json file which stores all the base configuration settings.

To automatically generate this file run: npm init.

Below is the package.json file for my Grunt Boilerplate project...

{
    "name": "Grunt Boilerplate",
    "version": "0.1.0",
    "description": "This is a project set-up using Grunt to take case of some standard tasks such as: compiling AMD based modules using RequireJS, watching/compiling Sass into CSS, watching/linting JS code and some other things such as running unit tests",
    "main": "Gruntfile.js",
    "dependencies": {},
    "devDependencies": {
        "grunt": "~0.4.1",
        "grunt-contrib-watch": "~0.3.1",
        "grunt-contrib-jshint": "~0.4.3",
        "grunt-contrib-uglify": "~0.2.0",
        "grunt-contrib-requirejs": "~0.4.0",
        "grunt-contrib-sass": "~0.3.0",
        "grunt-contrib-imagemin": "~0.1.4",
        "grunt-contrib-htmlmin": "~0.1.3",
        "grunt-contrib-jasmine": "~0.4.2",
        "grunt-template-jasmine-istanbul": "~0.2.1",
        "grunt-template-jasmine-requirejs": "~0.1.1",
        "grunt-contrib-connect": "~0.3.0"
    },
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "repository": {
        "type": "git",
        "url": "git@github.com:Integralist/Grunt-Boilerplate.git"
    },
    "keywords": [
        "Grunt",
        "JavaScript"
    ],
    "author": "Mark McDonnell",
    "license": "MIT"
}

…most of it is populated with content I provided when I ran npm init and the rest (such as devDependencies) was automatically generated for me when I started installing Grunt tasks (see next section).

Dependencies

Typically we don't want dependencies we install to be installed globally (remember we installed Grunt globally using the -g flag).

The reason being, the dependencies you install for Project X might be different from your next project.

For example, you might install a Grunt task with a version number of 1.0 and by the time you start your next project that task might have been updated to version 2.0 and part of the major version update was a complete change in their API. So if you globally updated the task, your last project would break as it's using the old API.

But you still want to use the latest and greatest version of that task. So instead of installing your dependencies globally, install them locally (e.g. install them into the specific project you're currently working on) which means there will no chance of conflicts between projects.

Below are the tasks I install locally as part of my Grunt Boilerplate project…

…a couple of things to note here are: the first item you see me install is Grunt itself (which is separated from the Grunt CLI). As we've already discussed, it means we can install different versions of Grunt if we want (depending on the project requirements).

The other thing is the use of the --save-dev flag which means our package.json file gets automatically updated to include the dependency we've just installed.

Because these dependencies are installed locally, you'll notice a new node_modules folder has appeared within your project. This folder holds all of the above dependencies/tasks we've just installed.

I suggest you create a .gitignore file (you are using Git for your version control system, right?) that ignores this folder (you don't want to end up pushing these modules into your repo for other users to pull down, best to let them install these themselves as per my instructions above).

OK, now that we have all the dependencies/tasks installed, lets look at the remaining piece of the puzzle, the Gruntfile.js.

Gruntfile.js

Gruntfile.js is the main set-up file and contains the settings for each task we have installed.

Because Grunt is run using Node.js you'll notice the content of the file is wrapped in a closure and assigned to a module exports property…

module.exports = function (grunt) {
    grunt.initConfig({
        // our Grunt task settings
    });
};

…within the function we call initConfig on the Grunt object, and we pass through an object literal holding our task settings.

One of the first properties we set is pkg: grunt.file.readJSON('package.json') which means that from within the other properties of our object we can access settings specified within our package.json configuration file.

For example, if we wanted to access the name of our package (which if you remember was set "name":"Grunt Boilerplate") then we could access it from within our object using <%= pkg.name %>.

From here, this is where we now start to explore the different tasks we previously installed (for full details of each task's own settings please go to their corresponding website/github repo).

Sass

The Sass task allows us to compile our Sass files into CSS.

Here is an example...

sass: {
    dist: {
        options: {
            style: 'compressed',
            require: ['./assets/styles/sass/helpers/url64.rb']
        },
        expand: true,
        cwd: './app/styles/sass/',
        src: ['*.scss'],
        dest: './app/styles/',
        ext: '.css'
    },
    dev: {
        options: {
            style: 'expanded',
            debugInfo: true,
            lineNumbers: true,
            require: ['./app/styles/sass/helpers/url64.rb']
        },
        expand: true,
        cwd: './app/styles/sass/',
        src: ['*.scss'],
        dest: './app/styles/',
        ext: '.css'
    }
}

You'll see in the above example we've set-up two sub tasks dist and dev.

The reason I've created two sub tasks is because while I'm developing my application I want my Sass files to compile in to expanded CSS (including debugging information), but when I finish my project I want to compile my Sass files in to compressed/minified CSS instead.

You'll see for each sub task I specify a options object which tells the compiler how I want the Sass to be compiled and where to find any additional Sass helper scripts.

You'll find in my Grunt Boilerplate (used in the above example) that I include a Sass helper script require: ['./app/styles/sass/helpers/url64.rb'] which allows you to use a special Sass function to convert a background image into a Base64 encoded string which is better performing than making an additional HTTP request for an image to be used - it also works around the IE8 issue where an Base64 encoded string can't be greater than 32kb of data.

Finally, we are using a Grunt specific pattern that allows us to better target and export multiple files: expand: true. Effectively that setting allows the other properties to follow it to be activated (if we didn't have expand: true set then the properties following it wouldn't work).

Let's look at the other properties a little bit closer, as you'll see them used in other tasks in a similar way so it's important for you to understand how they work…

cwd: './app/styles/sass/' - here I'm setting the 'current working directory' (this is where I want Grunt to find my Sass files).

src: ['*.scss'] - here I'm telling Grunt that I want it to look for any scss files within my 'current working directory'.

dest: './app/styles/' - here I'm telling Grunt that I want it to export the files to this directory.

ext: '.css' - and that I want each exported file to have an extension of css.

To run this specific task we could open our terminal and execute: grunt sass (which would execute both sub tasks), or we could run a specific sub task like so: grunt sass:dev.

I won't explain the running of specific tasks again from here because effectively it's exactly the same for all tasks.

RequireJS

This task is effectively the r.js build script that comes with RequireJS so I won't go into the details of how to write a build script - I'll instead refer you to the r.js documentation, but know that the following has exactly the same settings as you would have used for r.js previously…

requirejs: {
    compile: {
        options: {
            baseUrl: './app',
            mainConfigFile: './app/main.js',
            dir: './app/release/',
            fileExclusionRegExp: /^\.|node_modules|Gruntfile|\.md|package.json/,
            modules: [
                {
                    name: 'main'
                }
            ]
        }
    }
}

JSHint

JSHint is a task that mimics the web interface in that it lets you specify files to lint (i.e. makes sure the code is written with valid syntax according to a set of rules you want it to abide by)…

jshint: {
    files: ['Gruntfile.js', 'app/**/*.js', '!app/release/**', 'modules/**/*.js', 'specs/**/*Spec.js'],
    options: {
        curly:   true,
        eqeqeq:  true,
        immed:   true,
        latedef: true,
        newcap:  true,
        noarg:   true,
        sub:     true,
        undef:   true,
        boss:    true,
        eqnull:  true,
        browser: true,

        globals: {
            // AMD
            module:     true,
            require:    true,
            requirejs:  true,
            define:     true,

            // Environments
            console:    true,

            // General Purpose Libraries
            $:          true,
            jQuery:     true,

            // Testing
            sinon:      true,
            describe:   true,
            it:         true,
            expect:     true,
            beforeEach: true,
            afterEach:  true
        }
    }
}

…some things to note: we're using the bang operator ! to tell Grunt to ignore specific directories. So you'll see we said !app/release/ which means DON'T lint that particular directory because it will likely cause errors according to the rules set for JSHint (because that directory is our minified version of our JavaScript code that RequireJS compiled for us).

Also, you'll see we've told JSHint about certain variables that we expect to be present (i.e. to be globally available). We need to do this because JSHint inspects our JavaScript files in isolation so although when we run our application all the scripts are loaded together, at the time of linting JSHint isn't aware of these different globals and so if we didn't tell it about them it would just error.

Finally, for the full list of rules that JSHint abides by see the JSHint site.

Jasmine BDD

Jasmine is a Behaviour-Driven unit testing framework and assertion library.

This is probably one the most complicated task we cover because it requires a few other tasks.

The first first task it relies on is the connect task which fires up a web server using PhantomJS. But realise that this isn't required if your scripts have no DOM interaction (unlikely if you're doing web development), but because we're doing DOM manipulation and we're testing that interaction we need a DOM to test against, hence the need for the connect web server task…

connect: {
    test: {
        port: 8000
    }
}

…after that we set-up the actual jasmine task…

jasmine: {
    src: ['app/**/*.js', '!app/release/**'],
    options: {
        host: 'http://127.0.0.1:8000/',
        specs: 'specs/**/*Spec.js',
        helpers: ['specs/helpers/*Helper.js', 'specs/helpers/sinon.js'],
        template: require('grunt-template-jasmine-requirejs'),
        templateOptions: {
            requireConfig: {
                baseUrl: './app/',
                mainConfigFile: './app/main.js'
            }
        }
    }
}

…you'll see we have specified a host option which relates to the connect web server task.

We also specs option where we tell Grunt where to find out BDD spec/test files.

We have a helpers option where we can load additional scripts required for us to utilise for testing (this is where I load an additional script called Sinon.js which handles Spies, Mocks and Stubs within our unit tests).

Next comes the other complicated part of this task: we need a 'template' sub task which is specifically built for Jasmine grunt-template-jasmine-requirejs. We need this additional task because we're using AMD for making our JavaScript code modular, but AMD introduces race condition issues when it comes to running our async based scripts against a dynamic spec runner such as our Grunt Jasmine task.

You'll see that we just need to tell grunt-template-jasmine-requirejs where our base JS directory is and which file is the main AMD file to bootstrap our application. In the background the task generates a _SpecRunner.html file which basically requires in every single AMD modules and then starts running our tests within a callback once all the modules are loaded. It's not the greatest solution but it works.

If the tests pass then the _SpecRunner.html is removed so you never notice it, but if there are any failing tests then the file is left in your root directory so you can inspect the file and run it manually via a real web browser and try and debug any failing tests.

Image Minification

The ImageMin task does exactly what you would expect, it searches out any images it finds (png or jpg format) and compresses them so they are smaller in file size.

As you can see from the below example we're using the expand: true setting (and subsequent settings) to tell Grunt where to find our images and where to export them to…

imagemin: {
    png: {
        options: {
            optimizationLevel: 7
        },
        files: [
            {
                expand: true,
                cwd: './app/images/',
                src: ['**/*.png'],
                dest: './app/images/compressed/',
                ext: '.png'
            }
        ]
    },
    jpg: {
        options: {
            progressive: true
        },
        files: [
            {
                expand: true,
                cwd: './app/images/',
                src: ['**/*.jpg'],
                dest: './app/images/compressed/',
                ext: '.jpg'
            }
        ]
    }
}

HTML Minification

The HTMLMin task does exactly what you would expect, it searches out any HTML files it finds and compresses them so they are minified and thus smaller in file size.

As you can see from the below example we've NOT used the expand: true setting but instead this task provided a slightly different api to let us tell Grunt where to find our HTML files and where to export the minified versions to (but effectively we could use expand: true if we wanted)…

htmlmin: {
    dist: {
        options: {
            removeComments: true,
            collapseWhitespace: true,
            removeEmptyAttributes: true,
            removeCommentsFromCDATA: true,
            removeRedundantAttributes: true,
            collapseBooleanAttributes: true
        },
        files: {
            // Destination : Source
            './index-min.html': './index.html'
        }
    }
}

Registering tasks

So far we've looked at running specific Grunt tasks like so: grunt sass:dev and grunt jshint but you can also set-up a custom task which does nothing but run other tasks.

For example, you could create a task which when run would execute a specific set of tasks…

grunt.registerTask('release', ['jshint', 'jasmine', 'requirejs', 'sass:dist', 'imagemin', 'htmlmin']);

…in the above example we've created a 'release' task which when run will get our files ready to be pushed to our production environment. I would typically run this task when I was finished building my application.

As you can see it lints my JavaScript files, and then makes sure my JavaScript tests are passing. It then runs my RequireJS build script and then my dist Sass sub task (the one specifically for our production server, so it minifies all the compiled CSS), it then finally minifies any images it finds and minifies our HTML files.

When you execute the command grunt by itself then it will look for a registered task called default

grunt.registerTask('default', ['jshint', 'connect', 'jasmine', 'sass:dev']);

…in this case we're telling it to execute our JavaScript linting task, then to check our unit tests are still passing and finally to generate debug versions of our CSS.

Watching files

Instead of manually running a Grunt command every time we make a change (e.g. imagine making a change to your Sass file and then having to go to the terminal and running grunt sass:dev to get the Sass to compile to CSS just so you can see the change reflected in your browser), we can instead get Grunt to do the hard work for us and to automatically run a task (or multiple tasks) whenever a specific set of files have been changed/updated…

watch: {
    files: ['<%= jshint.files %>', '<%= jasmine.options.specs %>', '<%= sass.dev.src %>'],
    tasks: 'default'
}

…here you can see we're using the 'watch' task along with a specific interpolation syntax to let it know what files to keep an eye on for us.

The syntax is: <%= jshint.files %> and in this instance it means "look at the jshint property and return the value set on its sub property files".

So as you can see from the above example, we're telling the watch task to look out for any changes made to our JavaScript and Sass files, and if so run the default registered task.

Our full Grunt file

You can find the full file on my Grunt Boilerplate repo

Conclusion

Hopefully this guide gives you a good starting point to begin using Grunt to automate more of your work flow. It's an extremely powerful tool and we haven't even begun to scratch the surface as there is the custom task features built-in that let you write your own tasks that interact with the file system and do pretty much anything you want. Well worth investigating further.

Note: my Grunt Boilerplate project is constantly being updated so watch the repo to keep up to speed.

Updates

Since my original work on Grunt Boilerplate there has been lots of new tips and tricks that have come to light. I'll briefly list them below and leave it up to the reader to investigate further...

Vote on HN