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.

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.

Refresh the page and check your browser's developer tools to see that a new service worker was created. Check the cache and observe that main.css is stored. Open build/sw.js and observe that style/main.css is included in the file manifest.

Explanation

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. 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).

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. Turn off the server by pressing crtl + C from the command line to go "offline". Then refresh the page. Observe that our home page still loads! Remember to start the server up again by running npm run start from the project directory (you'll also need to re-run npm run build, since npm run start overwrites the service worker in build).

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

Let's add aroute 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. Run npm run build again to update the production service worker with the new routes. 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 exist 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 aroute 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 news 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 and re-visit those articles. Instead of the offline page, you should see the cached article. Remember to re-run npm run build if you restarted the server with npm run start.

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. 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.

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 and clicking any of the links to the articles. You should see the custom offline page! Remember to re-run npm run build after you restart the server with npm run start.

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