Add offline support to any Web app

Recently, I participated to the AngularAttack 2016 Hackathon, and built “Let Me See”: an application that helps people with sight loss see the world. This application was built using Ionic2 (and Angular2 of course). But most of all, I wanted the application to be progressive, so I added support for offline caching — for a better instant loading. Let me show you how I simply and quickly added that…

Oh! By the way, “Let Me See” has won the Innovation prize!

Before we start, I am going to assume you already have an existing web application. I am going to use “Let Me See” as an example, but the steps we are going to walk through together will apply to any modern Single Page Application.

Let us start. Here is the structure of our application…

As you can see, it is a simple Ionic2 application, with some extra configuration files for Firebase, since the application is hosted on Firebase. But let us talk about that in another blog post.

What you should notice is the presence of the sw-precache-config.json file. This file is used by the Service Worker Precache (sw-precache) tool.

Tl;Dr: The Service Worker Precache allows us to precache all necessary resources needed by the application shell. It also generates the server worker file for us, automagically!

Here is how the sw-precache tool works…

First, you have to decide whether you want to use this tool globally as a command line interface (CLI):

$ npm install --global sw-precache

And use it like so:

$ sw-precache --sw-file='www/sw.js' \
--static-file-globs='www/**/*.html'

Or locally with your build system:

$ npm install --save-dev sw-precache

And use it with your own build system (Gulp, Webpack…).

Then, we need to provide a configuration file: sw-precache-config.json. Here is how it looks for our “Let Me See” application:

{
"swFile": "www/sw.js",
"staticFileGlobs": [
"www/manifest.json",
"www/**/*.css",
"www/**/*.{ttf,woff,woff2,eof}",
"www/**/*.js",
"www/**/*.html",
"www/**/*.{png,jpg,gif,svg,mp3}"
],
"handleFetch": true,
"stripPrefix": "www/",
"cacheId": "let-me-see-v1",
"maximumFileSizeToCacheInBytes": 4194304,
"ignoreUrlParametersMatching": "[/./]",
"verbose": true
}

Let me quickly explain some of these options:

  • swFile: The path of the service worker file that will be generated (this option is not documented—see this file);
  • staticFileGlobs: An array of one or more string patterns. Those are the files that are going to be cached and used offline;
  • handleFetch: Determines whether the fetch event handler is included in the generated service worker code;
  • cacheId: A string used to distinguish the caches;
  • maximumFileSizeToCacheInBytes: Sets the maximum allowed size for a file in the precache list (you’re welcome);
  • stripPrefix: Removes a specified string from the beginning of path URL’s at runtime.

You can read a more detailed documentation about those options in the github repository.

Now that we have the sw-precache tool installed and ready. We need to get it work with our application. I choose to use sw-precache as a Gulp task because Ionic2 uses it as a build system. It even provides hooks where you can add your custom build tasks.

So, let us create our Gulp sw task:

gulp.task('sw', function(callback) {
var path = require('path');
var swPrecache = require('sw-precache');
var rootDir = 'www';
var options = require('./sw-precache-config.json');
options.ignoreUrlParametersMatching = [/./];
swPrecache.write(path.join(rootDir, 'sw.js'), options, callback);
});

Then, we call our task after the build task so the sw-precache tool can generate the Server Worker for us when the build has completed:

//...
gulp.task('build:after', ['sw']);
//...

Optionally, we can also run the sw task during the watch task. This means that the service worker will be auto generated each time you edit a file in your application:

gulp.task('watch', ['clean'], function(done){
runSequence(
['sass', 'html', 'fonts', 'scripts'],
'sw',
function(){ //...

I also highly recommend you to turn off the handleFetch options, which basically turns off the fetch event. Otherwise, the content would always be served from the service worker cache; and your live reload setup might not work as expected.

And that is it. You are done!

You now just added offline support for your Web application. You can see the full auto generated service worker for “Let Me See” here on github.

Last but not least, do not forget to register this service worker in your index.html:

<script>
if('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js');
}
</script>

Follow @manekinekko to know more about the Web Platform.