Write your own Grunt tasks

Last updated on Jan 20th, 2014

A primer on custom tasks

Aliasing existing tasks

Task aliasing is useful for when you want to run multiple tasks in sequence without having to invoke Grunt specifically for each one. The way to do this is:

grunt.registerTask('mytask', 'Optional task description', ['jshint', 'qunit', 'concat']);

This way you can simply run

grunt mytask

...and it will trigger the succession of jshint, qunit and concat.

We usually want to define a default task that will run when we simply type grunt in the command line:

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

In addition, the list of tasks can come with specific targets:

grunt.registerTask('default', ['watch:stylesheets']);

Working with files

Chances are your app will want to operate on files. You can learn about the different ways users can define files in the Files In-Depth chapter, and there certainly are a bunch of them! Fortunately, Grunt does the heavy-lifting for us and normalizes all formats into the Files Array format, expanding all the patterns it finds along the way.

So really all we have to do is grab the array from this.files containing objects which have two main properties:

Let's try this out!

Assume we have a project structure like this one:

app/
    src1.js
    src2.js
Gruntfile.js
package.json

We write the following Grunt code:

grunt.initConfig({
    logfiles: {
        filesObject: {
            files: {
                'dist/file1.js': ['app/src*.js']
            }
        },
        filesArray: {
            files: [{
                src: ['app/src*.js'],
                dest: 'dist/file1.js'
            }]
        }
    }
});

grunt.registerMultiTask(
    'logfiles', 
    'Log the source-destination mappings in a Grunt multitask', 
    function() {
        this.files.forEach(function(file) {
            grunt.log.writeln('File ' + file.dest + ' has the sources ' + file.src);
        });    
    }
);

Firstly, we created two targets corresponding to two different ways of defining files for our tasks. Secondly, we defined our multitask as a simple loop through the this.files array.

Now let's check what each of them outputs:

grunt logfiles:filesObject
> Running "logfiles:filesObject" (logfiles) task
> File dist/file1.js has the sources app/src1.js, app/src2.js

grunt logfiles:filesArray
> Running "logfiles:filesArray" (logfiles) task
> File dist/file1.js has the sources app/src1.js, app/src2.js

Identical — one less thing for us to worry about!

Excluding sources that don't exist

A good practice in all tasks is to filter out the source files that don't actually exist in the project:

this.files.forEach(function(file) {
    var sources = file.src.filter(function(path) {
        if (!grunt.file.exists(path)) {
            grunt.log.warn('Source file ' + path + ' not found.');
            return false;
        } else {
            return true;
        }
    });
});

Now the sources variable contains only the subset of files that are valid.