Introduction to Grunt

I have been fiddling with the idea to try out Grunt as I have seen a lot of buzz around it lately and of course I didn't want to miss the hype. So what is Grunt? Grunt is a JavaScript Task Runner that essentially allows developers to automate tasks in an easy and convenient fashion. The best explanation comes from Grunt's website:

The less work you have to do when performing repetitive tasks like minification, compilation, unit testing, linting, etc, the easier your job becomes.

So true, isn't it? Developers are lazy - lazy, in a sense that they don't like to do repetitive tasks, which could (and should) be automated.

Just think about it a bit, how many times have you:

  • merged your JavaScript files manually?
  • minified your Javascript files by a service?
  • compiled your LESS files?
  • left console.log messages in production code?

If you are nodding right now, I take it that you have experienced at least one of the above scenarios - I know, I have, multiple times for various projects.

Luckily, all of the above can be automated by using Grunt. Let me walk you through this now, step by step by creating a dummy project.

The very first step is to setup a package.json file and we are going to use npm init for this.

I would like to grab the moment here and demistify an incorrect belief. npm does not stand for "node package manager" - although it would make a lot of sense. The combination of these letters have been chosen due to their easy typeability. If you don't believe me, check npm's faq and look for the answer there.

npm init automatically creates the aforementioned package.json file by asking a few questions about the project itself - such as the name, version number, author and so on. You can leave most of the values to default as we are going modify the file anyway.

Based on the entered information and removing a few tags should result in a package.json file similar to this:

{
"name": "grunt-intro",
  "version": "0.0.0",
  "description": "Introduction to Grunt",
  "repository": {
    "type": "git",
    "url": "https://github.com/tpiros/grunt-intro.git"
  },
  "author": "Tamas Piros",
  "license": "BSD"
}

Let's also add two "dummy" JavaScript files under the js directory:

function Example() {};

Example.prototype.methodOne = function(number) {
  console.log("Function argument is set to: " + number);
  var result;
  if (number === 0) {
    result = "Zero."
  } else {
    result = "Not zero.";
  }
  return result;
};

Example.prototype.methodTwo = function(numberOne, numberTwo) {
  console.log("First argument: " + numberOne);
  console.log("Second argument: " + numberTwo);

  var add = numberOne + numberTwo;
  return add;
}

module.exports = Example;
function AnotherExample() {};

AnotherExample.prototype.methodOne = function(number) {
  console.log("Function argument is set to: " + number);
  var result;
  result = number*number;
  return result;
};

AnotherExample.prototype.methodTwo = function(numberOne, numberTwo) {
  console.log("First argument: " + numberOne);
  console.log("Second argument: " + numberTwo);

  var divide = numberOne / numberTwo;
  return divide;
}

module.exports = AnotherExample;

Two, totally random files, with some functions that I have made up in about 2 minutes - I have named them example.js and example2.js.

To get these files ready for a production environment, the following steps would have to be done (at least in an ideal scenario):

  • merge the two files into one
  • remove all logging statements
  • minify the final, merged file

In order to get ready for "grunting" we need to first install Grunt itself, and it's as easy as executing the following statement:

npm install -g grunt-cli

This will add grunt to the system path (due to the -g flag, therefore it will be globally executable, i.e. from every folder on your system. Since version 0.4 grunt-cli does not install the grunt task runner - it only makes sure that it runs the version of grunt that has been installed.

It's time to extend the package.json file by adding a devDepencies section, but mind you, we are not going to do this manually but we will use grunt-cli to take care of this for us.

npm install grunt --save-dev

The above statement installs the grunt task runner and also appends the package.json file by adding the following to it (thanks to the --save-dev flag):

"devDependencies": {
  "grunt": "~0.4.2",
}

So far, so good. We have the grunt task runner installed, so it's time to create its configuration file called Gruntfile.js, which will contain all our automation logic.

The skeleton of this file is the following:

module.exports = function(grunt) {
  //automation logic goes in here
};

First, let's hook up Gruntfile.js with package.json - this is an awesome functionality because we can automatically read out variables from the package file into the grunt file - you will see in a minute what I mean by this. Let's extend the code above slightly and also add our first automation, which will be merging our JavaScript files into one.

module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      dist: {
        src: [
          'js/*.js'
        ],
        dest: 'js/build/allexamples.js'
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.registerTask('default', ['concat']);
};

Here, I am simply reading the content of package.json and I add the concat task which is responsible for merging my files. src defines the files that need merging - here you can define them one by one, as well as use the asterix to merge all files under a particular library; and of course dest defines the location where the merged file should be placed. The great thing is that if the path defined doesn't exist, Grunt will automatically create it.

Let's also have a look at the two final lines. The first one is loading the npm modules that we also need to install first, so please make sure you run the following statement:

npm install grunt-contrib-concat --save-dev

(note the --save-dev flag which will append the package.json file by extending the devDependcies list:

"devDependencies": {
  "grunt": "~0.4.2",
  "grunt-contrib-concat": "~0.3.0"
}

Finally we register the task - this allows us to simply run grunt from the command line as opposed to having to define the tasks one by one - grunt default and grunt concat.

Go ahead and execute grunt from the command line and if you've done everything right, you should have a file called allexamples.js under /js/build/ and it should contain the content of both of the JavaScript files.

This is amazing so far isn't it? Let's take this to the next level and minify our newly created JavaScript. To achieve this, we are going to use another automation task, called uglify. The setup is pretty much similar again, first, we need to install the npm module by executing the following:

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

This again will add an entry to devDependencies.

As a next step, we have to add some configuration to Gruntfile.js:

uglify: {
  files: { 
    src: 'js/build/allexamples.js',
    dest: 'js/build/',
    expand: true,
    flatten: true,
    ext: '.min.js'
  }
}

Note If you have multiple task configurations, you need to append them by a , (comma) just as you'd with a standard JSON file.

Very similar to the concat's configuration, we tell Grunt where the source is, define a destination and an extenstion. (There are a lot more options for uglify than showed here, please refer to their documentation for further information.)

Don't forget to add the appropriate npm task and extend the registerTask method as well:

grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('default', ['concat', 'uglify']);

Note A word of warning here. The order in which you define the tasks in the array is important as grunt will run those in a sequental order. So for example, if you'd uglify your JavaScript and create a .min.js file, the concatination would only merge your files but it wouldn't uglify it.

If you re-run grunt now, you should have a file named allexamples.min.js under the js/build/ folder.

Finally, let's remove all the console.log() statements from our JavaScript file. By now, this should be a straight forward process - intall the npm package, load the task and finally register it.

npm install grunt-remove-logging --save-dev
removelogging: {
  dist: {
    src: "js/build/allexamples.js",
    dest: "js/build/allexamples.js"
  }
}
grunt.loadNpmTasks('grunt-remove-logging');
grunt.registerTask('default', ['concat', 'removelogging', 'uglify']);

Running grunt for a last time should result in you having a JavaScript file similar to this:

/*! grunt-intro 2013-11-27 */
function Example(){}function AnotherExample(){}Example.prototype.methodOne=function(a){var b;return b=0===a?"Zero.":"Not zero."},Example.prototype.methodTwo=function(a,b){var c=a+b;return c},module.exports=Example,AnotherExample.prototype.methodOne=function(a){var b;return b=a*a},AnotherExample.prototype.methodTwo=function(a,b){var c=a/b;return c},module.exports=AnotherExample;

I'm sure you've noticed the JavaScript comment in the first line of the file. That is enabled by one of uglify's options, namely:

banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'

If you recall, I have mentioned that we can hook up the package.json and Gruntfile.js files and here we are using a template using the <%= %> tags and take the name value from the package.json file.

So the JavaScript side of things have been taken care of. Let's do one more thing. I really don't like to compile my LESS files all the time - luckily, Grunt can take care of this for us as well. There's nothing tricky here, the steps are the same as above:

npm install grunt-contrib-less --save-dev
less: {
  development: {
    files: {
      "css/style.css": "less/style.less"
    }
  }
}
grunt.loadNpmTasks('grunt-contrib-less');
grunt.registerTask('default', ['concat', 'removelogging', 'uglify', 'less']);

The example LESS file that I have is magically transformed into a css file:

@base: #f938ab;

.box-shadow(@style, @c) when (iscolor(@c)) {
  -webkit-box-shadow: @style @c;
  -moz-box-shadow:    @style @c;
  box-shadow:         @style @c;
}
.box-shadow(@style, @alpha: 50%) when (isnumber(@alpha)) {
  .box-shadow(@style, rgba(0, 0, 0, @alpha));
}
.box { 
  color: saturate(@base, 5%);
  border-color: lighten(@base, 30%);
  div { .box-shadow(0 0 5px, 30%) }
}
.box {
  color: #fe33ac;
  border-color: #fdcdea;
}
.box div {
  -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
  -moz-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}

This is a very basic introduction to Grunt; it's a very powerful tool and it can achieve a lot more but I hope this post will serve people well who are new to this topic. Grunt is a very powerful tool for automation and it does save developers a lot of time, especially with tasks/operations that are reoccuring over the time.

If you have trouble following the above tutorial I have commited the examples from this article to GitHub, have a look at the repository and the README file for setup instructions to get yourself up and running.

Show Comments