关于此 Codelab
1. Overview
Workbox is the successor to sw-precache
and sw-toolbox
. It is a collection of libraries and tools used for generating a service worker, and for precaching, routing, and runtime-caching. Workbox also includes modules for easily integrating background sync and Google Analytics into your service worker.
In this lab, you'll use the workbox-sw.js library and the workbox-cli
Node.js module to build an offline-capable Progressive Web App (PWA).
What you'll learn
- How to write a service worker using the workbox-sw.js library
- How to add routes to your service worker using workbox-sw.js
- How to use the predefined caching strategies provided in workbox-sw.js
- How to augment the workbox-sw.js caching strategies with custom logic
- How to generate a production-grade service worker with
workbox-cli
What you should already know
- Basic HTML, CSS, and JavaScript
- ES2015 promises
- How to run commands from the command line
- Some familiarity with service workers is recommended
- Familiarity with Node.js is recommended
What you will need
- Computer with terminal/shell access
- Connection to the internet
- A browser that supports service worker
- A text editor
- Node.js and npm
2. Get set up
This lab requires Node.js. Install the latest long-term support (LTS) version if you have not done so already.
Clone the starter code from GitHub using 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.
3. Install project dependencies and start the server
Navigate to the project directory via the command line:
cd workbox-lab/project/
Run the following commands to install the project dependencies:
npm install npm install --global workbox-cli
Build and serve the app with these commands:
npm run build npm run start
Explanation
The npm install
command installs the project dependencies based on the configuration in package.json. Open project/package.json and examine its contents.
workbox-cli
is a command-line tool that lets you configure, generate, and modify service worker files. You'll learn more about this in a later step.
workbox-cli
is installed globally (with the --global
flag) so that you can use it directly from the command line, which you'll do in a later step.
The remainder of package.json is used to configure the following:
- The
build
script uses thecopyfiles
package to copy the files from the src folder to the build folder. This is currently our app's "build" process. - The
start
script starts anexpress
server inside the build folder to serve our app.
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'll be building the lab.
This folder contains:
- src/images is a folder that contains sample images
- src/js/animation.js is a javascript file for page animations
- src/pages is a folder that contains sample pages
- src/style/main.css is the stylesheet for the app's pages
- src/sw.js is where you will write your source service worker using workbox-sw.js
- src/index.html is the home page HTML file
- package.json and package-lock.json track Node.js dependencies
- server.js is a simple express server
All of these files were copied over to the build folder when the npm run build
command was run, and the server (started with npm run start
) is serving these files from the build directory.
Observe that we currently have an empty service worker (build/sw.js) which was installed in the browser by the registration code in index.html. You can see the status of service workers in Chrome DevTools by clicking on Service Workers in the Application tab:
index.html
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log(`Service Worker registered! Scope: ${registration.scope}`);
})
.catch(err => {
console.log(`Service Worker registration failed: ${err}`);
});
});
}
</script>
4. Write a basic service worker using workbox-sw
Now that we have the starting app working, let's start writing the service worker.
In the empty source service worker file, src/sw.js, add the following snippet:
src/sw.js
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.5.0/workbox-sw.js');
if (workbox) {
console.log(`Yay! Workbox is loaded 🎉`);
workbox.precaching.precacheAndRoute([]);
} else {
console.log(`Boo! Workbox didn't load 😬`);
}
Save the file.
Explanation
In this code, the importScripts
call imports the workbox-sw.js library from a content delivery network (CDN). Once the library is loaded, the workbox
object gives our service worker access to all the Workbox modules.
The precacheAndRoute
method of the precaching
module takes a precache "manifest" (a list of file URLs with "revision hashes") to cache on service worker installation. It also sets up a cache-first strategy for the specified resources, serving them from the cache by default.
Currently, the array is empty, so no files will be cached.
Rather than adding files to the list manually, workbox-cli
can generate the manifest for you. Using a tool like workbox-cli
has multiple advantages:
- The tool can be integrated into your build process. Adding
workbox-cli
to your build process eliminates the need for manual updates to the precache manifest each time that you update the app's files. workbox-cli
automatically adds "revision hashes" to the files in the manifest entries. The revision hashes enable Workbox to intelligently track when files have been modified or are outdated, and automatically keep caches up to date with the latest file versions. Workbox can also remove cached files that are no longer in the manifest, keeping the amount of data stored on a user's device to a minimum. You'll see whatworkbox-cli
and the file revision hashes look like in the next section.
Learn more
While you are using workbox-cli in this lab, Workbox also supports tools like gulp with workbox-build and webpack with workbox-webpack-plugin.
5. Configure Workbox
The first step towards injecting a precache manifest into the service worker is configuring which files we want to precache. In this step, we create the workbox-cli
configuration file.
From the project directory, run the following command:
workbox wizard --injectManifest
Next, follow the command-line prompts as described below:
- 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, theroot
is the build/ directory, which should be suggested by the prompt. You can either type "build/" or choose "build/" from the list. - The second prompt asks what types of files to cache. For now, choose to cache CSS files only.
- 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. Type "src/sw.js" and press return.
- The fourth prompt asks for a path in which to write the production service worker. You can type "build/sw.js" and press return.
- The final prompt asks what we want to name our configuration file. Press return and use the default answer (workbox-config.js).
Once you've completed the prompts, you'll see a log with instructions for building the service worker. Ignore this for now (if you already tried it, that's okay). Rather than building your service worker directly, you will add it to the build process in the next step.
But first, examine the newly created workbox-config.js file.
Explanation
Workbox creates a configuration file (in this case workbox-config.js) that workbox-cli
uses to generate service workers. The config file specifies where to look for files (globDirectory
), which files to precache (globPatterns
), and the file names for our source and production service workers (swSrc
and swDest
, respectively). We can also modify this config file directly to change what files are precached. We explore modifying the config file in a later step.
6. Inject a manifest into the service worker
Now let's use the workbox-cli
tool to inject the precache manifest into the service worker.
Open package.json and update the build
script to run the Workbox injectManifest
command. The updated package.json should look like the following:
package.json
{
"name": "workbox-lab",
"version": "1.0.0",
"description": "a lab for learning workbox",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"copy": "copyfiles -u 1 src/**/**/* src/**/* src/* build",
"build": "npm run copy && workbox injectManifest workbox-config.js",
"start": "node server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.16.3"
},
"devDependencies": {
"copyfiles": "^1.2.0",
"workbox-cli": "^3.5.0"
}
}
Save the file and run npm run build
from the command line.
The precacheAndRoute
call in build/sw.js has been updated. In your text editor, open build/sw.js and observe that style/main.css is now included in the file manifest.
Return to the app in your browser (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 you can test your new service worker. 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 check that a new service worker was installed. 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.
Explanation
When workbox injectManifest
is called, Workbox makes a copy of the source service worker file (src/sw.js) and injects a manifest into it, creating your production service worker file (build/sw.js). Because you configured workbox-config.js to cache *.css
files, your production service worker has style/main.css in the manifest. As a result, style/main.css was pre-cached during the service worker installation.
Now whenever you update our app, you can simply run npm run build
to rebuild the app and update the service worker.
7. Reinject an updated manifest
Let's modify the Workbox config file to precache our entire home page. Replace the contents of workbox-config.js with the following code, and save the file:
workbox-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-config.js"
]
};
From the project directory, re-run npm run build
to update build/sw.js. The precache manifest in the production service worker (build/sw.js) has been updated to contain index.html, main.css, business.jpg, animation.js, and icon.svg.
Refresh the app and activate the updated service worker in the browser. 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).
Return to the command line window that is running your server (the one started with npm run start
) and turn off the server by pressing Ctrl+C
. Now our app is "offline". Refresh the page and observe that your home page still loads!
Explanation
By editing the globPatterns
files in workbox-config.js, you can easily update the manifest and precached files. Re-running the workbox injectManifest
command (via npm run build
) updates your production service worker with the new configuration.
In addition to precaching, the precacheAndRoute
method sets up an implicit cache-first handler. This is why the home page loaded while you were offline even though you had not written a fetch handler for those files!
8. Add routes to the service worker
workbox-sw.js has a routing
module 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 precacheAndRoute
call. Make sure you're not editing the production service worker, build/sw.js, as this file will be overwritten when we run workbox injectManifest
again.
src/sw.js
workbox.routing.registerRoute(
/(.*)articles(.*)\.(?:png|gif|jpg)/,
workbox.strategies.cacheFirst({
cacheName: 'images-cache',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 50,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
})
]
})
);
Save the file.
Restart the server and rebuild the app and service worker with the following commands:
npm run build
npm run start
Refresh the app and activate the updated service worker in the browser. Navigate to Article 1 and Article 2 . Check the caches to see that the images-cache
now exists and contains the images from Articles 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 routing
class. The first parameter in registerRoute
is a 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 route uses the strategies
class to access the cacheFirst
run-time caching strategy. Whenever your app requests article images, the service worker checks the cache first for the resource before going to the network.
The handler in this code also configures Workbox to maintain a maximum of 50 images in the cache (ensuring that users' devices don't get filled with excessive images). Once 50 images has been reached, Workbox will remove the oldest image automatically. The images are also set to expire after 30 days, signaling to the service worker that the network should be used for those images.
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 five 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.
9. Use a customized networkFirst cache strategy
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.
Add the following code below the previous route in src/sw.js:
src/sw.js
const articleHandler = workbox.strategies.networkFirst({
cacheName: 'articles-cache',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 50,
})
]
});
workbox.routing.registerRoute(/(.*)article(.*)\.html/, args => {
return articleHandler.handle(args);
});
Save the file and run npm run build
on the command line to rebuild the service worker. 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 revisit those articles. Instead of the browser's default offline page, you should see the cached article. Remember to re-run npm run start
to restart the server.
Optional: Test the networkFirst
strategy by changing the text of the cached article and reloading the page. Make sure to run npm run build
to update the build files. Even though the old article is cached, the new one is served and the cache is updated.
Explanation
Here you are using the networkFirst
strategy to handle a resource you expect to update frequently (such as trending news articles). This strategy updates the cache with the newest content each time it's fetched from the network.
In the above code, we use the handle
method on the built-in networkFirst
strategy. The handle
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. In the caching strategy, you could have passed directly to the second argument of registerRoute
as you did in the previous examples. Instead, you return a call to the handle
method in a custom handler function to gain access to the response, as you'll see in the next step.
Handle invalid responses
The handle
method returns a promise that resolves with a response, so you can access the response with a .then
.
Add the following .then
inside the article
route after the call to the handle
method:
src/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;
});
The updated route should look like the following:
src/sw.js
workbox.routing.registerRoute(/(.*)article(.*)\.html/, args => {
return articleHandler.handle(args).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-config.js file for precaching. The full file should look like the following:
workbox-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-config.js"
]
};
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. On the app home page, try clicking the Non-existent article link. This link 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, the service worker returns the custom offline page that was precached. If the response exists but the status is 404, then your custom 404 page is returned. Otherwise, you return the original response.
10. Optional: Add a customized cacheFirst cache strategy
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 add a final service worker route for the app's "post" pages (pages/post1.html, pages/post2.html, etc.).
The route should use Workbox's cacheFirst
strategy, creating a cache called "posts-cache" that stores a maximum of 50 entries.
If the cache or network returns a resource with a 404 status, then return the pages/404.html resource.
If the resource is not available in the cache, and the app is offline, then return the pages/offline.html resource.
Hints
- To test your changes, remember to restart the server with
npm run start
and rebuild the service worker withnpm run build
. - Review the previous section for how to add additional logic to Workbox's built-in strategy handlers.
- The Workbox
cacheFirst
handler handles no-connectivity differently thannetworkFirst
. Instead of passing an empty response to the.then
, it rejects and goes to the next.catch
.
11. Congratulations!
You have learned how to use Workbox to create production-ready service workers!
What we've covered
- Writing a service worker using the workbox-sw.js library
- Adding routes to your service worker using workbox-sw.js
- Using the predefined caching strategies provided in workbox-sw.js
- Augmenting the workbox-sw.js caching strategies with custom logic
- Generating a production-grade service worker with
workbox-cli
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
- Workboxjs.org
- Workbox - developers.google.com