Getting started with Grunt

Lately, there's been an uprising of all kinds of tools and frameworks to enhance your workflow. Some are just redundant and some are genius, like Grunt.js.

The last couple of months, I've been working on a specific project with a great team, using a lot of different frameworks and libraries like Backbone.js, Underscore.js, require.js, Bower and so on. Since it's such a complex project, we had to start thinking about letting these tools do stuff for us instead of trying to manage everything ourselves.

For example, all of these frameworks and libraries have versions so keeping them all up to date can be a time-consuming task. Therefor, we started using Bower to manage all of our packages, together with require.js to include them when necessary. I'll probably write about those as well, but I wanted to introduce Grunt.js first because it's a really simple thing, that can save you a lot of time and money.

What's Grunt.js

So what is this thing you're speaking of you ask? Grunt.js is a JavaScript task runner, simple as that. It's great for automating tasks you normally would execute manually and above all, it's free!

A fine example could be the watching and compiling of .less files. Most people would use an app to do this. Some are free, like the Less.app, and for some you have to pay, like Codekit. But why waste time and money if you can use a tool like Grunt.js to manage this for you?
Of course, this is a pretty simple task, but Grunt has plugins for about anything you can think of and if it doesn't exist yet, you can create your own plugin easily.

Let's get started

Setting up Grunt is easy. They have great documentation but I'll sum up the most important steps here.

  1. Make sure you have npm installed because this is used to install and manage Grunt and the plugins you want to use.
  2. Install the grunt-cli globally, so the grunt command is in your system path and can be executed from any directory, by running npm install -g grunt-cli from the command line.
  3. Now, your project needs a package.json and a Gruntfile.js. The package.json file is used by npm and should contain the dependencies for your project.
    The Gruntfile.js will contain tasks and load Grunt plugins for you. Both of these files should be in the root of your project and committed with your source code.

Package.json

There are three ways to add this file. You can use the grunt-init or the npm init command which will create a basic package.json file or you can start from a sample file like this one:


{
  'name': 'my-project-name',
  'version': '0.1.0',
  'devDependencies': {
    'grunt': '~0.4.1',
    'grunt-contrib-jshint': '~0.1.1',
    'grunt-contrib-nodeunit': '~0.1.2'
  }
}

Once you've added this file, you can start adding the plugins by running npm install <module> --save-dev for each plugin you need. This command will install the module and add it to the list of devDependencies for you (at least, if you use the --save-dev parameter).

An example of a package.json file I'm using for a Jekyll project is as follows:

	
{
  "name": "Product site",
  "description": "Development setup for the product site.",
  "version": "1.0.0",
  "author": "Thomas Deceuninck ",
  "engines": [
    "node"
  ],
  "repository": {
    "type": "git",
    "url": "url-to-the-repository"
  },
  "devDependencies": {
    "grunt": "0.4.0",
    "grunt-cli": "0.1.6",
    "grunt-contrib-watch": "0.3.1",
    "grunt-contrib-clean": "0.4.0",
    "grunt-contrib-cssmin": "0.4.1",
    "grunt-contrib-less": "0.5.0",
    "grunt-shell": "~0.2.1"
  }
}

Gruntfile.js

Now that we have added the package.json file, we can add stuff to the Gruntfile.js. This file should contain the following parts:

  • a "wrapper" function
  • projects and task configuration
  • loading grunt plugins and tasks
  • custom tasks

There's an example file here, but I'll immediately skip to the file I use for my Jekyll project in combination with the above package.json file.

	
module.exports = function (grunt) {
    grunt.initConfig({
        // The clean task ensures the parsed css is removed
        clean: ["_site/css/"],

        // Compress generated css files
        cssmin: {
                "css/screen.css": ["css/screen.css"]
        },

        // Automatically run a task when a file changes
        watch: {
            styles: {
                files: ["css/less/*"],
                tasks: "less"
            }
        },

        // Compile specified less files
        less: {
            compile: {
                options: {
                    // These paths are searched for @imports
                    paths: ["css/less"]
                },
                files: {
                    "css/screen.css": "css/less/screen.less"
                }
            }
        },

        // Add shell tasks
        shell: {
            copyCss: {
                command: "cp css/screen.css _site/css/screen.css"
            }
        }
    });

	// Load tasks so we can use them
    grunt.loadNpmTasks("grunt-contrib-watch");
    grunt.loadNpmTasks("grunt-contrib-clean");
    grunt.loadNpmTasks("grunt-contrib-cssmin");
    grunt.loadNpmTasks("grunt-contrib-less");
    grunt.loadNpmTasks("grunt-shell");

    // The default task will show the usage
    grunt.registerTask("default", "Prints usage", function () {
        grunt.log.writeln("");
        grunt.log.writeln("Product site development");
        grunt.log.writeln("------------------------");
        grunt.log.writeln("");
        grunt.log.writeln("* run 'grunt --help' to get an overview of all commands.");
        grunt.log.writeln("* run 'grunt dev' to start developing.");
    });

    // The dev task will be used during development
    grunt.registerTask("dev", ["clean", "less:compile", "watch:styles"]);
};

Here, I use Grunt for some simple stuff when running the "dev" task:
I first delete the already parsed css file ("clean" task) and compile all less files ("less:compile" task). Since Jekyll checks for changed files to compile when its server is running, I added this to make sure that all .less files are compiled again, without having to edit a .less file first to trigger a change. This could occur if you have edited a .less file, before you fired up the Jekyll server.

Note that the clean and the "less:compile" tasks are only executed once when you run the grunt dev task.
After that, the watch task is running, which compiles all edited styles in the directory we specified ('css/less/*').

I created this "dev" task to combine some tasks so that I wouldn't have to run them separately. Running grunt clean, grunt less:compile and grunt watch:styles one by one would work as well.

I also added an extra task to demonstrate the possibilities of Grunt: the shell task. There's this grunt-shell plugin that lets Grunt execute commands on the command line for you. The power of the command line is infinite so I'm sure you get the picture when I'm saying that Grunt.js is capable of doing almost everything you can think of.

The example "shell:copyCss" task here, actually does the same as the clean task: it removes the original screen.css and replaces it with the latest compiled version.

Some other examples of more complex tasks could be the use of require.js through a Grunt task or even firing up a proxy server.

From the moment you have to perform a task more than once while working on a project, you should take a minute to add it to your Gruntfile.js and let it do that stuff for you.
Just run the npm install <module> --save-dev again, add the task to your Gruntfile.js and you're good to go.

This little tool has helped me to enhance my workflow a lot and I this post encourages you to check it out.