Workbox - Generate a service worker with ease

Back in July 2016 I wrote a blog post about how to implement a service worker. At the time the technology was still fairly new, with only Firefox, Chrome, Opera and Samsung Internet having enabled the API in their browsers. It’s amazing what a difference 18 months can make in the Frontend world. WebKit announced in January that service workers would be supported in Safari Technology Preview 48. Closely followed by Microsoft announcing Edge 17 will also support them. The future looks bright for service workers so we’d better get used to creating them! So the question you may be asking is how do I create one?

The old way (copy / paste / hope)

The old way seemed very much a case of read the documentation, copy a script, modify for your own needs then hope you didn’t break anything when you deploy it to your users. There’s nothing wrong with this method and it is a great way to learn a new technology (I do it all the time!). But this isn’t a good strategy to use when it comes to service workers.

With a service worker, if you aren’t careful you could inadvertently break your website completely for your users. If this were to happen they would have no idea why or even how to fix it. Imagine a service worker being installed on a users machine that is broken in some way. Say for example all requests are swallowed by the service worker once it is activated. When the user revisits your site they won’t see a homepage. In fact they would most likely only get a standard browser error page, but only for your website. This is bad! But what is worse is that since the user can no longer get to your website, they can also no longer receive service worker updates to fix the issue! Now you may be thinking ‘they can just clear the cache’. Afraid not, even if they clear the browser cache the service worker will still persist. You’ve inadvertently created a zombie service worker on the users machine that they can only kill if they know how to use the DevTools or update the device.

Alexander Pope spoke about this topic in his 25 minute presentation at JSConf EU 2017:

Scary stuff huh! So what’s the solution? Well you could be very careful and test your code multiple times on multiple machines before releasing to production… but you are already doing that anyway aren’t you? So that may not help you in this instance. It simply isn’t possible to test all possible permutations across all devices. Another more viable solution to consider is simply abstract the problem away. This is where Workbox comes to the rescue.

The new way (Workbox)

Workbox is a collection of libraries that you can use to generate your service workers in a safe and well tested manner. If you are looking to add offline functionality or improve loading performance for repeat users, there really isn’t a simpler way to do it. It will even make deployment much easier because you can even integrate Workbox into your development workflow using npm (workbox-cli), Gulp or Webpack.

I recently removed my own implementation of a service worker for this blog and replaced it with one generated by Workbox. Now every time I run gulp to generate the static website (I use Jekyll), Workbox examines the resulting HTML and assets looking for byte-level differences from the previous versions. If differences are found it will generate a new service worker file (sw.js) with the relevant files revision hash updated. The update cycle will then be triggered on the clients machine and the service worker will cache the modified assets. You are now no longer required to manually manage your cached assets and service worker script!

Example setup

To setup Workbox with Gulp it is fairly straightforward. The examples below are what I have used for this site.

Config

This is the config used by Workbox to define what is cached and where the final service worker file (sw.js) is generated. Notice how there is a Stale While Revalidate request strategy used with the Google Font. This means the resource is requested from both the cache and the network in parallel, then the service worker immediately responds with the cached version. If there is an updated version of the resource it is swapped out in the background, with no impact on a users browsing experience. This strategy is great for non-critical page assets like favicons and avatars. It also displays another powerful feature of Workbox. You can easily define different request strategies for different assets in the config and it will handle the implementation for you.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// workbox-cli-config.js
module.exports = {
    globDirectory: '_site',
    globPatterns: [
        'images/app-shell/*'
    ],
    swDest: '_site/sw.js',
    clientsClaim: true,
    skipWaiting: true,
    runtimeCaching: [
        {
            urlPattern: new RegExp('https://fonts.googleapis.com/css?family=Dosis|Open+Sans'),
            handler: 'staleWhileRevalidate',
        }
    ]
};

Gulp

For reference I have my gulp tasks split into separate files for easy maintenance. There really isn’t much going on in this file. We require Workbox, point it to the config defined above and watch for success / errors.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// generate-service-worker.js
const gulp = require('gulp');
const workbox = require('workbox-build');

function generateServiceWorker() {
    return workbox.generateSW(
        require('../workbox-cli-config')
    ).then(() => {
        console.info('Service worker generation completed.');
    }).catch((error) => {
        console.warn('Service worker generation failed: ' + error);
    });
}

gulp.task('generate-service-worker', generateServiceWorker);

I’ve had to create an additional gulp task to copy the service worker files from the _site directory (where the static site is generated by gulp), back into the project root. This task is required because GitHub pages doesn’t generate a _site directory when deploying the site. Once the file is moved and committed into the project root it will then be deployed by GitHub pages like any other file.

1
2
3
4
5
6
7
8
9
// copy-sw-code-to-root.js
const gulp = require('gulp');

function copySwCodeToRoot() {
    return gulp.src(['_site/sw.js', '_site/workbox*'])
        .pipe(gulp.dest('./'));
}

gulp.task('copy-sw-code-to-root', copySwCodeToRoot);

Output

What comes out the end is clean and simple auto-generated sw.js file that you can register in the usual manner. Note the importScripts() at the top of the generated file. This is loading a production version of Workbox.js. This is the file that does all the heavy lifting for us so we don’t need to get involved in all the details of the service worker setup. These implementation details have been abstracted away into well tested external script that we don’t need to worry about. Very useful!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// sw.js
importScripts('workbox-sw.prod.v2.1.2.js');

/**
 * DO NOT EDIT THE FILE MANIFEST ENTRY
 *
 * The method precache() does the following:
 * 1. Cache URLs in the manifest to a local cache.
 * 2. When a network request is made for any of these URLs the response
 *    will ALWAYS comes from the cache, NEVER the network.
 * 3. When the service worker changes ONLY assets with a revision change are
 *    updated, old cache entries are left as is.
 *
 * By changing the file manifest manually, your users may end up not receiving
 * new versions of files because the revision hasn't changed.
 *
 * Please use workbox-build or some other tool / approach to generate the file
 * manifest which accounts for changes to local files and update the revision
 * accordingly.
 */
const fileManifest = [
  {
    "url": "images/app-shell/bubble.svg",
    "revision": "cd8f3e69287067840832444472f3aece"
  },
  //...
];

const workboxSW = new self.WorkboxSW({
  "skipWaiting": true,
  "clientsClaim": true
});
workboxSW.precache(fileManifest);
workboxSW.router.registerRoute(/https:\/\/fonts.googleapis.com\/css?family=Dosis|Open+Sans/, workboxSW.strategies.staleWhileRevalidate({}), 'GET');

Conclusion

I’ve only scratched the surface of what Workbox can do. Take a look at the documentation and tutorials available for more information about the full feature set of Workbox. Version 3.0.0 beta 0 was released very recently, with a whole new set of features. It is certainly a project to keep watch or star on GitHub.

Progressive Web Apps and service workers are here to stay. You only need to take a look in your Chrome DevTools under “Applications > Service Workers” to see how many websites you browse have already installed a service worker on your machine. If treated with care and implemented correctly they can significantly boost your site performance and offer your users some really cool features. But if the implementation goes wrong… well you may never see those users again! Using Workbox to generate the service worker for your Progressive Web App should be high on your priority list.