Revisioning asset files
Contents
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:
- clean
- assets
- usemin
- collect
- 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.