How to use Gulp in Visual Studio

Updated Sept 16, 2015: Improved sample gulpfile.
Updated Aug 30, 2015: Improved sample gulpfile and added sections to answer common questions.

What is Gulp?

Gulp calls itself the streaming build system. No, this isn’t a replacement for build systems like msbuild or nant. In this case, we are talking about building the client side parts of our applications like JavaScript files, StyleSheets (CSS, SASS or LESS) and HTML files.

The basic idea with Gulp is that you use pipes to stream a set of data (usually files) through some kind of processing. As it turns out, it is pretty easy to use and is probably best described using an example.

Installing Node and Gulp

If you don’t already have it installed, download and install node.js.

If you are using VS 2015 and ASP.NET 5, Visual Studio will install npm and gulp for you automatically.

Once node is installed, we need to install gulp using the node package manager (npm). From the command line, run

npm install gulp -g

Setting up your Visual Studio project

Rather than create a new sample project here, I’m going to use the Hot Towel SPA template from John Papa. First, I will create an new empty web application and then install the HotTowel.Angular nuget package.

Install-Package HotTowel.Angular

I wanted to use this template as an example because it is a perfect candidate for Gulp. The application is written using AngularJS and the code is split across 14 different JS files. From a code maintenance / readability standpoint, it is definitely good to split the code into files like this. From an application loading performance standpoint however, loading 14 separate JS files is generally not a great idea.

Here is a snapshot of the traffic captured using Fiddler.

Let’s see what we can do to fix this using Gulp.

Initializing our project for Gulp

First, we need to create a package.json file in the root directory of the your project. We can do this by running npm init on the command line or simply creating a file with the following contents:

{

"name": "YourProjectName",

"version": "1.0.0"

}

Next, we will install a few packages that we will use for this project. Run the following commands from the same folder that you added the package.json file.

npm install gulp --save-dev
npm install gulp-concat --save-dev
npm install gulp-uglify --save-dev
npm install del --save-dev

Note, the --save-dev option here is telling node to add these packages to a devDependencies section in the package.json file and install the packages in a node_modules folder in the current folder . At any time, you or another developer on your team can re-install all the devDependencies by simply running npm install.

Finally, create a gulpfile.js file in the same folder.

// include plug-ins
var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var del = require('del');

var config = {
//Include all js files but exclude any min.js files
src: ['app/**/*.js', '!app/**/*.min.js']
}

//delete the output file(s)
gulp.task('clean', function () {
//del is an async function and not a gulp plugin (just standard nodejs)
//It returns a promise, so make sure you return that from this task function
// so gulp knows when the delete is complete
return del(['app/all.min.js']);
});

// Combine and minify all files from the app folder
// This tasks depends on the clean task which means gulp will ensure that the
// Clean task is completed before running the scripts task.
gulp.task('scripts', ['clean'], function () {

return gulp.src(config.src)
.pipe(uglify())
.pipe(concat('all.min.js'))
.pipe(gulp.dest('app/'));
});

//Set a default tasks
gulp.task('default', ['scripts'], function () { });

Now, run gulp from the command line and you should see some output stating that scripts task is completed successfully.

This will have created an all.min.js file that contains minified JavaScript from all the js files in the app folder.

Now we include all.min.js in the Visual Studio project and replace the 14 separate script includes with a single include to the new script.

<script src="app/all.min.js"></script>

Now, we can see that when our application loads, a single JS file is needed.

Watching for changes

Wouldn’t it be nice gulp could automatically re-run the scripts task whenever we make a change any of our js files? Sure, we can do that!

Add the following to our gulpfile.js:

gulp.task('watch', function(){
return gulp.watch(config.src, ['scripts']);
});

Now, if we run gulp watch from the command line, our new watch task will watch for changes to any of our js files. When a change is detected it will trigger the scripts task to execute, regenerating the all.min.js file.

You can learn more about gulp.watch here. Note that at this time, the built in gulp.watch has some bugs that stop it from watching new files. These should be fixed soon but in the mean time you might want to use use the popular gulp-watch plugin.

Integrating with Visual Studio

So far, we have been working primarily in the command line. It would be nice if we could integrate with our existing Visual Studio experience.

Luckily, we can with the new Task Runner Explorer plugin. Once the plugin is installed, open the Task Runner Explorer from the View –> Other Windows –> Task Runner Explorer menu.

This window will show you all the tasks in the gulp file and allow you to bind those tasks to certain Visual Studio events. This way, we don’t need to remember to run the gulp tasks from the command line. The IDE can handle it for us.

What I like to do is bind the watch task to the Solution Open event and bind the scripts task to the Before Build event.

With these bindings, we can make sure that all.min.js is correctly generated when we build the application and also regenerated anytime I make changes to the js files.

The Task Runner Explorer also shows the output of any running tasks. Here you can see the output from the watch task, which is always running in the background in this configuration.

Development vs Production

To simplify debugging, you may want to include all your individual scripts in a development build and only include the single concatenated/minified script in your production build. Take a look at my post on Web Optimization in ASP.NET Core MVC for some ideas on how to this can be accomplished. That post covers ASP.NET Core MVC but a similar approach could be used in MVC 5. With the following in your cshtml file, the individual files would be included when debug=”true” is set in your web.config. If debug=”false”, then only the single concatenated/minified file is included.

@if (HttpContext.Current.IsDebuggingEnabled)
{
<!-- Bootstrapping -->
<script src="app/app.js"></script>
<script src="app/config.js"></script>
<script src="app/config.exceptionHandler.js"></script>
<script src="app/config.route.js"></script>

<!-- common Modules -->
<script src="app/common/common.js"></script>
<script src="app/common/logger.js"></script>
<script src="app/common/spinner.js"></script>

<!-- common.bootstrap Modules -->
<script src="app/common/bootstrap/bootstrap.dialog.js"></script>

<!-- app -->
<script src="app/admin/admin.js"></script>
<script src="app/dashboard/dashboard.js"></script>
<script src="app/layout/shell.js"></script>
<script src="app/layout/sidebar.js"></script>

<!-- app Services -->
<script src="app/services/datacontext.js"></script>
<script src="app/services/directives.js"></script>
}
else
{
<script src="app/all.min.js"></script>
}

Note that this only works for cshtml files in an MVC application as it requires the Razor view engine.

Why?

So, now we’ve seen a very simple example of bundling and minification. As many have pointed out, this could have easily been accomplished with the runtime bundling in MVC5 provided by System.Web.Optimization. I have a few thoughts on this:

Runtime vs. Compile-Time Optimizations

System.Web.Optimization takes the approach of bundling/minifying your assets at runtime. The first time someone asks for a bundle, it will combine and minify all the files in that bundle and cache the results for the next request. While the cost of this is minimal, it has always seemed to me that it is a strange to use server resources to do this task. At the time of publishing our application to the server, we already know what the code is. To me it makes more sense to do this step on the build server or on the developer machine BEFORE publishing the code. Task runners like Gulp take the approach of doing these asset optimization steps at compile/build time.

Note that there are some specific use cases such as CMS tools that require runtime optimizations because the assets might not be known at compile time. For the vast majority of applications, I think the task runner approach is more logical.

Extensibility and Consistency

There is no question that the runtime bundling in MVC 5 provides a better ‘out-of-the-box’ experience. When you create a new project, bundling and minification is setup and working. It is easy to add new files. People generally understand the concepts and don’t need to spend a lot of time fiddling with the bundle configuration. Where System.Web.Optimization starts to fall apart for me is when I want to take things 1 step further.

What if I want to start using a CSS pre-processor like LESS or SASS? There is no way built-in way to tie CSS pre-processors into System.Web.Optimization. Now I need to start looking for VS plugins or extensions to System.Web.Optimization. If we’re lucky, these will work well. In my experience they have some problems, are often out-of-date or are just not available. One big problem with using VS plugins is that I can’t make use of those on the build server. Another problem is trying to make sure that everyone on the team has the right plugins installed.

With Gulp, all I need to do is include a gulp plugin (eg, gulp-less) and add the less compilation step to my stylesheet pipeline. It would be a 1 or 2 line change to my gulp file. The node package manager is able to ensure that everyone on the team has the right gulp plugins installed. Since everything is command line based, it is also very easy to call the same tasks from the build server if necessary.

So the big advantages are extensibility and consistency. System.Web.Optimization is very good at doing a couple things, but it is also limited to doing those couple of things. When we want to take things a little further, we start to run into some pain points with ensuring a consistent development environment. Gulp on the other hand is extremely flexible and extensible in a way that makes it easy to provide consistency for your team.

Wrapping it up

This post really only scratched the surface of what is possible with Gulp. There are 1,000’s of Gulp plugins available for pretty much everything you can imagine. This is not limited to JavaScript files. You could be processing stylesheets, images or audio.

My typical pipeline for scripts in a SPA is TypeScript Lint => SourceMaps => TypeScript Compile => Concat => Uglify. With the magic of a gulp-watch, this all happens anytime I make a change to my typescript file. The only script files I commit to source control are the TypeScript files. The compiled JavaScript and concatenated files do not need to be checked in because they can be generated at any time using Gulp. The build server is responsible for doing that before publishing the application to the server.

You might also be interested in a few follow up blog posts: