Perfecting your Grunt Workflow

Deep within the brain of a developer, nestled between the gentle folds of the nucleus accumbens, lives a strong and primitive craving for automation. The concept of a function, f(x)=y, and likely algebra itself induces this region of the brain to release a powerful, pleasurable burst of dopamine.

It is from this area of the brain that Grunt, the automation system extraordinaire, was born. Grunt saves you the effort of running repetitive tasks such as concatenation, minification, translation (LESS -> CSS), testing, deployment, code linting, and more. It’s a bit like a Makefile with a bunch of community-contributed bells and whistles written in JavaScript.

A well-configured Gruntfile can put you close to nirvana but getting there may involve a good deal of amygdala-coordinated anger and rage. This post skips the basics to offer advanced tips and tricks for perfecting your Grunt workflow developed while building a Japanese Mahjong AI and simulator. The Mahjong codebase is written in Node.js but there is no reason you couldn’t use Grunt to automate your language of choice.

The following sections will use my Gruntfile as an example with specific sections reproduced here for clarity.

Stylesheets

As a warmup here is code to manage all your stylesheets.


var less_src_files = ['public/css/src/**/*.less'];

// within grunt.initConfig
less: {
    development: {
        files: {
            'public/css/dist/all.css': less_src_files
        }
    },
    production: {
        options: {
            cleancss: true
        },
        files: {
            'public/css/dist/all.min.css': less_src_files
        }
    }
}

The less directive converts LESS files in the specified folders to CSS, creating a concatenated version (all.css) and a minified version of the same (all.min.css). The latter uses class-css for minification.

JavaScript

Next let’s deal with JavaScript concatenation and minification.


var js_src_files = ['public/js/global/**/*.js',
    'shared/**/*.js',
    'public/js/src/**/*.js'
];

// within grunt.initConfig
concat: {
    options: {
        separator: ';\n'
    },
    dist: {
        src: js_src_files,
        dest: 'public/js/dist/all.js'
    }
},

uglify: {
    options: {
        banner: '\n'
    },
    build: {
        src: js_src_files,
        dest: 'public/js/dist/all.min.js'
    }
}

The concat directive gathers files within the specified directories producing all.js as a result. Note that the order of the folders is maintained which can be useful if you need some global libraries (jQuery, socket.io, Underscore, etc.) to appear before other functions.

The uglify section performs a similar task, concatenating the same JavaScript files and producing a minified all.min.js. In development you can serve all.js while saving all.min.js for production.

The banner option is useful if you would like to produce a short comment at the top of your JavaScript file.


meta: {
    banner:
        '/*!\n' +
        ' * mahjong.js <%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd, HH:MM") %>)\n' +
        ' * MIT licensed\n' +
        ' *\n' +
        ' * Copyright (C) 2013 Benjamin Gleitzman\n' +
        ' */'
}

You might have noticed that there is a bit of duplicated effort — surely uglify must also concatenate files so why not run concat followed by uglify? The answer is concurrency, the next task on our list.

Concurrency

Some Grunt tasks can take a while to complete. The concurrent directive orchestrates running tasks in parallel.


concurrent: {
    compress: ['less', 'concat', 'uglify'],
    start: {
        tasks: ['mochaTest', 'nodemon', 'watch'],
        options: {
            logConcurrentOutput: true
        }
    }
},

Running grunt concurrent:compress causes our three previously mentioned steps (less, concat, and uglify) to be run in parallel. In practice running these steps concurrently, rather than running concat followed by uglify, appears to be faster.

Let’s shift focus to the last three tasks mentioned by concurrent: testing, application monitoring, and task triggering.

Testing

This section is rather simple. Mocha tests are located and run from the test/ directory with the results are printed to standard output.


mochaTest: {
    test: {
        options: {
            reporter: 'spec'
        },
        src: ['test/**/*.js']
    }
},

Application Monitoring

When modifying files you would like your application to restart.


var port = grunt.option('port') || 3000;

// within grunt.initConfig
nodemon: {
    dev: {
        options: {
            nodeArgs: ['--port', port]
        }
    }
},

Nodemon watches files in the directory where Grunt was started and restarts the application server whenever the files change.

Task Triggering

The watch directive triggers other tasks when certain files are changed. Thus, editing a LESS file will result in a new version of all.min.css.


watch: {
    files: ['public/**/*.*',
        'shared/**/*.*',
        'views/partials/**/*.*',
        '!**/dist/**'
    ], // ignore dist folder
    tasks: ['concurrent:compress']
}

When any of the files within public/, shared/, or views/partials/ are modified the concurrent:compress task will be run (concurrently concatenating and minifying CSS and JavaScript). Note that the dist/ folder is ignored since we don’t want to end up in an infinite loop when minified files are created.

Hopefully this post gets you a little closer to Grunt nirvana.