We're setting up a new ASP.NET 5 ASP.NET Core 1.0 project in Visual Studio 2015, and my team is trying to get used to the idea of "Task Runners" such as Grunt.js or Gulp.js. We're brand new to this idea, and my personal philosophy is What I don't understand, I cannot change, so obviously I need to understand these pieces of tech before I can hope to use them properly. Let's see if we can understand what a Task Runner is, what Gulp.js can do for us, and how using these makes building our applications a little bit easier.

What is a Task Runner?

In Visual Studio, a Task Runner contains collection of "tasks" that can be executed either on demand, or automated as part of a build process. Visual Studio includes native support for Grunt.js and Gulp.js task runners, and each of these is designed to make building client-side resources easier. For this project, we're using Gulp.js as our task runner, so the rest of this demo will use Gulp.js. But what is Gulp.js?

What is Gulp.js?

Gulp.js bills itself as the "streaming build system". Gulp.js provides a set of automation tools that allow us developers to automate common processes, such as bundling and minification of CSS and JS files. In short, Gulp.js makes these processes part of the build for your application.

(NOTE: because Gulp.js performs bundling and minification, I'd be willing to bet that the default MVC bundling and minification will be disappearing at some point in the future)

The Default Gulpfile.js

In ASP.NET Core projects, Gulp.js and Visual Studio use a file called gulpfile.js to define and bind tasks (we'll get into what "binding" a task means just a bit further down). Now let's look at this file, created by Visual Studio 2015 in an ASP.NET Core 1.0 Release Candidate 1 project:

/// <binding Clean='clean' />
"use strict";

var gulp = require("gulp"),  
    rimraf = require("rimraf"),
    concat = require("gulp-concat"),
    cssmin = require("gulp-cssmin"),
    uglify = require("gulp-uglify");

var paths = {  
    webroot: "./wwwroot/"
};

paths.js = paths.webroot + "js/**/*.js";  
paths.minJs = paths.webroot + "js/**/*.min.js";  
paths.css = paths.webroot + "css/**/*.css";  
paths.minCss = paths.webroot + "css/**/*.min.css";  
paths.concatJsDest = paths.webroot + "js/site.min.js";  
paths.concatCssDest = paths.webroot + "css/site.min.css";

gulp.task("clean:js", function (cb) {  
    rimraf(paths.concatJsDest, cb);
});

gulp.task("clean:css", function (cb) {  
    rimraf(paths.concatCssDest, cb);
});

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

gulp.task("min:js", function () {  
    return gulp.src([paths.js, "!" + paths.minJs], { base: "." })
        .pipe(concat(paths.concatJsDest))
        .pipe(uglify())
        .pipe(gulp.dest("."));
});

gulp.task("min:css", function () {  
    return gulp.src([paths.css, "!" + paths.minCss])
        .pipe(concat(paths.concatCssDest))
        .pipe(cssmin())
        .pipe(gulp.dest("."));
});

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

This file allows us developers to use Gulp.js through the new Task Runner Explorer window, which looks something like this:

But, what does this file actually do? Let's break it down.

Visual Studio Bindings and Strict Mode

The first couple of lines of code in gulpfile.js are these:

/// <binding Clean='clean' />
"use strict";

The XML you see is Visual Studio's binding. Clean='clean' states that the 'clean' task should be run when Visual Studio executes a Clean.

The second line forces strict mode. Douglas Crockford explains:

"Strict Mode is an opt-in mode that repairs or removes some of the language’s most problematic features."

This is one of those features that, for now, I'm too wary of to mess with. Needless to say, read Crockford's post if you want to understand more of what strict mode is supposed to do.

Node Packages Required

The next block of code looks like this:

var gulp = require("gulp"),  
    rimraf = require("rimraf"),
    concat = require("gulp-concat"),
    cssmin = require("gulp-cssmin"),
    uglify = require("gulp-uglify");

This imports packages from Node Package Manager (NPM), similarly to how NuGet imports packages for server-side development. Currently we're importing five packages.

  • gulp: The gulp package itself.
  • rimraf: A deep deletion module which allows for deletion of files and folders.
  • gulp-concat: Concatenates files (AKA bundling).
  • gulp-cssmin: Minifies CSS files.
  • gulp-uglify: Minifies JS files.

We'll be using each of these packages in this default file.

Path Setup

The next block of code is relatively easy to understand:

paths.js = paths.webroot + "js/**/*.js";  
paths.minJs = paths.webroot + "js/**/*.min.js";  
paths.css = paths.webroot + "css/**/*.css";  
paths.minCss = paths.webroot + "css/**/*.min.css";  
paths.concatJsDest = paths.webroot + "js/site.min.js";  
paths.concatCssDest = paths.webroot + "css/site.min.css";  

All this is doing is setting up some paths we'll be using later. Note the "**" wildcard. The "**" wildcard specifies that any folder at that location is to be included in the path. This allows you to have as many subfolders under "js" or "css" folders as you want, and any JS or CSS files in the subfolders can still be targeted and processed by Gulp.js.

Clean Tasks

Now we start to see the implementations of the tasks themselves (after all, the tasks are what this thing can actually do). Here's the implementation for the Clean tasks.

gulp.task("clean:js", function (cb) {  
    rimraf(paths.concatJsDest, cb);
});

gulp.task("clean:css", function (cb) {  
    rimraf(paths.concatCssDest, cb);
});

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

Note that the first two tasks, "clean:js" and "clean:css", call the rimraf() function to delete certain files. The "clean" task removes the minified files if they exist. Further, showing good coding standards, the "clean" task simply calls the "clean:js" and "clean:css" tasks, and doesn't do any work itself.

Min Tasks

Now we get to a more complicated task. Here's the first "min" task:

gulp.task("min:js", function () {  
    return gulp.src([paths.js, "!" + paths.minJs], { base: "." })
        .pipe(concat(paths.concatJsDest))
        .pipe(uglify())
        .pipe(gulp.dest("."));
});

We're defining a task called "min:js" and specifying that it do three things:

  1. Concatenate (or bundle) all the files at the paths in the paths.js set, but that are not in the paths.minJs set into one file.
  2. Minify the resulting file.
  3. Place the resulting minified file at root location.

A similar set of things happens in the "min:css" task:

gulp.task("min:css", function () {  
    return gulp.src([paths.css, "!" + paths.minCss])
        .pipe(concat(paths.concatCssDest))
        .pipe(cssmin())
        .pipe(gulp.dest("."));
});

The only difference is that we're using cssmin() to minify CSS files rather than uglify().

And, of course, the final line of code combines both of these tasks into one call:

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

In short, the "min" tasks bundle and minify your CSS and JS files.

Running Tasks

Now we can investigate how to run these tasks using Visual Studio. Take a look at the structure of a brand-new ASP.NET Core 1.0 RC1 Project in Visual Studio 2015:

Note the site.css and site.js files. What happens after we run the "min" task? You can run a task by right-clicking it and selecting "Run".

Now, we'll see that the site.css and site.js files each have a minified version.

It follows that running the default "clean" would remove those minified files.

Binding Tasks to Events

I mentioned at the beginning of this post that you can bind these tasks to run during certain Visual Studio events. Any task can run during any event, and each task can run during multiple events. The events we can bind Gulp.js tasks to are:

  • Before Build
  • After Build
  • Clean
  • Project Open

We can bind tasks to events by using the interface (right-click the task you want to bind):

You can also do it directly by modifying the binding code in the gulpfile.js:

/// <binding BeforeBuild='min, clean' Clean='clean' />

Summary

The integration of Gulp.js into Visual Studio 2015 makes managing JS, CSS, and HTML files much simpler. We can create tasks for common operations like bundling and minification, and those tasks can now automatically be run as part of events like Clean or After Build. All-in-all, Task Runners like Gulp.js bring Visual Studio and ASP.NET Core just another step closer to being the best IDE and project stack out there. Of course, I might be just a little biased.

Did this help, or did I miss something about Gulp.js or Task Runners that you found useful? Need to yell at me because you're frustrated and no one else is around? Sound off in the comments!

Happy Coding!

NOTE (1/25/2016): This post has been updated to reflect the naming change from ASP.NET 5 to ASP.NET Core 1.0