This codelab will show you how to use sw-precache and sw-toolbox, two progressive web app libraries built by google to make service worker implementation faster and easier. There are multiple ways of using both libraries. The libraries can be used together or separately. In this codelab you'll write a gulp task to create a service worker that takes advantage of both libraries.

You won't learn every last detail about sw-precache and sw-toolbox. It should give you a good start to understanding caching using our libraries.

Is this a progressive web app?

This codelab uses a small progressive web app called Redder, a Reddit client for finding and reading subreddits related to JavaScript. It shows Reddit posts in the app itself and opens in a new window Reddit links to other sites.

But is it a progressive web app? Yes. Just to clarify, let's go through the requirements.

Progressive - Redder's critical code works without a service worker or any of the other technologies that make something a progressive web app. Note, however that the starting version of this app contains a service worker because this is a caching codelab, not a service worker codelab. You could remove the service worker completely and it would still work.

Responsive - It uses Material Design Light, which provides visual elements optimized for cross-device use.

Connectivity independent - It uses a service worker to work offline or on low quality networks.

App-like - The application is built using the app shell model. It also implements a web app manifest so that users can launch it from an Android home screen and interact with it as though it were a native application.

Fresh - It's always up-to-date thanks to its service worker and background syncing.

Safe - The app only works on domains using HTTPS or on localhost.

Discoverable - Redder is discoverable as an "application" thanks to W3C manifests and service worker registration scope allowing search engines to find it.

Re-engageable - It makes re-engagement simple by being launchable from a user's home screen (though we won't be exploring that in this codelab).

Installable - Did we mention that users can add it to their home screen?

Linkable - It has a button that lets users share it with their friends.

What you'll need

Next up

Let's set up a project that needs caching capabilities.

Get the code

Download Zip

Set up your work area

Extract the contents of the downloaded zip file. It will create a folder called caching-with-libraries/. Open a console window and create a work folder as follows:

$ cd caching-with-libraries
$ mkdir work
$ cp -r step-02/* work
$ cd work

Install and run the web server

To test our work, we're going to need a web server. In production, we'd need one that supports HTTPS. For development and testing, anything running on localhost will do.

If you need something quick you can install the Chrome Web Server from the Chrome Web Store. You may want to install this anyway, since other codelabs use it.

After installing Chrome Web Server, launch it from the Chrome App Launcher using this icon.

Click Choose Folder and select work/app/. While you're in the Web Server, also select "Automatically show index.html".

Open a new browser tab and navigate to localhost:8887. Click one of the subreddit names in the navigation panel. You'll get a list of recent topics on that subreddit.

Install the libraries

Let's kick off the install process for sw-precache (which gives you sw-toolbox for free).

$ cd work
$ npm install --save-dev sw-precache

Next up

Since load time varies, this is a good time to pause and discuss what we're going to build. If sw-precache isn't already done installing, it should be by the time you finish reading the next section.

We have several questions to answer before we can build anything.

Let's consider all the things we could cache.

Some items we'll want to cache immediately and not update very often. Others we'll want to cache based on a user action or symbol. This distinction brings us to the first thing we need to know about caching.

Types of caching

Broadly, there are two types of caching

Precaching—We'll precache items that the app will always need immediately and will update with the application version. This is the primary job of sw-precache.

Runtime caching—This is how we cache everything else. The sw-toolbox library provides five ways to do this: network first, cache first, fastest, cache only, and network only. If you've ever read Jake Archibald's The Offline Cookbook you'll be familiar with these.

The Offline Cookbook describes five types of caching, all supported by sw-toolbox. The types with asterisks are used in Redder.

Next up

It's time to write some code.

You can use sw-precache with gulp, grunt, or from a command line. We're going to use gulp.

In the root of caching-with-libraries/ you'll find a file called empty-gulpfile.js. Copy this file to your work/ directory, rename it gulpfile.js, then open it in your text editor. It looks like this:

'use strict';

var gulp = require('gulp');
var path = require('path');

// Gulp commands go here.

First, import the sw-precache library using the require() method.

var swPrecache = require('sw-precache');

Next, add an empty gulp task.

gulp.task('make-service-worker', function(callback) {

});

You may have noticed that your work/ directory contains a folder called app/, which contains the actual files in our web app. We've done this so that we can keep our development files (such as the gulp file) and our application files separate. Let's note this in a variable. We'll use this variable later.

gulp.task('make-service-worker', function(callback) {
  var rootDir = 'app';

});

Call swPrecache.write()

The sw-precache library has a function called write(), which tells it to create a service worker at a location we define. Add it to your task.

gulp.task('make-service-worker', function(callback) {
  var rootDir = 'app';

  swPrecache
    .write(path.join(rootDir, 'serviceworker.js'), {}, callback);

});

The write()method takes three arguments.

Most of the remaining code work in this codelab will be to add to the options object.

At this point you could actually run the gulp task.

$ gulp make-service-worker

The generated service worker won't do much because we didn't configure it. This will at least tell you if you've done everything right to this point.

Next up

To make our libraries do something, we need to add properties to the options argument in swPrecache.write().

Let's get down to business. We need to make the service worker do something. To do that we need to add items to the options object.

Tell sw-precache what to cache

First, we're going to precache Redder's app shell. Use staticFileGlobs in the options argument, which is simply an array of strings. It could look something like this:

{staticFileGlobs: [rootDir + '/index.html',
                   rootDir + 'css/styles.css',
                   rootDir + 'images/dog.png'
                  ...], // contents excerpted
}

We don't want to list every file individually. We're likely to miss a file especially when we update the application, and especially in larger more complex applications. Fortunately, staticFileGlobs takes node globs. You can read about them in the node glob readme. The short version is they're strings that describe files using command line file patterns. Add a staticFileGlobs property to the options object.

{staticFileGlobs: [rootDir + '/**/*.{html,css,png,jpg,gif}',
                        rootDir + '/js/*.js']
}

This will pick up everything in our app shell, and the resulting service worker will put it in the user's browser cache. If we built a service worker with what we've got, our application serving directory would be in the path for all the files we're trying to precache. (The staticFileGlobs property is about telling sw-precache where to look for files, not about telling the resulting service worker where to serve them from.) Let's tell sw-precache to remove the application directory from the path of any files it finds.

Our final precaching instructions look like this:

{staticFileGlobs: [rootDir + '/**/*.{html,css,png,jpg,gif}',
                        rootDir + '/js/*.js'],
 stripPrefix: rootDir
}

Why do JavaScript files get their own line?

If we put js in the list of extensions on the first line, precaching would pick up the service worker and any files it imports. We do not want that. Service workers are designed to be cached by default and we don't want our caching to prevent a service worker from updating.

Since the service worker is in rootDir, we can avoid this by explicitly bypassing rootDir when looking for all other JavaScript files.

Build the service worker

In the root of work/ and run gulp.

$ gulp make-service-worker

Your work/app/ directory now contains a file called serviceworker.js.

Verify precaching

If you haven't done so already, open Chrome Web Server, make sure the selected folder is work/app/, and make sure it's running. Now open a browser tab to localhost:8887.

We'll use DevTools to verify precaching. Open DevTools by right-clicking anywhere on the Redder app and selecting Inspect. At the top of the window select the "Resources" panel.

In the left-hand navigation open "Cache Storage". You should see several items whose names begin with "sw-precache". Clicking any of those items will show the cache's content in the right hand pane.

If your Resources panel doesn't look like the one shown above, either double-check your work or copy the contents of step-03/ into your work/ folder and carry on.

Next up

We'll show you how to implement runtime caching.

In this section, we're going to cover the basics of implementing runtime caching by implementing one caching handler and take a short detour to set up background syncing to preload the runtime caches.

Runtime caching is configured by adding an array of caching strategy objects to the options object. At a minimum, each caching strategy object requires a urlPattern and a handler, though some caching strategies require more. Generally, the whole property looks something like this:

runtimeCaching: [
{
        urlPattern: /some regex/,
        handler: 'cachingStrategy'
},
{
        urlPattern: /some regex/,
        handler: 'cachingStrategy'
}
// Repeat as needed.
],

The urlPattern property is a regular expression used to match file requests to a caching strategy. The handler property is the name of a caching strategy for the specified regular expression.

Caching post titles

If you peek at the version of gulpfile.js in final/ you'll see that it implements three different caching strategies one for each of three different kinds of content. We're not going to implement all three in this code lab. We're going to focus on one, the caching of post titles.

Since titles change frequently, we're going to use a network-first caching strategy. Add a runtimeCaching property to your swPrecache.write() call, immediately below the stripPrefix option:

runtimeCaching: [
{
        urlPattern: ,
        handler: 'networkFirst'
}],

Next, tell the service worker which URLs should be retrieved from the cache by using the urlPattern property. Subreddit post titles are returned by URLs that have the following pattern.

http://www.reddit.com/r/subredit_name.json

A regular expression that matches it looks like this:

https:\/\/www\.reddit\.com\/r\/\w{1,255}\.json

The result looks like this:

runtimeCaching: [
{
        urlPattern: /https:\/\/www\.reddit\.com\/r\/\w{1,255}\.json/,
        handler: 'networkFirst'
}],

Using the right cache

You may recall that we're caching subreddits, titles, and articles and we're using three different caching strategies to do it. We're going to give our caches names to identify them. To the subreddits caching pattern add an options property, with a cache property named 'titles'.

runtimeCaching: [
{
        urlPattern: /https:\/\/www\.reddit\.com\/r\/\w{1,255}\.json/,
                handler: 'networkFirst',
                options: {
                        cache: {
                                name: 'titles'
                        }
                }
}],

Run gulp again and reload localhost in your browser.

$ gulp make-service-worker

If you get any console errors, double-check your work or compare it to step-03/gulpfile.js before proceeding.

Background sync the runtime caches

Redder has a bonus trick. It uses background synchronization to prefill the runtime caches. It does this for subreddits, titles, and articles. Exactly how it works and when it's triggered isn't important to this codelab. What is important is that it uses caches with particular names and they need to match what's in gulpfile.js. As with runtime caching, background syncing doesn't work with links to non-reddit articles.

Copy the sync.js file from caching-with-libraries/ to work/app/. Add the importScripts property to the write() function. Make it import a service worker file called sync.js, located in your app/ directory.

importScripts: ['sync.js']

Regenerate the gulp file and hard reload the app.

Next up

We're going to take a detour into DevTools and play with some features you can use to debug caching.

Let's look at how to debug what we've added so far.

Turn on debugging

The sw-toolbox library has a debugging option. Turning this on will make sw-precache print status messages to the DevTools console.

toolbox.options.debug = true;

If we added this to the service worker manually, we'd need to re-add it every time we regenerate the service worker. To get around this we've put the debug statement in a file called config.js that we need to add to the the importScripts property.

importScripts: ['config.js','sync.js']

Copy the config.js file from caching-with-libraries/ to work/app/. Regenerate the service worker file. In the browser window, hold down the refresh button so that the menu displays. Select Empty Cache and Hard Reload.

Open the Console

Since we've turned on debugging, we need to see the messages it writes to console.log(). Open the console by pressing Esc.

There may be messages in the console already. Ignore them for now.

In the browser window, hold down the refresh button so that the menu displays. Select Empty Cache and Hard Reload. The console output looks something like the image below. (The order of console messages may vary.)

Notice that we have messages from both Redder and from sw-toolbox. Chrome DevTools also logs calls to fetch(). Notice the line that says [sw-toolbox] preCache List: (none). This isn't a mistake. The sw-toolbox library, which can be used independently of sw-precache, has it's own precaching capability. Since we're not using that feature, we get this message despite the files that are precached by sw-precache.

Simulating offline and low latency conditions

Select Empty Cache and Hard Reload a second time. Select different subreddits from the application drawer. In each case notice what's written to the console. You should see something like this:

First, sw-toolbox tells you which strategy it will use to load the requested URL, then the actual fetch appears.

In the Console pane, switch to the Network conditions tab and click Network throttling. Notice that there are a number of presets to simulate different types of networks and a feature for customizing network conditions. In the list of presets, select Offline.

Go back to the list of subreddits and select one that you've already visited. In addition to the sw-toolbox message you've already seen, a network error for the same URL. The sw-toolbox library responds to this by falling back to the cache.

Despite the errors in the console, the Fetch complete statement still appears. The is because the fetch, which was triggered by the app shell, was fulfilled by the service worker via the cache. From the app shell's point of view, fetch still works.

Add a navigation fallback

Finally, select a subreddit you have not visited. Notice that you get network errors, and sw-toolbox still tries to retrieve something. In the browser you have the thing we've been trying to avoid: the browser's offline message.

The point of progressive web apps is so that our website works off line and in poor network conditions. A good progressive web app uses precaching and background syncing and other such capabilities to provide something to the user regardless of network conditions.

We're not omnipotent. Eventually, the user's going to request a resource that can't be retrieved from either the network or the caches. For that we want to create a fallback page to show when a requested resource isn't available.

Add the following to the options object in the swPrecache.write() method.

navigateFallback: 'message.html'

Note that this property only sets the runtime behavior. For this to work, message.html must already be in the precache.

We haven't even scratched the surface of the progressive libraries or caching strategies. We hope you've learned enough to explore these topics on your own. The documentation for sw-precache and sw-toolbox describe many other features. The final/ directory has the completed version of the gulpfile.js for Redder, which implements the cache first and the cache only strategies. Finally feel free to serve it from final/app/, fiddle with it, break it, do whatever you want with it. Fork it from GitHub if you have something more complicated in mind.

Finally, if you'd like to know more about caching patterns, be sure to check out Jake Archibald's The Offline Cookbook.