Web Dev Tools 101: Task Runners

Continuing on from my last article, I am now going to look at task runners. We have already bumped into one task runner when I was developing the ASP.NET vNext project a couple of weeks ago. Today I will take a deeper look at them.


You want a package manager to manage your libraries. You need a task runner to handle your build process. Both Grunt and Gulp do the same thing – it’s just a case of which one you feel more comfortable with.

Why do you need to know?

Task runners have one job – run things. That’s all they do. In particular, they run sets of things, like your build process. Let’s apply that to our problem of a web site. The common wisdom is that if you want your web site to download and execute fast then you need to minimize the number of files you transmit and then minimize the size of those files. This is such a big deal that the F12 developer tools has a specific Network tab to measure how long the page takes to load. Short version – the more files, the longer it takes. The bigger the files, the longer it takes.

But I like highly readable, commented code with nice variable names and nicely laid out. I like the syntax of ECMAScript 6 and LESS stylesheets. In short, I like things I can’t really distribute. I also need to package the libraries I use from their normal location to their web site location, remove all that pesky console and debugger output and who knows what else.

In short, I need to process the files I write before I distribute them.

Enter the task runner. I can tell a task runner to compile my LESS files to CSS then minify them to make them as small as possible, concatenate them all together and put them in a file called web.css in my build area. Similarly, I can tell a task runner to download and copy the latest versions of my Javascript libraries to the right place, and while I’m at it, let’s minify all the Javascript files so they are easy to use. At the same time, I maybe have a development task runner that doesn’t do that – it just puts the files in the right place.

There are two task runners for Javascript – Grunt and Gulp. We’ll take a look at a simple build system in each one.

There are a couple of things you want to look at:

  1. Task Definition. You need to be able to define tasks individually and then build the tasks up into pipelines of tasks. That way you can say “do a build” and it will execute the right things in the right order.
  2. Development vs. Production. You need to be able to say “I do things this way in Development, but I do them this other way in Production.” That way you can have a nice development environment that works when you are writing code, but all the right things happen when you release the code.
  3. File Watching. One of the big boosts in productivity has been file watching. With a file watcher the system is compiled as soon as you change a file, allowing you to get on with the testing immediately. Instead of Write Code – Build – Test, it becomes Write Code – Test. That’s a much tighter loop.
  4. Plugin Ecosystem. If you can’t do something with a task, you won’t use the task runner. As a result, both grunt and gulp have rich ecosystems of plugins. I don’t think you will have a problem here, but there may be a newcomer you are considering instead of grunt and gulp – be aware that this may constrain your choice.

With that, let’s take a look at each one.


Grunt is the older of the two task runners. It uses a Javascript file called Gruntfile.js – you can use Coffeescript as well, but why bother? The Gruntfile.js is going to be small enough and it isn’t “production code” – it’s a build script.

You define individual tasks with a JSON notation inside a initConfig() method. Let’s take a look at a sample (we’ll take a look at the same sample in Gulp later on). We haven’t talked about some of these tools yet, but we will in later articles. Let’s say your build system needs to do the following:

  • test
    • Run jshint on everything in src/js
  • build for development
    • Run the less preprocessor on src/less/site.less and output build/assets/site.css
    • Copy the src/js/*.js files into build/assets/site.js
  • build for production
    • Run the less preprocessor on src/less/site.less
    • Clean the CSS Output before output to build/assets/site.css
    • Minify the src/js/*.js files into build/assets/site.js

Three “super-tasks” to get things done. Let’s take a look at the task definition:

module.exports = function(grunt) {
		jshint: {
			files: [ 
		less: {
			development: {
				files: {
					"build/assets/site.css": "src/less/site.less"
			production: {
				options: {
					plugins: [
						new require('less-plugin-clean-css')(cleanCssOptions)
				files: {
					"build/assets/site.css": "src/less/site.less"
		uglify: {
			production: {
				files: {
					'build/assets/site.min.js': [ "src/js/*.js" ]
		copy: {
			development: [
					expand: true,
					src: [ 'src/js/**' ],
					dest: 'build/assets'

Most of the tasks are two-level – a task type (like jshint, or uglify) corresponds to a plugin and everything is a plugin. The second level is generally a tag that you can make up – like development or production. In each case the documentation on the grunt website for the plugin has been good, with all the options explained fully and generally an example is provided. Mostly I just cut and paste the example and then tweak the paths for my situation.

The next step in the file is to load the plugins that we are using. Each plugin has a name and you want to add this to your package.json file so that npm can load them for you.


Finally, you want to define your task lists. I don’t want to run each task individually – I want composite tasks for my workflow:

	grunt.registerTask('build', [ 'jshint' ]);
	grunt.registerTask('development', [ 'jshint', 'less:development', 'copy:development' ]);
	grunt.registerTask('production', [ 'less:production', 'uglify:production' ]);

The final close-curly-brace is the closing brace for the initConfig() method. With these three sections defined, I can run grunt with the following:

grunt build development

It will run the named tasks in order.

Some of the good stuff – a wide variety of plugins means you can get the job done and probably have an example or two to handle the situation you need. It’s also got good Visual Studio support, so you can link tasks to specific build actions in Visual Studio.

Now on to the bad stuff. I find the syntax a little obtuse. Sometimes you need two levels and sometimes one. You can organize this file and split it up into sub-files, but basically you are operating at a task level. Still, it does the job and does it well.


Gulp is the new kid on the block. It does exactly the same stuff. It has plugins and does all the same stuff. It just does it using more-Javascript-like syntax rather than JSON syntax for configuration. It “solves” perceived problems in Grunt, the main one being that grunt plugins may do multiple things – gulp plugins concentrate on doing one thing. But it’s a plugin – let’s see how long that lasts.

Let’s take a look at the same example as before, but in Gulp. Instead of a gruntfile.js you have a gulpfile.js – same function, different name. Whereas Grunt concentrated on defining the tasks and then linking those tasks together, Gulp concentrates on the pipeline first.

var gulp = require('gulp');
var jshint = require('gulp-jshint');
var less = require('gulp-less');
var concat = require('gulp-concat');
var uglift = require('gulp-uglify');

gulp.task('jshint', function() {

gulp.task('scripts', function() {
	gulp.src([ './src/js/*.js' ])

gulp.task('devscripts', function() {
	gulp.src([ './src/js/*.js' ])

gulp.task('style', function() {
	gulp.src([ './src/css/site.less' ])

gulp.task('production', [ 'style', 'scripts' ]);
gulp.task('development', [ 'style', 'devscripts' ]);

Our first section deals with the modules that we are using. The format here is CommonJS (for node compatibility). I really like this new format – much more syntactically cleaner than the RequireJS one (more on module loading in a later article). We need to download these modules with npm install before they are used – just like in the grunt case.

Each task is now a pipeline. It starts with a source – what are we operating one. Each stage is a pipe, and it ends (generally, but not always) with a pipe to a destination. For instance, I can write the scripts task as:

cat ./src/js/*.js | uglify > ./build/assets/all.js

I can run a task individually, like ‘jshint’ using gulp jshint. I also have defined two combination tasks at the bottom for building the style and the scripts together. In the case of development I had to use an extra task for handling the minification with uglify.

The major downside to gulp is documentation. I found all the documentation I needed, including tutorials and examples, right on the grunt page. I had to go a googling in order to find tutorials and examples for gulp. I’m sure they will close that gap rapidly, however.

The Verdict

Which one is better? Do you like defining tasks or pipelines? Do you prefer JSON or Javascript as a notation? Answer those questions and you have the answer to which one you believe is better. I’m choosing gulp because I came from a UNIX background and more recently I play a lot with PowerShell. The pipeline approach seems more appropriate to me.

You can’t lose with either one. My suggestion is to pick your likely candidate and use it.