December 2015

Volume 30 Number 13

Visual Studio - Modern Tools for Web Development: Grunt and Gulp

By Adam Tuliper | December 2015

There are many tools available to the modern Web developer. Two that are often found in today’s Web projects are the JavaScript task runners Grunt and Gulp. Using JavaScript to run a task might seem like a foreign concept if you’ve never done it, or if you’re used to plain vanilla Visual Studio Web development, but there are good reasons to give them a try. JavaScript task runners, which work outside of the browser and typically use Node.js at the command line, allow you to easily run front-end development-related tasks, including minification, concatenating multiple files, determining script dependencies and injecting script references in proper order to HTML pages, creating unit test harnesses, processing front-end build scripts like TypeScript or CoffeeScript, and more. 

Which One—Grunt or Gulp?

Choosing a task runner is mostly a personal or project preference, unless there’s a plug-in you want to use that only supports a particular task runner. The primary differences are that Grunt is driven by JSON configuration settings and that each Grunt task typically must create intermediate files to pass things off to other tasks, while Gulp is driven by executable JavaScript code (that is, not just JSON) and can stream results from one task to the next without having to use temporary files. Gulp is the newer kid on the block and, as such, you see a lot of newer projects using it. Still, Grunt has plenty of well-known supporters, such as jQuery, which uses it to build … jQuery. Both Grunt and Gulp work via plug-ins, which are modules you install to handle a particular task. There’s a vast ecosystem of plug-ins available, and often you’ll find a task package that supports both Grunt and Gulp, so, again, using one or the other generally comes down to a personal choice.

Installing and Using Grunt

The installer for both Grunt and Gulp is Node Package Manager (npm), which I briefly covered in my October article (msdn.com/magazine/mt573714). The command to install Grunt actually has two parts. The first is a one-time install of the Grunt command-line interface. The second is installing Grunt into your project folder. This two-part install lets you use multiple versions of Grunt on your system, and use the Grunt command-line interface from any path:

#only do this once to globally install the grunt command line runner
npm install –g grunt-cli
#run within your project folder one time to create package.json
#this file will track your installed npm dependencies
#like grunt and the grunt plug-ins, similar to bower.json (see the last article)
npm init
#install grunt as a dev dependency into your project (again see last article)
#we will still need a gruntfile.js though to configure grunt
npm install grunt –save-dev

Grunt Configuration

The Grunt configuration file is simply a JavaScript file with a wrapper function containing the configuration, plug-in loading and task definition, as shown in Figure 1.

Figure 1 The Grunt Configuration File

module.exports = function (grunt) {
  // Where does uglify come from? Installing it via:
  // npm install grunt-contrib-uglify --save-dev
  grunt.initConfig({
    uglify: {
      my_target: {
        files: {
          'dest/output.min.js': '*.js'
        }
      }
    }
  });
  // Warning: make sure you load your tasks from the
  // packages you've installed!
  grunt.loadNpmTasks('grunt-contrib-uglify');
  // When running Grunt at cmd line with no params,
  // you need a default task registered, so use uglify
  grunt.registerTask('default', ['uglify']);
  // You can include custom code right inside your own task,
  // as well as use the above plug-ins
  grunt.registerTask('customtask', function () {
    console.log("\r\nRunning a custom task");
  });
};

You can run the tasks in Figure 1 at the command line simply by calling:

#no params means choose the 'default' task (which happens to be uglify)
grunt
#or you can specify a particular task by name
grunt customtask

After doing so, you’ll find the uglified (minified) and concatenated result at wwwroot/output-min.js. If you’ve used ASP.NET minification and bundling, you’ll see that this process is different—it isn’t tied to running your app or even compilation, and there are many more options you can choose for tasks like uglifying. Personally, I find this workflow to be more straightforward and understandable.

You can chain tasks together with Grunt to be dependent on one another. These tasks will run synchronously, so that one must complete before moving on to the next.

#Specify uglify must run first and then concat. Because grunt works off
#temp files, many tasks will need to wait until a prior one is done
grunt.registerTask('default', ['uglify', 'concat']);

Installing and Using Gulp

Installing Gulp is similar to installing Grunt. I’ll go into a little more detail with Gulp, but note that you can do similar things with either; I just don’t want to be too repetitive. Gulp has both a global install, so you can use it from any path on your system, and a local install into your project folder that’s versioned to your particular project. The globally installed Gulp will hand off control to the one installed in your local project if it finds it, thus respecting your project’s version of Gulp:

#Only do this once to globally install gulp
npm install –g gulp
#Run within your project folder one time to create package.json
#this file will track your installed npm dependencies
#like gulp and the gulp plug-ins
npm init
#Install gulp as a dev dependency into your project
#we will still need a gulpfile.js to configure gulp
npm install gulp --save-dev

Gulp Configuration and API

Gulp configuration is significantly different from Grunt. The gulpfile.js configuration file, which typically has the structure shown in Figure 2, contains the “requires” for loading plug-ins and then defining tasks. Note that I’m not using JSON configuration settings here; instead, tasks are code-driven.

Figure 2 The Gulp Configuration File

// All the 'requires' to load your
// various plug-ins and gulp itself
var gulp = require('gulp');
var concat = require('gulp-concat');
// A custom task, run via: gulp customtask
gulp.task('customtask', function(){
  // Some custom task
});
// Define a default task, run simply via: gulp
gulp.task('default', function () {
  gulp.src('./lib/scripts/*.js')
    .pipe(concat('all-scripts.js'))
    .pipe(gulp.dest('./wwwroot/scripts'));
});

Gulp works via several key APIs and concepts: src, dest, pipe, task and globs. The gulp.src API tells Gulp which files to open to work with, and those files are then typically piped to some other function, instead of temp files being created. This is a key difference from Grunt. The following example shows some basic examples of gulp.src without piping the results, which I’ll cover shortly. This API call takes what’s known as a glob as a parameter. A glob is basic­ally a pattern you can enter (somewhat like a regular expression) to, for example, specify a path to one or more files (see more about globs at github.com/isaacs/node-glob):

#Tell gulp about some files to work with
gulp.src('./input1.js');
#This will represent every html file in the wwwroot folder
gulp.src('./wwwroot/*.html')
#You can specify multiple expressions
gulp.src(['./app/**/*.js', './app/*.css']

The dest (destination) API, as you might imagine, specifies a destination and also takes a glob. Because globs are so flexible for defining paths, you can either output individual files or output to a folder:

#Tell dest you'll be using a file as your output
gulp.dest ('./myfile.js');
#Or maybe you'll write out results to a folder
gulp.dest ('./wwwroot');

Tasks in Gulp are simply the code you write to do something. The format for tasks is pretty simple, but tasks can be used in several ways. The most straightforward way is to have a default task and one or more other tasks:

gulp.task('customtask', function(){
  // Some custom task to ex. read files, add headers, concat
});
gulp.task('default', function () {
  // Some default task that runs when you call gulp with no params
});

Tasks can be executed in parallel or they can be dependent on each other. If you don’t care about the order, you can just chain them together, as follows:

gulp.task('default', ['concatjs', 'compileLess'], function(){});

This example defines the default task, which does nothing but run separate tasks to concatenate JavaScript files and compile LESS files (assuming that code was in the tasks named here). If the requirements dictate one task completes before the other executes, you need to make one task dependent on another and then run multiple tasks. In the following code, the default task waits for concat to complete first, which in turn waits for uglify to complete:

gulp.task('default', ['concat']);
gulp.task('concat', ['uglify'], function(){
  // Concat
});
gulp.task('uglify', function(){
  // Uglify
});

The pipe API is used to pipe results from one function to another using the Node.js stream API. The workflow typically is: read src, pipe to a task, pipe the results to dest. This complete gulpfile.js example reads all JavaScript files in /scripts, concatenates them into a single file and writes the output to another folder:

// Define plug-ins – must first run: npm install gulp-concat --save-dev
var gulp = require('gulp');
var concat = require('gulp-concat');
gulp.task('default', function () {
  #Get all .js files in /scripts, pipe to concatenate, and write to folder
  gulp.src('./lib/scripts/*.js')
    .pipe(concat('all-scripts.js'))
    .pipe(gulp.dest('./wwwroot/scripts'));
}

Here’s a practical, real-world example. Often you’ll want to concatenate files and/or add informational headers to your source code files. You can easily do that in a few steps by adding a couple of files to the root of your Web site (you could do this task all in code, as well—see the gulp-header docs). First, create a file called copyright.txt that contains the headers to add, like this:

/*
MyWebSite Version <%= version %>
https://twitter.com/adamtuliper
Copyright 2015, licensing, etc
*/

Next, create a file named version.txt containing the current version number (there are plug-ins, like gulp-bump and grunt-bump, to increment version numbers, as well):

1.0.0

Now, install the gulp-header and gulp-concat plug-ins in your project root:

npm install gulp-concat gulp-header --save-dev

Alternatively, you can manually add them to the package.json file and let Visual Studio do the package restore for you.

Last, you just need gulpfile.js to tell Gulp what to do, as shown in Figure 3. If you don’t want to concatenate all your scripts together and instead just add headers to every file, you can simply comment out the pipe(concat) line. Simple, right?

Figure 3 Gulpfile.js

var gulp = require('gulp');
var fs = require('fs');
var concat = require("gulp-concat");
var header = require("gulp-header");
// Read *.js, concat to one file, write headers, output to /processed
gulp.task('concat-header', function () {
  var appVersion = fs.readFileSync('version.txt');
  var copyright =fs.readFileSync('copyright.txt');
  gulp.src('./scripts/*.js')
  .pipe(concat('all-scripts.js'))
  .pipe(header(copyright, {version: appVersion}))
  .pipe(gulp.dest('./scripts/processed'));
});

You then simply run the task via the following command and, voila, you’ve concatenated all the .js files, added a custom header and written the output to the ./scripts/processed folder:

gulp concat-header

The Task Runner Explorer

Visual Studio provides support for Gulp and Grunt via the Task Runner Explorer, which is included in Visual Studio 2015 and available as a Visual Studio Extension. You can find it in View | Other Windows | Task Runner Explorer. The Task Runner Explorer is pretty awesome because it will detect if you have a gulpfile.js or gruntfile.js in the project, parse the tasks and give you a UI to execute the tasks it finds, as shown in Figure 4. Moreover, you can define tasks to run when predefined actions happen in your project, which I’ll cover next because  ASP.NET 5 uses this functionality in its default templates. Simply right-click on a task to execute it or to bind it to a particular action, for example, to execute a task on Project Open.

The Task Runner Explorer Showing Both Grunt and Gulp Tasks with Options
Figure 4 The Task Runner Explorer Showing Both Grunt and Gulp Tasks with Options

As Figure 5 shows, Grunt provides options when you highlight a Grunt task that you won’t see with Gulp, in particular the Force (to ignore warnings) and Verbose options (the F and V on the upper left). These are simply parameters passed to the Grunt command line. The great thing about the Task Runner Explorer is it shows you the commands it passes to the Grunt and Gulp command line (in the task output window), so there’s no mystery as to what’s going on behind the scenes.

Additional Grunt Options and the Command Line
Figure 5 Additional Grunt Options and the Command Line

Gulp and ASP.NET 5

The ASP.NET 5 templates included in Visual Studio 2015 use Gulp, and they install Gulp into your project’s node_components folder so it’s all ready for you to use in your project. You can still use Grunt in an ASP.NET 5 project, of course; you’ll just need to remember to install it into your project via npm or by adding it to packages.json inside devDependencies and letting the auto package restore feature in Visual Studio do its thing. I want to stress: You can do all of this via the command line or inside of Visual Studio, whichever you prefer.

As of this writing, the current ASP.NET 5 templates include a couple of tasks to minify and concatenate .css and .js files. In previous versions of ASP.NET, these tasks were handled in compiled code at run time, which ideally isn’t where or when these sorts of tasks should be done. As you can see in Figure 6, the tasks named clean and min call their css and js methods to minify those files or to clean previously minified files.

Out-of-the-Box Tasks in the ASP.NET 5 Preview Templates
Figure 6 Out-of-the-Box Tasks in the ASP.NET 5 Preview Templates

The following Gruntfile.js line shows another example of running multiple tasks at the same time:

gulp.task("clean", ["clean:js", "clean:css"]);

You have the option to bind Grunt and Gulp tasks to four different actions in Visual Studio. With MSBuild, it was common to define a pre-build and post-build command line to execute various tasks. With the Task Runner Explorer, you can define Before Build, After Build, Clean and Project Open events to execute these tasks. Doing so simply adds comments to the gulpfile.js or gruntfile.js files that don’t affect the execution, but are looked for by Task Runner Explorer. To see the “clean” binding in the ASP.NET 5 gulpfile.js, take a look at this line at the top of the file:

// <binding Clean='clean' />

That’s all that’s required to hook into the event.

Wrapping Up

Both Grunt and Gulp are great additions to your Web arsenal. Both are well supported and have a vast ecosystem of plug-ins available. Every Web project can benefit from something they can provide. For more information, be sure to check out the following:


Adam Tuliper is a senior technical evangelist with Microsoft living in sunny SoCal. He is a Web dev, game dev, Pluralsight author and all-around tech lover. Find him on Twitter: @AdamTuliper or on his Adam’s Garage blog at bit.ly/1NSAYxK.

Thanks to the following Microsoft technical expert for reviewing this article: Michael Palermo