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.