Setting up an SPA web app build pipeline with Gulp

  Javascript, Tutorial

Revisioning source files

All the files we talked about so far are going into the browser where they will be interpreted and rendered for your display. Originally, these files are served by an internet server like Tomcat, JBoss AS, WebSphere and alike. On their way from being served to being rendered by the browser, some kind of caching will certainly happen somewhere. Most prominently, the browser caches files to quite some extend. This is a welcomed feature because it prevents downloading files which are already held in the browser’s cache. This behaviour can lead to quite some problems for developers, though. If new revisions of these files are put on the server, they not necessarily replace the cached versions of older ones in customer’s browsers. Often, the browser still thinks he has got the most current version and will refrain from downloading it again. As a result, customers may see outdated content although newer has already been released.

In order to reliably prevent a browser cache from holding on to outdated files, developers employ techniques that are called cache busters. Their only purpose is to let the browser think that there is a new file which he has never downloaded before. The only way to do that is to rename newly released files to one with a unique name. Some developers use a counter in the filename so that each revision increases the counters value. For example, the next revision of the CSS file style003.css will be named style004.css. Unfortunately, all files which referred to style003.css now must be modified in order to reflect the change.

This is where our next build step comes into play.

But first we need to do some housekeeping, because until now, the dist folder had been created once and the files in there were overwritten each time the task was run. In this chapter, this will change. If we want to revision our files, filenames are going to change each time their content change. As a result, older files will start accumulating in the dist folder. To get rid of them, we need the gulp-clean plugin. Depending on how we coded the corresponding Gulp task, gulp-clean will delete files and folders for us. A perfect match for our dist folder. Remember, the dist folder holds the distributables of our web app. When we build our app using the gulp-usemin plugin, the dist folder and all files within are created from scratch. So deleting the whole dist folder before running the usemin task will do the trick. What’s special now is that we create a new clean task but also define clean as a dependency in the usemin task.

Again, the first step is installing the plugin:

c:> npm install gulp-clean --save-dev

We know that package.json has  automatically been updated, so there is no need to show it here again. We add the clean plugin module to Gulpfile.js and create a new separate clean task:

var gulp = require("gulp");
var clean = require("gulp-clean");
var uglify = require("gulp-uglify");
var usemin = require('gulp-usemin');
var minifyHtml = require('gulp-minify-html');
var minifyCss = require('gulp-minify-css');

gulp.task('clean', function (cb) {
   return gulp.src('dist', {read: false})
     .pipe(clean());
});

gulp.task('usemin', ['clean'], function () {
  return gulp.src('src/index.html')
   .pipe(usemin({ 
         css: [minifyCss()],
         html: [minifyHtml({empty: true})],
         js: [uglify()] }))
   .pipe(gulp.dest('dist/'));
});

In the above code, you’ll detect a small but important change in the usemin task. It now contains a new argument ['clean'].This argument makes sure that the comma separated tasks specified within the brackets are run prior of the usemin task. This makes sure that usemin doesn’t start writing new files to dist before dist had been wiped from the filesystem.It is important to understand that Gulp will create separate asynchronous tasks whereever possible. You can’t be sure that a task is run before another one has completed. This behaviour can lead to build issues tath are really difficult to pinpoint. For now, we mitigated the problem asynchronous tasks can incurr to our project, but for sure they are waiting for you.

Next level of complication of the revisioning project will be the gulp-rev plugin. Rev() simply creates a hash value from a file’s content and appends it to it’s filename. So, for example, the previous file style-27b03e9819.css might become style-232a3f445a.css caused perhaps by a change of a single character in the file. Caches are getting busted by this, be sure of that.

For our next example, we need to install the gulp-rev plugin:

c:> npm install gulp-rev --save-dev

Now scan through the example Gulpfile.js below and find out where the gulp-rev plugin is run:

var gulp = require("gulp");
var clean = require("gulp-clean");
var uglify = require("gulp-uglify");
var usemin = require('gulp-usemin');
var minifyHtml = require('gulp-minify-html');
var minifyCss = require('gulp-minify-css');
var rev = require('gulp-rev');

gulp.task('clean', function () {
   return gulp.src('dist', {read: false})
     .pipe(clean());
});

gulp.task('usemin', ['clean'], function () {
  return gulp.src('src/index.html')
   .pipe(usemin({ 
         css: [minifyCss(), rev()],
         html: [minifyHtml({empty: true})],
         js: [uglify(), rev() ]}))
   .pipe(gulp.dest('dist/'));
});

When running the usemin task, the following happens:

  • The clean task is run first because it was specified as a dependency of usemin
  • The clean task deletes the dist folder
  • usemin will run the css, html and js steps, sending the css and javascript files through the rev plugin
  • If the dist folder doesn’t exist, it will be created
  • All resulting files will be stored in dist and all original files are left unchanged

This technique obviously won’t work for index.html, because it is not a good idea to revision the main entry file into our web app. Image the index.html would be named differently each time it is deployed. How would customers of our web app know about the new name. They certainly created a browser favorite for one version of index.html and that favorite would be outdated as soon as a new revision gets online. As the main entrance into our web app, the filename “index.htm” must not change. If the content of index.html changes for whatever reason, we will fall into the cache trap, so we need to do something about it.

One way to solve the dilemma would be to create an index.html file which certainly will never be modified again after its first deployment. This index.html will forward to the “real” index.html file whose name can freely change because we have set up usemin to take care of references to revisioned filenames.

This is a good exercise for the readers od this article. In the next chapter, we’ll solve the riddle.

LEAVE A COMMENT