Setting up an SPA web app build pipeline with Gulp

  Javascript, Tutorial

Revisioning asset files

Not ultimately scared yet? Then let’s put something on top of it. We talked about the gulp-rev plugin’s capability to create a mapping file that are used by gulp-collect. Another ‘manifest’ file is the foundation of the HTML5 Application Cache feature of modern browsers.

The HTML5 app cache allows browsers to prefetch some or all of a website assets such as HTML files, images, CSS, JavaScript, and so on, while the client is online. It is not necessary for these files to have been accessed before in order to populate the cache. HTML5 application cache prefetches pages that have not been visited at all and are thereby unavailable in the regular browser’s network cache. Prefetching files can speed up the site’s performance, though you might download files that are not needed during the user’s session.

If configured properly, the HTML5 app cache can allow a website to run offline on the client device, because its files are available in the app cache. The ultimate advantage for this feature is that you can use the web app to some extend without even being connected to the internet.

This is what Wikipedia writes about “Cache Manifest in HTML5”:

Web applications consist of web pages that need to be downloaded from a network. For this to happen there must be a network connection. However, there are many instances when users cannot connect to a network due to circumstances beyond their control. HTML5 provides the ability to access the web application even without a network connection by using the cache manifest.

Web applications consist of resources identified by URLs. These can be HTML, CSS, JavaScript, images or any other source that is required for a web application to be rendered. Their addresses can be copied into a manifest file, which can be updated regularly by the author of the web application, indicating any new web addresses that are added or deleted. When connecting to a network for the first time, a web browser will read the HTML5 manifest file, download the resources given and store them locally. Then, in the absence of a network connection, the web browser will shift to the local copies instead and render the web application offline.

In order for the offline applications to work, a cache manifest file must be created by the web developer. If the web application exceeds more than one page then each page must have a manifest attribute that points to the cache manifest. Every page referencing the manifest will be stored locally.[3] The cache manifest file is a text file located in another part of the server. It must be served with the following content type:[4]

text/cache-manifest

The following line must be added to the html element in order for the cache manifest file to work.[4]

<!DOCTYPE HTML>
<html manifest="cache.appcache">
  <body></body>
</html>

The argument to the manifest attribute is a relative or absolute path to the manifest file.

Ok, these are quite a few prerequisites. Getting the mechanism up and running exceeds the scope of this article. We limit this chapter to how we can automate the writing of the manifest file.

To the rescue, there is gulp-appcache. Depending onhow we designed the corresponding gulp task , this plugin collects the specified files and lists them in the newly created manifest file.

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

Our package.json ought to look like this by now:

{
  "name": "buildpipeline",
  "version": "1.0.0",
  "description": "",
  "main": "Gulpfile.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "Gulp"
  ],
  "author": "sgoemans",
  "license": "ISC",
  "devDependencies": {
    "gulp": "^3.9.1",
    "gulp-minify-html": "^1.0.6",
    "gulp-usemin": "^0.3.23",
    "gulp-minify-css": "^1.2.4",
    "gulp-uglify": "^2.0.0",
    "gulp-clean": "^0.3.2",
    "gulp-rev": "^7.1.0",
    "gulp-rev-collector": "^1.0.5",
    "gulp-appcache": "0.0.4"
  }
}

By now it should be a routine job to create a new task in Gulpfile.js:

// Generate HTML5 Cache Manifest file
gulp.task('manifest', function () {
    return gulp.src(['dist/**/*'], {base: './'})
        .pipe(manifest({
            hash: true,
            preferOnline: true,
            network: ['*'],
            filename: 'app.manifest',
            exclude: ['app.manifest', 'rev-manifest.json']
        }))
        .pipe(gulp.dest('dist'));
});

We can now link the manifest task into our build pipeline:

  1. clean
  2. assets
  3. usemin
  4. collect
  5. manifest << our new entry point into the build pipeline

This is our final Gulpfile.js:

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');
var collect = require('gulp-rev-collector');
var manifest = require('gulp-appcache');

// Generate HTML5 Cache Manifest file
gulp.task('manifest', ['collect'], function () {
    return gulp.src(['dist/**/*'])
        .pipe(manifest({
            hash: true,
            preferOnline: true,
            network: ['*'],
            filename: 'app.manifest',
            exclude: ['app.manifest', 'rev-manifest.json']
        }))
        .pipe(gulp.dest('dist'));
});

gulp.task('collect', ['usemin'], function () {
    return gulp.src(['dist/rev-manifest.json', 'dist/**/*.js', 'dist/**/*.css', 'dist/**/*.html'])
        .pipe(collect())
        .pipe(gulp.dest('dist/'));
});

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

gulp.task('assets',['clean'],  function () {
    return gulp.src(['src/assets/**/*'])
        .pipe(rev())
        .pipe(gulp.dest('dist/assets'))
        .pipe(rev.manifest('./dist/rev-manifest.json', {
            merge: true,
            base: './dist'
        })) // merge with the existing manifest (if one exists)
        .pipe(gulp.dest('./dist'));
});

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

We can now trigger the complete build pipeline by entering

c:> gulp manifest

After its finished, there will be a new file called app.manifest in the dist folder.

CACHE MANIFEST

CACHE:
index.html
assets/pill_green_sm-4b8546f4db.png
assets/pill_grey_sm-6c162f4b40.png
assets/pill_red_sm-27795e8821.png
assets/pill_yellow_sm-93662c81c0.png
js/lib-a62877a33e.js
css/style-27b03e9819.css

NETWORK:
*

SETTINGS:
prefer-online

# hash: c790d5d7d7567730573f93f75743857b3cb3dd24bc9385c3ac2e31a341ac9fc4

Its content reflects the settings we specified in the manifest task. The sections in app.manifest have the following meaning:

  • CACHE MANIFEST
  • CACHE: – Files listed under this header will be cached after they are downloaded for the first time. This is the default section for entries. Files listed under this header (or immediately after the CACHE MANIFEST) will be explicitly cached after they’re downloaded for the first time.
  • NETWORK: – Most sites use “*”. An asterisk can be used to indicate that all other resources/files require an internet connection. The asterisk also indicates that the browser should allow all connections to non-cached resources from a cached page.
  • FALLBACK: – Files listed under this header specifies offline fallback pages if a page is inaccessible over the network.

Once files are in the cache, they won’t be updated as long as the manifest files hasn’t changed. Developers must change the manifest file if they want to make sure that updated code files are downloaded to the app cache again. A simple version number in a comment suffices. BUT, If the files listed in CACHE are in the network cache, they won’t actually download, since the network cache will spit back the same file to the application cache. Therefore, it’s recommended to add a hash to the filenames, just like we did with gulp-rev, in order to bust the network cache.

 

LEAVE A COMMENT