Skip to main content

Introduction to Grunt

7 min read

Older Article

This article was published 13 years ago. Some information may be outdated or no longer applicable.

I’d been wanting to try Grunt for a while. So what is it? Grunt is a JavaScript Task Runner. It lets developers automate repetitive tasks in an easy, convenient way. The best explanation comes straight from Grunt’s site:

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

Hard to argue with that. Developers are lazy (in the best sense). We don’t want to do repetitive things that should be automated.

Think about how many times you’ve:

  • merged JavaScript files by hand?
  • minified JavaScript files through some web service?
  • compiled your LESS files?
  • left console.log messages in production code?

If you’re nodding, you’ve hit at least one of those. I know I have, multiple times across different projects.

Grunt automates all of the above. Let me walk you through it step by step with a dummy project.

First, set up a package.json file using npm init.

Quick aside: npm does not stand for “node package manager”, even though that’d make sense. The letters were chosen because they’re easy to type. Don’t believe me? Check npm’s FAQ.

npm init creates the package.json file by asking a few questions about the project: name, version number, author, and so on. Leave most values at their defaults since we’ll be modifying the file anyway.

You should end up with something like 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 create 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 random files I knocked together in about two minutes. I’ve named them example.js and example2.js.

To get these production-ready, we need to:

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

First, install Grunt itself:

npm install -g grunt-cli

This puts grunt on the system path (the -g flag makes it globally executable). Since version 0.4, grunt-cli doesn’t install the grunt task runner itself. It just ensures the right version runs.

Now extend the package.json by adding a devDependencies section. We won’t do this manually. Let grunt-cli handle it:

npm install grunt --save-dev

This installs the grunt task runner and appends package.json with the following (thanks to --save-dev):

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

Good so far. Grunt’s installed. Time to create its configuration file, Gruntfile.js, which holds all the automation logic.

The skeleton:

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

Let’s hook Gruntfile.js up with package.json. This is a great feature because we can read variables from the package file right into the grunt file (you’ll see what I mean in a moment). Let’s extend the code and add our first automation: merging JavaScript files.

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']);
};

We read package.json and add the concat task for merging files. src defines which files to merge (you can list them individually or use a wildcard). dest is the output location. If the path doesn’t exist, Grunt creates it.

Look at the last two lines. The first loads the npm module, which we need to install:

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

(The --save-dev flag extends devDependencies:)

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

Finally, we register the task. This lets us run just grunt from the command line instead of specifying tasks individually like grunt default and grunt concat.

Run grunt from the command line. If everything’s right, you’ll find allexamples.js under /js/build/ with the contents of both JavaScript files.

Brilliant so far. Let’s go further and minify our new JavaScript. We’ll use the uglify task. Same pattern: install the npm module first:

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

This adds another entry to devDependencies.

Then add the 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, separate them with a , (comma) like a standard JSON file.

Similar to concat: we tell Grunt the source, destination, and extension. (There are many more uglify options. Check the documentation for details.)

Don’t forget to load the npm task and update registerTask:

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

Note The order of tasks in the array matters. Grunt runs them sequentially. If you uglified first and then concatenated, the final file would be merged but not minified.

Run grunt again. You should now have allexamples.min.js in the js/build/ folder.

Last step: strip out all console.log() statements. By now you know the drill. Install, load, register.

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']);

Run grunt one last time. You should end up with a JavaScript file like 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;

You’ve probably noticed the JavaScript comment on the first line. That comes from one of uglify’s options:

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

Remember how we hooked up package.json and Gruntfile.js? Here we’re using a template with <%= %> tags to pull the name value from package.json.

JavaScript’s sorted. One more thing. I really don’t want to compile LESS files by hand. Grunt handles that too. Same steps as before:

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']);

My example LESS file gets transformed into CSS:

@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);
}

That’s a basic run through Grunt. It can do a lot more, but I hope this helps anyone getting started. Grunt saves developers real time on tasks that come up again and again.

If you had trouble following along, I’ve committed the examples to GitHub. Check the repository and the README file for setup instructions.