Last week I discussed how to setup Node.js and Gulp in Visual Studio 2015. During that discussion, I mentioned that I’m using gulp to bundle, minify and cache-bust my HTML, CSS, and JavaScript files.
This week, my intent is to walk you through exactly how I do that.
So, if you don’t already have Node.js and Gulp installed, you may want to go back and read the article I wrote last week.
Since most of the people who read this blog are ASP.NET developers, there may be a few .NET specific tips along the way. But the Gulp file I am going to walk you through is technology agnostic. So if you are using some other technology, you’ll still benefit from this article.
Bundling
The first thing we want to do is that we want to combine all of our CSS files and JavaScript files into one file for CSS and one file for JavaScript. There are several ways that you might do this, but what I wanted was some way that would allow me to work unbundled during development and bundled when I released the code. Since I’m working with a single page application (SPA), this was a simple matter of configuring my default file to be index.debug.html for development and index.release.html and using web.config transforms to specify which should be used in which environment. If you are working in some other environment, I’m sure you have some kind of similar way of specifying the default file based on an environment setting.
So all of my development work will be done in index.debug.html.
Like I said, there are many ways of bundling JavaScript and CSS code, but the way I’ve found that seems to have the least amount of work involved is by using the gulp-useref module. This module lets us put tokens in our html file that specify which files we want to compress and what the resulting file name should be.
Your HTML file would look something like this:
1 | <html> |
useref sees the build token and creates a css file named combined.css and a JavaScript file named combined.js and changes the output html so that it looks like this:
1 | <html> |
To use this feature in your Gulp file, install it using NPM
1 | npm install gulp-useref --save-dev |
Make sure that you run NPM in the directory your gulpfile.js file is in.
In your gulp file, you’ll add the following code:
1 | var gulp = require('gulp'); |
The end result is that there will be a new index.debug.html file in a sub directory named index along with the new css/combined.css and scripts/combined.js file.
Conditional Processing
One of the many things I like about Gulp is that it is stream based. That is, I don’t have to write files to a directory unless or until I want to. Unlike Grunt (the other popular file processing Node.js based tool) where everything is entirely file based.
However, because I have three different types of files coming out of the previous process, and I want to compress each of the files, I’ll need some way to conditionally process the files coming out of useref.
For this, we need gulp-if.
You can install gulp-if using NPM using the following command:
1 | npm install gulp-if --save-dev |
The basic using of gulp-if looks like this:
1 | var gulpif = require('gulp-if'); |
Minify JavaScript
The next thing we want to do is that we want to minify the resulting combined JavaScript file. There are several that you could use. The one I settled on is gulp-uglify. Install gulp-uglify using
1 | npm install gulp-uglify --save-dev |
So, combining this with gulp-if, the usage would look like this:
1 | var gulpif = require('gulp-if'); |
This is just the general gist. We’ll put all of this together in a few more paragraphs.
Minify CSS
To minify CSS, I decided to use gulp-cssnano Install gulp-cssnano using:
1 | npm install gulp-cssnano --save-dev |
And use it in our code like this:
1 | var cssnano = require('gulp-cssnano'); |
Map Files
Once we have all of our files minified, we’ll want some way of being able to see the original source code even though you have a minified file that the site is using. I’m not going to go into a lot of detail about what a map file is or what it does here. But if you have a problem on a production server, you are going to want to at least have map files available so you can track the issue down. To create a map file, you’ll want to use gulp-sourcemaps. Install using
1 | npm install gulp-sourcemaps --save-dev |
I’ll show you how this all plugs in soon.
One More Package
Yes, believe it or not, there is one more package we need yet to make this all work. You see, to get the minify stuff to work with useref we need to install the lazypipe module. Which you can install using:
1 | npm install lazypipe --save-dev |
Putting it All Together
And so now, finally, we can put this all together in one big script.
1 | var gulp = require('gulp'); |
Compressing The HTML file
So, as the comments say and as I’ve mentioned before, this places everything in the index directory. What we want to do next is to compress the HTML file and move the index.debug.html back up to the root directory with the name index.release.html and place the css file and the javascript file in the appropriate directories hanging off the root. To compress the HTML file, you can use the NPM module gulp-htmlmin, which you can install using
1 | npm install gulp-htmlmin --save-dev |
And the code
1 | var htmlmin = require('gulp-htmlmin'); |
Rename the Index File
For renaming the file, we use gulp-rename
1 | npm install gulp-rename --save-dev |
And the following code:
1 | var gulpRename = require('gulp-rename'); |
This renames the file and puts it in the root directory.
Moving Files
Next we need to get the files that are in our index directory back out to the directories they belong in. To do this, we use the built-in node command gulp.dest().
1 | gulp.task('copyJs', ['useRef'], function () { |
Cache-busting
One of the age-old problems of using CSS and JavaScript pages on our site is that when we put new versions up, we have no way of telling the browser that we just put a new file up unless we change the file name. The trick is to make the file look like a new file. This is typically done by putting a query parameter at the end.
Once again, there are several solutions to this problem available for Gulp, but the one I like the best reads the file and generates a hash string for it and appends that as the query string. This make the file look unique but only causes the browser to download the file if it really is different.
To implement cache-busting, you’ll want to install gulp-cache-bust
1 | npm install gulp-cache-bust --save-dev |
And the final bit of code to make all of this work:
1 | var cacheBuster = require('gulp-cache-bust'); |
Enhancements
If you wanted to go to the trouble, you could create this as one great big script that never actually put the files in the index directory. But I have found having the files written out to the intermediate directory to be valuable for debugging purposes.
The Final Code
You can get the complete Gulp script from GitHub.