Progressive Web Apps: Working with Workbox

1. Welcome

In this lab, you'll take website with an existing service worker and convert it to using Workbox. This is the second in a series of companion codelabs for the Progressive Web App workshop. The previous codelab was Going Offline. There are six more codelabs in this series.

What you'll learn

  • Convert an existing Service Worker to use Workbox
  • Add an offline fallback to a PWA

What you should know

  • Basic HTML and JavaScript

What you will need

2. Get Set Up

Start by either cloning or downloading the starter code needed to complete this codelab:

If you clone the repo, make sure you're on the pwa03--workbox branch. The zip file contains the code for that branch, too.

This codebase requires Node.js 14 or higher. Once you have the code available, run npm ci from the command line in the code's folder in order to install all of the dependencies you'll need. Then, run npm start to start the development server for the codelab.

The source code's README.md file provides an explanation for all distributed files. In addition, the following are the key existing files you'll be working with throughout this codelab:

Key Files

  • service-worker.js - Application's service worker file
  • offline.html - Offline HTML to use when a page isn't available

3. Migrate to Workbox

Looking at the existing service worker, precaching looks like it can be broken down into two steps:

  • Cache relevant files during Service Worker install
  • Serve those files again with a Cache Only strategy

The index.html file and the / route both still make sense to precache, as this web app's HTML isn't going to change much, but the other files, like the CSS and JavaScript, may change and we don't really want to need to go through the whole Service Worker lifecycle every time they do. Additionally, the current service worker only takes into account a subset of our CSS and JavaScript, we want all of it covered. Caching these items with a Stale While Revalidate strategy makes more sense; quick response that can be updated in the background as needed.

Precaching revisited

Migrating to Workbox, we don't need to keep any of the existing code, so delete everything in service-worker.js. In the previous lab, we set up this Service Worker to be compiled, so we can use ESModule Imports here to bring in Workbox from its NPM modules. Let's start by revisiting precaching. In service-worker.js, add the following code:

import { warmStrategyCache } from 'workbox-recipes';
import { CacheFirst } from 'workbox-strategies';
import { registerRoute } from 'workbox-routing';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
import { ExpirationPlugin } from 'workbox-expiration';

// Set up page cache
const pageCache = new CacheFirst({
  cacheName: 'page-cache',
  plugins: [
    new CacheableResponsePlugin({
      statuses: [0, 200],
    }),
    new ExpirationPlugin({
      maxAgeSeconds: 30 * 24 * 60 * 60,
    }),
  ],
});

warmStrategyCache({
  urls: ['/index.html', '/'],
  strategy: pageCache,
});

registerRoute(({ request }) => request.mode === 'navigate', pageCache);

Explanation

To get precaching set up for /index.html and /, there are five modules to pull from. While that may seem like a lot, this code is much more powerful than the previous code written.

It starts by setting up a new Cache First caching strategy, chosen instead of a Cache Only strategy to allow for other pages to be added to the cache as needed. A name is given to it, page-cache. Workbox strategies can take a number of plugins that can affect the lifecycle of saving and retrieving content from the cache. Here, two plugins, the Cacheable Response plugin and the Expiration plugin, are used to ensure only good server responses are cached, and that each item in cache will get flushed after 30 days.

Next, the strategy's cache gets warmed with /index.html and / using the warm strategy cache Workbox recipe. This will add those items to this cache during the service worker's install event.

Finally, a new route is registered. Any request that's a page navigation will be managed by this Cache First strategy, either pulling from the cache or the network and then caching the response.

Caching assets

With route precaching sorted, it's time to re-implement caching for the site's assets; it's CSS and JavaScript. To do so, first add StaleWhileRevalidate to your workbox-strategies import, then add the following code to the bottom of your Service Worker:

// Set up asset cache
registerRoute(
  ({ request }) => ['style', 'script', 'worker'].includes(request.destination),
  new StaleWhileRevalidate({
    cacheName: 'asset-cache',
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200],
      }),
    ],
  }),
);

Explanation

This route starts by determining if the type of request is a style, a script, or a worker, corresponding to CSS, JavaScript, or Web Workers. If it is, it uses a Stale While Revalidate strategy, trying to serve from the cache first, falling back to the network if not available, while trying to update the version in cache from the network if possible. Like the page strategy, this strategy will only cache good responses.

4. Add offline fallback

With the original service worker migrated to Workbox, there's one more thing that needs to be done to prevent the PWA from crashing when offline; adding an offline fallback.

Offline fallbacks can be set for anything that may not be available when offline: pages, fonts, CSS, JavaScript, images, etc.... At a minimum, a page fallback should be set for all PWAs so if a user navigates to a page not in cache, they'll stay within your app's context.

Workbox recipes provides an offline fallback recipe that can be used to do just this! To use it, first add offlineFallback to your workbox-recipes import, then add the following code to the bottom of your Service Worker:

// Set up offline fallback
offlineFallback({
  pageFallback: '/offline.html',
});

Explanation

The offline fallback recipe sets up a Cache Only strategy that gets warmed with the provided fallbacks. It then sets up a Workbox default catch handler, catching any failed routing requests (if there's nothing in the cache and something can't be reached on the network), pulling the content of the relevant files from the cache and returning it as content as long as the request continues to fail.

5. Congratulations!

You've learned how to take use Workbox to set up caching strategies for routes and provide an offline fallbacks for your PWA.

The next codelab in the series is IndexedDB