tooling.report

feature

Service worker

Can asset URLs be injected into Service Workers?

Person in shorts with blue hair walking left

Introduction

Service Workers are a tool for enhancing web pages with long-lived functionality like offline support and push notifications. Although a service worker is a type of JavaScript dependency, it generally needs to contain information about the build output, like a list of URLs to pass to cache.addAll. This information is necessary for "pre-caching" resources that haven't been used yet so that they're available offline in the future.

The Test

This test bundles an application consisting of one JavaScript module and a stylesheet that references an image. The page registers sw.js as its service worker, and each build tool is configured to detect and process this script. The hashed URLs for all assets and the HTML page itself are exposed to sw.js, though the mechanism for this varies between tools.

index.js

import './styles.css';

navigator.serviceWorker.register('./sw.js');

styles.css

body {
  background: url('./image.png');
}

image.png

<binary data>

index.html

<!DOCTYPE html>

sw.js

const assets = ['..']; // obtained differently in each tool
const version = 'ab57fcd2'; // obtained differently in each tool

async function install() {
  const cache = await caches.open(version);
  await cache.addAll(assets);
}
addEventListener('install', e => e.waitUntil(install()));

async function activate() {
  const keys = await caches.keys();
  await Promise.all(keys.map(key => key !== version && caches.delete(key)));
}
addEventListener('activate', e => e.waitUntil(activate()));

The build result should include a bundled version of index.js, the processed stylesheet, and the image asset - all with hashed URLs. The HTML file and processed service worker script should also be output, without being hashed.

To pass this test, the service worker needs to be aware of the final URL of index.js, styles.css, image.png, and index.html.

The service worker should also have access to a variable or identifier that can be used to create a cache name. This identifier should be generated based on the content of all cached files.

Conclusion

browserify

Gulp allows build steps to happen in series if necessary. In this case, all files except the service worker are processed first, including using gulp-rev-all to hash assets and output a rev-manifest.json. Then the service worker is processed, and content from the rev-manifest.json, and calculated hash version, can be injected into the script.

parcel

Parcel handles ServiceWorkers correctly, but doesn’t seem to give you access to the list of assets.

Issues

rollup

Rollup's generateBundle hook gives you full access to build details before files are written. You can use this to populate your service worker.

webpack

Webpack does not automatically handle Service Worker, but it does expose the necessary asset metadata during compilation for plugins to be able to embed hashed assets URLs. While it's not possible to use Webpack's own DefinePlugin to embed this metadata into modules, plugins can "embed" asset information by prepending variable declarations to generated scripts.

This example uses a custom Webpack plugin that bundles a Service Worker using a Child Compiler, inlining global constants that contain a version hash and list of URLs for the Service Worker to cache.