Service Workers are a crucial technology that enable Progressive Web Applications (PWAs) on the mobile web. They provide features such as caching of resources and push notifications, which have traditionally been the differentiating factors between native apps and web apps.

In this tutorial, we will cover the following topics:

Introduction to Service Workers

Service Workers are the backbone of Progressive Web Applications, as they enable caching of resources and push notifications. They act as a programmable proxy between your web page and the network, allowing you to intercept and cache network requests. This gives you the ability to create an offline-first experience for your app.

Service Workers are a type of JavaScript file associated with a web page that runs on a separate worker context, separate from the main thread. This allows them to perform computations without blocking the UI responsiveness. However, since they run on a separate thread, they do not have DOM access or access to the Local Storage and XHR APIs. They can only communicate with the main thread using the Channel Messaging API.

Service Workers work in conjunction with other Web APIs such as the Fetch API and Cache API. It is important to note that they are only available on HTTPS protocol pages, except for local requests, which do not require a secure connection for easier testing.

Background Processing

Service Workers can continue to run even when the associated application is in the background, closed, or even if the browser is closed while the app is running. This makes them extremely useful for handling background tasks such as caching network requests and managing push notifications.

Offline Support

Service Workers are instrumental in providing offline support for web applications. Traditionally, the offline experience for web apps has been poor. Without a network connection, most web apps would not work at all, unlike native apps which generally offer a working version or some kind of user-friendly message.

Service Workers provide two types of caching to improve the offline experience:

Precaching assets during installation

Assets that are reused throughout the application, such as images, CSS, and JavaScript files, can be installed the first time the app is opened. This forms the basis of the App Shell architecture, which improves the performance and user experience of the app.

Caching network requests

Using the Fetch API, Service Workers can intercept network requests and determine if the server is not reachable. In such cases, the Service Worker can provide a response from the cache instead of making a network request. This ensures that the app can still function even when the network is unavailable.

A Service Worker Lifecycle

Service Workers go through three steps to be fully functional: registration, installation, and activation.

Registration

During registration, the browser is informed about the location of the Service Worker and starts the installation process in the background. The registration code is typically placed in a separate JavaScript file associated with the web page.

if ('serviceWorker' in navigator) {
 window.addEventListener('load', () => {
 navigator.serviceWorker.register('/worker.js')
 .then((registration) => {
 console.log('Service Worker registration completed with scope: ',
 registration.scope)
 }, (err) => {
 console.log('Service Worker registration failed', err)
 })
 })
} else {
 console.log('Service Workers not supported')
}

Registration only occurs if the Service Worker is new or has been updated. The register() method also accepts a scope parameter, which determines the part of the application that can be controlled by the Service Worker. By default, the Service Worker has control over all files and subfolders contained in the folder that contains the Service Worker file.

Installation

If the browser determines that a Service Worker is outdated or has never been registered before, it proceeds to install it. The install event is triggered during this stage, providing an opportunity to initialize and cache assets using the Cache API.

self.addEventListener('install', (event) => {
 // Initialization and caching
});

Activation

The activation stage occurs once the Service Worker has been successfully registered and installed. The Service Worker is now ready to work with new page loads. However, it cannot interact with pages that were loaded while the old Service Worker was active. This means the Service Worker is only useful when the user interacts with the app for the second time or reloads previously loaded pages.

self.addEventListener('activate', (event) => {
 // Cleanup
});

The activation event is typically used to clean up old caches and any other resources associated with the previous version of the Service Worker that are no longer needed.

Updating a Service Worker

Updating a Service Worker is as simple as changing a single byte in the Service Worker file. When the register code is executed, the browser detects the change and updates the Service Worker. However, the updated Service Worker does not become available until all pages that were loaded with the previous version are closed. This ensures that the app does not break during the update process. Refreshing the page is not enough to update the Service Worker, as the old worker is still running and needs to be removed.

Fetch Events

A fetch event is triggered when a resource is requested on the network. This event allows us to check the cache before making a network request. In the following example, the Cache API is used to check if the requested URL is already stored in the cache. If it is, the cached response is returned. Otherwise, the fetch event continues and makes the network request.

self.addEventListener('fetch', (event) => {
 event.respondWith(
 caches.match(event.request)
 .then((response) => {
 if (response) {
 return response
 }
 return fetch(event.request)
 }
 )
 )
})

The fetch event is powerful for providing efficient and offline-first experiences by utilizing caching strategies.

Background Sync

Background sync is a feature that allows outgoing connections to be deferred until the user has a working network connection. This is essential for offline functionality, as it ensures that the user can interact with the app and queue server-side updates until a network connection is available.

navigator.serviceWorker.ready.then((swRegistration) => {
 return swRegistration.sync.register('event1')
});

In the Service Worker, the sync event is listened for and the desired action is performed when the event is triggered.

self.addEventListener('sync', (event) => {
 if (event.tag == 'event1') {
 event.waitUntil(doSomething())
 }
})

If the action fails, another sync event will be scheduled to automatically retry until it succeeds. This allows apps to update data from the server as soon as a network connection is available.

Push Events

Push events enable web apps to provide native push notifications to users. This is achieved using the Push API and Notifications API. Push and Notifications are separate concepts and technologies, but they work together to provide a comprehensive push notification system for web apps. Since Service Workers run even when the app is not active, they can listen for push events and show notifications to the user or update the app’s state.

self.addEventListener('push', (event) => {
 console.log('Received a push event', event)

 const options = {
 title: 'I got a message for you!',
 body: 'Here is the body of the message',
 icon: '/img/icon-192x192.png',
 tag: 'tag-for-this-notification',
 }

 event.waitUntil(
 self.registration.showNotification(title, options)
 )
})

A note about console logs

If you have any console log statements in the Service Worker, make sure to enable the “Preserve log” feature in the Chrome DevTools or its equivalent. By default, the console is cleared before loading a page, which means that console logs from the Service Worker may not be visible. Enabling the “Preserve log” feature ensures that the logs are retained and can be viewed.

Tags: Service Workers, Progressive Web Applications, Caching, Push Notifications, Offline Support, Fetch API, Cache API, Background Sync, Push API, Notifications API