Workbox is the successor to sw-precache and sw-toolbox. It is a collection of libraries and tools used for generating a service worker, precaching, routing, and runtime-caching. Workbox also includes modules for easily integrating background sync and Google analytics into your service worker.

See the Workbox page on developers.google.com for an explanation of each module contained in Workbox. This lab uses the two core tools that Workbox provides, workbox-sw, and workbox-cli.

What you'll learn

What you should already know

What you will need

This lab requires Node.js. Install the latest long term support (LTS) version if you have not already.

Clone the starter code from GitHub with the following command:

git clone https://github.com/googlecodelabs/workbox-lab.git

Alternatively, you can click here to download the code as a zip file.

Navigate to the project directory via the command line:

cd workbox-lab/project/

Then run the following commands to install the project dependencies and start the Node.js server:

npm install
npm run start

Explanation

In this step, the project dependencies are installed based on the configuration in package.json. Open project/package.json and examine its contents. There are two packages we are concerned with in this lab:

workbox-cli is a command-line tool that allows us to inject a file manifest into a source service worker file. You'll learn about this in a later step.

workbox-sw is a high-level library that makes it easier to precache assets and configure routes with caching strategies in a service worker. While the library is installed at node_modules/workbox-sw/build/importScripts/workbox-sw.dev.vX.Y.Z.js, there is a copy for easier access at src/workbox-sw.dev.v2.0.0.js.

The start script uses the copyfiles package to copy the files from the src folder to the build folder and start the express server inside the build folder.

The build script is how we will use workbox-cli in this lab.

Open the app and explore the code

Once you have started the server, open the browser and navigate to http://localhost:8081/ to view the app. The app is a news site containing some "trending articles" and "archived posts". We will be performing different runtime caching strategies based on whether the request is for a trending article or archived post.

Open the workbox-lab/project folder in your text editor. The project folder is where you will be building the lab.

This folder contains:

All of these files were copied over to the build folder when the server was started in the previous step (the server serves from the build directory). In particular, observe that we currently have an empty service worker (build/sw.js) which was installed by the registration code in index.html:

index.html

<script>
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('sw.js')
      .then(function(registration) {
        console.log('Service Worker registration successful with scope: ',
        registration.scope);
      })
      .catch(function(err) {
        console.log('Service Worker registration failed: ', err);
      });
  }
</script>

You can see the status of service workers in developer tools.

Now, in the empty source service worker file, src/sw.js, add the following snippet:

src/sw.js

importScripts('workbox-sw.dev.v2.0.0.js');

const workboxSW = new WorkboxSW();
workboxSW.precache([]);

Save the file.

Explanation

This code imports the workbox-sw library that was installed in the previous step, and creates an instance of WorkboxSW so the service worker can access the library methods from this object.

The final line in sw.js calls workboxSW.precache([]). The precache method takes a precache "manifest" (a list of file URLs with revision hashes) to cache on service worker installation. Currently, the array is empty, so no files will be cached. Rather than adding files to the list manually, the next step shows how to use workbox-cli to generate the manifest. Populating the manifest array with a build tool like workbox-cli automatically adds file revision hashes to the manifest entries. The revision hashes enable Workbox to intelligently track when files have been modified, and automatically keep caches up to date with the latest file versions. For these reasons, using a build tool like workbox-cli is the recommended approach. You'll see what workbox-cli and the file revision hashes look like in the next section.

Workbox also removes cached files no longer in the manifest, and it even sets up a fetch handler to respond to any requests for precached files using a cache-first strategy. See this example for a full explanation.

Now let's use the workbox-cli tool to inject a manifest in the service worker. From the project directory, run the following command:

npm run build

This command aliases to workbox inject:manifest in the package.json file.

Next, follow the command-line prompts as described below:

  1. The first prompt asks for the root of the app. The root specifies the path where Workbox can find the files to cache. For this lab, the root is the build/ directory, which should be suggested by the prompt. You can either type "build/" or choose "build" from the list.
  2. The second prompt asks what types of files to cache. For now, choose to cache CSS files only.
  3. The third prompt asks for the path to your source service worker. This is the service worker file, src/sw.js, to which we added code in the previous step. You can type "src/sw.js" or press return.
  4. The fourth prompt asks for a path in which to write the production service worker. You can type "build/sw.js" or press return.
  5. The final prompt asks if you would like to save these settings to a config file. Type "y" to answer yes or press return.

Open your browser and navigate to http://localhost:8081/. Open your browser's developer tools (in Chrome use Ctrl+Shift+I on Windows, Cmd+Opt+I on Mac). Unregister the previous service worker and clear all service worker caches for localhost so that any existing cached resources won't interfere with rest of the lab. In Chrome DevTools, you can do this in one easy operation by going to the Application tab, clicking Clear Storage and then clicking the Clear site data button.

Refresh the page and checkthat a new service worker was created. You can see your service workers in Chrome DevTools by clicking on Service Workers in the Application tab. Check the cache and observe that main.css is stored. In Chrome DevTools, you can see your caches by clicking on Cache Storage in the Application tab. In your text editor, open build/sw.js and observe that style/main.css is included in the file manifest.

Explanation

When workbox inject:manifest is called, Workbox makes a copy of the source service worker file (src/sw.js) and injects a manifest into it, creating our production service worker file (build/sw.js). Because we chose CSS files as a resource type to cache, our production service worker now has style/main.css in the manifest, which was pre-cached during the service worker installation. The manifest also contains a revision hash of style/main.css, as described in the previous section. This hash is what allows Workbox to intelligently update the cached file if it changes.

Workbox also creates a config file, workbox-cli-config.js, that workbox-cli uses to update the service worker in the future. The config file specifies where to look for files (globDirectory), which files to precache (globPatterns), which files to ignore (globIgnores), and the file names for our source and production service workers, swSrc and swDest, respectively. This lets us conveniently reinject an updated file manifest if we modify files, without having to answer the prompts again. We can also modify this config file directly to change what files are precached. We can explore that in the next step.

Let's modify the Workbox config file to precache our entire home page, and add a templatedUrls property. Replace the contents of workbox-cli-config.js with the following code, and save the file:

workbox-cli-config.js

module.exports = {
  "globDirectory": "build/",
  "globPatterns": [
    "**/*.css",
    "index.html",
    "js/animation.js",
    "images/home/*.jpg",
    "images/icon/*.svg"
  ],
  "swSrc": "src/sw.js",
  "swDest": "build/sw.js",
  "globIgnores": [
    "../workbox-cli-config.js"
  ],
  "templatedUrls": {
    "/": ["index.html"]
  }
};

From the project directory, re-run npm run build to update build/sw.js. Observe that we didn't need to answer the prompts again and our production service worker (build/sw.js) has been updated to contain index.html, main.css, business.jpg, animation.js, icon.svg, and / in the manifest.

Activate the updated service worker in the browser and refresh the app. In Chrome DevTools, you can activate the new service worker by going to the Application tab, clicking Service Workers and then clicking skipWaiting. Observe in developer tools that the globPatterns files are now in the cache (you might need to refresh the cache to see the new additions).

Turn off the server by pressing Ctrl+C from the command line to go "offline". Then refresh the page. Observe that our home page still loads!

Explanation

By editing the globPatterns in workbox-cli-config.js, we can easily update the manifest and precache the files. The templatedUrls option tells Workbox that our site responds to requests for slash "/" with the contents of index.html, so we don't end up having to manage two separate precache entries. Re-running the workbox inject:manifest command (via npm run build) updated our production service worker with the new configuration.

In addition to precaching, the precache method sets up an implicit cache-first handler. This is why the home page loaded while we were offline even though we had not written a handler for those files!

workbox-sw has a router method that lets you easily add routes to your service worker.

Let's add a route to the service worker now. Copy the following code into src/sw.js beneath the existing code. Make sure you're not editing the production service worker, build/sw.js, as this file will be overwritten when we run workbox inject:manifest again.

src/sw.js

workboxSW.router.registerRoute(/(.*)articles(.*)\.(?:png|gif|jpg)/,
  workboxSW.strategies.cacheFirst({
    cacheName: 'images-cache',
    cacheExpiration: {
      maxEntries: 50
    },
    cacheableResponse: {statuses: [0, 200]}
  })
);

Save the file.

Restart the server and rebuild the service worker with the following commands (the commands must be run in order):

npm run start
npm run build

Activate the updated service worker in the browser and navigate to Article 1 and Article 2 . Check the caches to see that the images-cache now exists and contains the images from Article 1 and 2. You may need to refresh the caches in developer tools to see the contents.

Explanation

In this code we added a route to the service worker using the registerRoute method on the router class. registerRoute takes an Express-style or regular expression URL pattern to match requests against. The second parameter is the handler that provides a response if the route matches. In this case the routes uses the strategies class to access the cacheFirst run-time caching strategy. The built-in caching strategies have several configuration options for controlling how resources are cached.

The handler in this code configures Workbox to maintain a maximum of 50 images in the cache (ensuring that user's devices don't get filled with excessive images). Once 50 images has been reached, Workbox will remove the oldest image automatically. This handler also uses the cacheableResponse option to allow responses with a status of 0 (opaque responses). Without the cacheableResponse option, Workbox does not cache opaque responses when using the cacheFirst strategy. We might expect opaque responses if we fetched images from 3rd party APIs.

Don't worry if you aren't familiar with CORS and opaque responses, because they are only a small part of caching. You can learn more about CORS and making web requests in this fetch lab.

Optional: Write your own route that caches the user avatar. The route should match requests to /images/icon/* and handle the request/response using the staleWhileRevalidate strategy. Give the cache the name "icon-cache" and allow a maximum of 5 entries to be stored in the cache. This strategy is good for icons and user avatars that change frequently but the latest versions are not essential to the user experience. You'll need to remove the icon from the precache manifest so that the service worker uses your staleWhileRevalidate route instead of the implicit cache-first route established by the precache method.

Write the basic handler using the handle method

Sometimes content must always be kept up-to-date (e.g., news articles, stock figures, etc.). For this kind of data, the cacheFirst strategy is not the best solution. Instead, we can use the networkFirst strategy to fetch the newest content first, and only if that fails does the service worker get old content from the cache.

Copy the following code to the bottom of src/sw.js:

src/sw.js

const articleHandler = workboxSW.strategies.networkFirst({
  cacheName: 'articles-cache',
  cacheExpiration: {
    maxEntries: 50
  }
});

workboxSW.router.registerRoute('/pages/article*.html', args => {
  return articleHandler.handle(args);
});

Save the file and run npm run build in the command-line. Clear the caches and then update the service worker in the browser. In Chrome's developer tools, you can clear the cache and unregister the service worker simultaneously from the Application tab by going to Clear storage and clicking Clear site data. Refresh the home page and click a link to one of the Trending Articles. Check the caches to see that the articles-cache was created and contains the article you just clicked. You may need to refresh the caches to see the changes.

Optional: Test that articles are cached dynamically by visiting some while online. Then take the app offline again by pressing Ctrl+C in the command line and re-visit those articles. Instead of the offline page, you should see the cached article. Remember to re-run npm run start and then npm run build to restart the server and rebuild the service worker (the commands must be run in order).

Explanation

Here we are using the networkFirst strategy to handle a resource we expect to update frequently (trending news articles). This strategy updates the cache with the newest content each time it's fetched from the network.

Optional: Test the networkFirst strategy by changing the text of the cached article and reloading the page. Make sure to edit the file in the build folder so that you can see the changes in the browser. Even though the old article is cached, the new one is served and the cache is updated.

In the above code we use the handle method on the built-in networkFirst strategy. Each caching strategy in Workbox is an extension of the Handler constructor, which contains the handle method. This method takes the object passed to the handler function (in this case we called it args) and returns a promise that resolves with a response. We could have passed in the caching strategy directly to the second argument of registerRoute as we did in the previous examples, but we return a call to the handle method in a custom handler function instead to gain access to the response, as you'll see in the next step.

Handle invalid responses

The handle method returns a promise resolving with the response, so we can access the response with a .then.

Add the following .then inside the article route after the call to the handle method:

sw.js

.then(response => {
    if (!response) {
      return caches.match('pages/offline.html');
    } else if (response.status === 404) {
      return caches.match('pages/404.html');
    }
    return response;
  });

Then add pages/offline.html and pages/404.html to the workbox-cli-config.js file for precaching. The full file should look like this:

workbox-cli-config.js

module.exports = {
  "globDirectory": "build/",
  "globPatterns": [
    "**/*.css",
    "index.html",
    "js/animation.js",
    "images/home/*.jpg",
    "images/icon/*.svg",
    "pages/offline.html",
    "pages/404.html"
  ],
  "swSrc": "src/sw.js",
  "swDest": "build/sw.js",
  "globIgnores": [
    "../workbox-cli-config.js"
  ],
  "templatedUrls": {
    "/": ["index.html"]
  }
};

Save the files and run npm run build in the command line. Clear the caches and then activate the updated service worker in the browser. Try clicking the Non-existent article link, which points to an HTML page, pages/article-missing.html, that doesn't actually exist. You should see the custom 404 page that we precached!

Now try taking the app offline by pressing Ctrl+C in the command line and then click any of the links to the articles. You should see the custom offline page!

Explanation

The .then statement receives the response passed in from the handle method. If the response doesn't exist, then it means the user is offline and the response was not previously cached. Instead of letting the browser show a default offline page, it returns the custom offline page that was precached. If the response exists but the status is 404, then our custom 404 page is returned. Otherwise, we return the response.

So far you have implemented many caching strategies with Workbox, and in the previous section you learned how to add custom logic to the default Workbox caching options.

This last exercise is a challenge with less guidance (you can still see the solution code if you get stuck). You need to:

Hints

You have learned how to use Workbox to create production-ready service workers!

What we've covered

You can also use Workbox with build tools like gulp and webpack! To learn how to use the workbox-build library with gulp and webpack, check out this other workbox lab.

Resources