ServiceWorker

What is ServiceWorker?

  • A JavaScript worker that sits between your app and the network
  • Used for caching, push notifications, and background sync
  • Fully event driven, no persistent state

ServiceWorker is the single most important new browser feature that enables Progressive Web Applications. It is a JavaScript worker that sits between your app and the network acting as a proxy. It can handle caching and intercept network requests, so you can deliver a fast and reliable user experience regardless of the network.

Lifecycle

ServiceWorkers can react to incoming push messages even when a user is not on the page. ServiceWorkers can only run code in response to events.

Registering a ServiceWorker

A ServiceWorker is registered using the navigator.serviceWorker.register method. It's recommended to delay the registration until after the load event to keep the initial render fast. Before registering the ServiceWorker, ensure that the target browser supports it by checking that the navigator object has a serviceWorker property.

window.addEventListener('load', async () => {
if ('serviceWorker' in navigator) {
try {
await navigator.serviceWorker.register('./sw.js');
} catch (err) {
console.log('ServiceWorker registration failed', err);
}
}
});

By default, the scope of a ServiceWorker is determined by it's location. Serving it from the root of your application allows it to control all sub-paths, whereas having it in a sub-folder would limit it's scope to only that folder and its children. You can override this by passing {scope: '/path'} to the register method.

Once the ServiceWorker has been registered and downloaded, it will move into the install state.

Installing

The browser will always try to download the ServiceWorker file when a user visits the application. If there are any changes between the previous ServiceWorker and the newly loaded one, the newly loaded ServiceWorker receives an install event.

self.addEventListener('install', async installEvent => {});

The install state is typically used to cache static assets like HTML files, CSS, and JavaScript.

Changes to any static assets cached by the ServiceWorker will not trigger an install. To cache new versions of your changed files, you need to change the ServiceWorker file. If you are writing your own ServiceWorker, you might add a comment with the current date and time, for instance. A more production-ready solution is using a library like Workbox (discussed later) to create checksums of all the static files to automatically update the ServiceWorker and only downloading the files that have changed.

If the installation is completed successfully, the ServiceWorker will either move into a waiting or activate state. Pages that are loading a ServiceWorker for the first time will move straight to activate. The default behavior for pages that are already controlled by a previous version of a ServiceWorker will move to a waiting state until all tabs and windows for that page are closed, only then moving into activate. This is to avoid accidental disruptions to running pages. If you know what you are doing and want to activate the new ServiceWorker immediately, you can call self.skipWaiting() in the install listener.

Activating

Once a ServiceWorker is ready to take control of the application, it will move into the activate state. You can hook onto this by listening to the activate event.

self.addEventListener('activate', async activateEvent => {});

The activate state is commonly used to clear out old files from the cache.

If you want the newly activated ServiceWorker to start handling fetch event on all open clients (tabs/windows) you can call self.clients.claim() in the activate listener.

Intercepting network requests

The most important task of the ServieWorker is to ensure that a PWA works reliably regardless of network conditions. It does this by listening to fetch events, essentially intercepting outgoing network requests.

self.addEventListener('fetch', async activateEvent => {});

In the fetch listener, you can decide how each request should be handled. Static assets, for instance, can be returned directly from cache without a network request. Dynamic content can be fetched and cached, so it's available later on if the user is offline. Read more about different caching strategies.

Receiving Push messages

ServiceWorkers also enable PWA to receive push notifications from the server, even if the application is not open. To enable push notifications, you need to get user acceptence for receiving push notifications, and then listen for push events in the ServiceWorker.

self.addEventListener('push', async activateEvent => {});

Read more about Push notifications

Background sync

The last type of event that a ServiceWorker can respond to is the sync event. This event can be used for things like sending out messages that were queued up while offline.

self.addEventListener('sync', async activateEvent => {});

Read more about Background Sync

Production service workers

The ServiceWorker API is low-level by design. It offers library developers enough flexibility to provide almost any level of functionality on top of it. However, because the API is so low-level, it can be a lot of work to write a robust, production-ready Serviceworker.

In most cases, it makes more sense to use a higher level library like Workbox that offers automatic ServiceWorker generation and support for complex caching strategies including cache expiration and size limits.

The production PWA with Webpack and Workbox tutorial walks you through setting up a Webpack build system that generates a ServiceWorker using Workbox.