Grunt.js

The Javascript Task Runner

Josh Bavari / @jbavari

Why use a task runner?

In one word: Automation

Stop being the grunt, use the grunt

I typed 'grunt serve'

With that command, Grunt linted my files, minified CSS & Javascript, ran unit tests, fired up an express server, and watches any changes to css files to automatically update my browser.

It's not a 'new' thing

Its the new thing


You've used some sort of task runner before. Think:

  • Make
  • Rake
  • Ant / Maven
  • Quartz

What can you automate?

(Most) Anything a human can do, Grunt can do (better)


  • Minification
  • Compilation
  • Unit Testing
  • Linting
  • Sass or LESS
  • Preprocessing
  • Live-reloading
  • Image resizing

And more...

Anatomy of a Grunt project

Two main things needed

  • A package.json file to list dependencies
  • A Gruntfile to define the tasks

Anatomy of a package.json file

This file is used by Node.js and list the project dependencies and their versions

{
    "name": "AutomateMe",
    "version": "0.1.0",
    "devDependencies": {
        "grunt": "~0.4.2",
        "grunt-contrib-jshint": "~0.6.3",
        "grunt-contrib-nodeunit": "~0.2.0",
        "grunt-contrib-uglify": "~0.2.2"
    }
}
                        

Anatomy of a Gruntfile

A Javascript file that wraps a grunt function and configures a series of plugins. Any valid javascript can go here.

A Gruntfile has:

  • The wrapper function
  • Project and task configuration
  • Loading Grunt plugins and tasks
  • Custom tasks
  • Other Javascript as needed

The wrapper function

Every Gruntfile (and gruntplugin) uses this basic format, and all of your Grunt code must be specified inside this function:


module.exports = function(grunt) {
  // Do grunt-related things in here
};
                        

Project and task configuration

Most Grunt tasks rely on configuration data defined in an object passed to the grunt.initConfig method. The <% %> template strings may reference any config properties, configuration data like filepaths and file lists may be specified this way to reduce repetition


grunt.initConfig({
    foo_files: ['./*.js'],
    bar_files: ['./*.css'],
    //concat config for concatenating files
    //command line via 'grunt concat'
    concat: {
        foo: {
          // concat task "foo" target options and files go here.
          files: '<%= foo_files %>'
        },
        bar: {
          // concat task "bar" target options and files go here.
          files: '<%= bar_files %>'
        },
    },
}
                        

Loading Grunt Plugins and Tasks

Any plugins you use or tasks you want to import, you use as such:


// Load the plugin that provides the "concat" task.
grunt.loadNpmTasks('grunt-contrib-concat');

//Register the task to run
grunt.registerTask('compile', ['clean', 'concat', 'jshint', 'karma', 'uglify', 'preprocess', 'shell:build']);

//OPTIONAL
//Loads .js files in ./tasks directory
grunt.loadTasks("tasks");

//tasks folder - compile.js
module.exports = function(grunt) {
 
    grunt.registerTask('compile', ['clean', 'concat', 'jshint', 'karma', 'uglify', 'preprocess', 'shell:build']);

};
                        

Custom Tasks

You can define custom tasks with Javascript or multiple tasks with targets


module.exports = function(grunt) {

  // A very basic default task.
  grunt.registerTask('log', 'Log some stuff.', function() {
    grunt.log.write('Logging some stuff...').ok();
  });

};
                        

Multi Tasks


grunt.initConfig({
  log: {
    foo: [1, 2, 3],
    bar: 'hello world',
    baz: false
  }
});

grunt.registerMultiTask('log', 'Log stuff.', function() {
  grunt.log.writeln(this.target + ': ' + this.data);
});
                        

Grunt Options

You can think of Grunt options as the command line parameters you pass to grunt for calling tasks. They can be used anywhere in


module.exports = function(grunt) {

    //grunt express --host=server.raisemore.com
    var host = grunt.option('host');
};
                        

Gruntfile complete example

module.exports = function(grunt) {

//Standard javascript - retrieve a command line parameter
//Say the user typed 'grunt --foo=bar'
var foo = grunt.option('bar') || 'fubar';

  grunt.initConfig({
    // Arbitrary non-task-specific properties.
    my_property: 'whatever',
    our_file_list: ['./js/*.js'],
    pkg: grunt.file.readJSON('package.json'),
    // jshint task configuration here - you can specify several targets per task 
    jshint: {
      all: {
        options: {
          smarttabs: false //specify options for just this target
        }, 
        files: '<%= our_file_list %>' //Special Grunt syntax to get a config variable
      },
      just_a_few: {
        files: ['./js/index.js', './js/nav.js']
      }
      // specify global options for all targets
      options: {
        smarttabs: true,
        eqnull: true,
        eqeqeq: false
      }
    }
  }
});

  // Load the plugin that provides the "jshint" task.
  grunt.loadNpmTasks('grunt-contrib-jshint');

  // Default task(s).
  grunt.registerTask('default', ['jshint']);

  grunt.registerTask('custom', 'This is how you define a custom task.', function(arg1, arg2){
    //Javascript here folks
  });

};
                        

Getting started is easy

  • Install Grunt
  • Create Gruntfile.js
  • Create package.json

Installing Grunt

Grunt mainly runs from its command line interface in the working directory the Gruntfile


                            npm install -g grunt-cli
                        

Working with existing Grunt project

If your working directory has a Gruntfile - just install project dependencies and view the defined tasks!


                            npm install
grunt --help
                        

Preparing a new Grunt project

Create your package.json file - contains all project depenencies

npm init

Create your Gruntfile

Here I'd recommend taking the sample Gruntfile from their website, and start adding in tasks as you see fit

Adding Plugins is easy too

Find the plugin you want to use, and just run npm install

//We use the --save-dev flag to save this entry to our package.json file
npm install grunt-contrib-uglify --save-dev

Register the plugin in Gruntfile

Now that we added a plugin via npm, we must include in Gruntfile

grunt.loadNpmTasks('grunt-contrib-watch');

Lets get dirty in the mud

The best thing about Grunt is the hundreds of plugins that are easy to configure and use. Lets dive in a few common tasks

Code Minification with UglifyJS

Install the plugin

npm install grunt-contrib-uglify --save-dev

Configure the plugin & load npm task

//...snip previous config...
  uglify: {
    subset: {
      files: {
        'dest/output.min.js': ['src/input1.js', 'src/input2.js']
      }
    },
    all: {
      files: {
        'dest/all.min.js': ['']
      }
    }
  }
});

grunt.loadNpmTasks('grunt-contrib-uglify');
                        

Run the task!

grunt uglify

Specify which uglify task target to run

grunt uglify:subset
grunt uglify:all

Getting Sassy

Just like we saw at Thunder Plains, we can use Sass with the sass ruby gem and the grunt sass plugin.

gem install sass
    npm install grunt-contrib-sass --save-dev

Put the sassy pants on

sass: {
  dist: {
    files: {
      './src/stylesheets/styles.css': './src/stylesheets/styles.scss'
    }
  }
}
                        

Run it

grunt sass

Automation on File Modification

Lets watch our SASS / JS / CSS files and when they change, automatically compile or minify.

Grunt Watch Plugin

Using the Grunt Watch plugin, we can monitor file changes and execute tasks on file change events.

//Command Line to install plugin
    npm install grunt-contrib-watch --save-dev

    //Gruntfile to load plugin
    grunt.loadNpmTasks('grunt-contrib-watch');
                        

Gruntfile Config for Watch

// ...snip prior config...
watch: {
  main: {
    files: [ 'Gruntfile.js', 'js/reveal.js', 'css/reveal.css' ],
    tasks: 'default'
  },
  edits: {
    files: [ 'css/editable.css' ],
    options: {
      livereload: 35729,
    }
  }
},
                        

LiveReload

Include livereload.js file - server pushes file changes to browser

#editable_css - edit my css below and watch me change
/* Edit the CSS here and click save to live reload */
#editable_css {
    color: white;
    border: solid 1px red;
}
                        

Automating Unit Testing

Grunt has a wide variety of plugins to assist with testing. Perhaps a good solution would be to set up watch to see any javascript changes, and on those changes execute the unit tests

Jasmine

Run jasmine specs headlessly through PhantomJS.


  jasmine: {
    pivotal: {
      src: 'src/**/*.js',
      options: {
        specs: 'spec/*Spec.js',
        helpers: 'spec/*Helper.js'
      }
    }
  }
                    

QUnit

Run QUnit unit tests in a headless PhantomJS instance.


// Project configuration.
grunt.initConfig({
  qunit: {
    all: ['test/**/*.html']
  }
});
                        

Karma

Run all your unit tests for Jasmine/QUnit/Mocha through multiple browsers Chrome/Firefox/Safari/Phantom


karma: {
  options: {
    configFile: 'karma.conf.js',
    runnerPort: 9999,
    browsers: ['Chrome', 'Firefox']
  },
  continuous: {
    singleRun: true,
    browsers: ['PhantomJS']
  },
  dev: {
    reporters: 'dots'
  }
}
                        

Git Hooks

It is worth mentioning that there is a Grunt plugin for Git-hooks - enforce testing on users before they commit.


githooks: {
    all: {
        'pre-commit': 'test'
    }
}
                    

Image Tasks

Say you want to resize images, or minify them. There are Grunt Plugins for those.

Image resizing


image_resize: {
  android_small: {
    options: {
      height: 426,
      width: 320
    },
    files: {
      //Destination : source
      './android/res/drawable/splash.png': resize_file
    }
  },
  android_normal: {
    options: {
      height: 470,
      width: 320
    },
    files: {
      //Destination : source
      './android/res/drawable-mdpi/splash.png': resize_file

    }
  },
  android_large: {
    options: {
      height: 640,
      width: 480
    },
    files: {
      //Destination : source
      './android/res/drawable-ldpi/splash.png': resize_file,
      './android/res/drawable-hdpi/splash.png': resize_file
    }
  }
                        

Image Minification


imagemin: {
    static: {
      options: {
        optimizationLevel: 3
      },
      files: {
        'dist/img.png': 'src/img.png', // 'destination' : 'source'
        'dist/img.jpg': 'src/img.jpg',
        'dist/img.gif': 'src/img.gif'
      }
    },
    dynamic: {
      files: [{
        expand: true,
        cwd: 'src/', 
        src: ['**/*.{png,jpg,gif}'],
        dest: 'dist/'
      }]
    }
  }
                        

Now, write your own

Theres a Yeoman generator to help you write your own Grunt plugins - https://github.com/yeoman/generator-gruntplugin

References