Workbox Notes - Continued
I need to learn some more about workbox when actually implementing the service worker functionality. I will take notes during the process and keep the notes here.
These notes are a continuation of a daily reading article on workbox. When I returned from doing some other stuff to implement service worker functionality, I discovered that I still need to learn some more about it to implement the functionality effectively. So, I am returning to do that now. These notes will mainly cover workbox modules that I use, which can be found here.
Service Worker Packages
workbox-background-sync
The new BackgroundSync API is a solution to the problem of requests failing and trying to send the requests again later. When a service worker detects that a network request has failed, it can register to receive a sync
event, which gets delivered when the browser thinks connectivity has returned. The sync event can be delivered even if the user has left the application.
import {BackgroundSyncPlugin} from 'workbox-background-sync';
import {registerRoute} from 'workbox-routing';
import {NetworkOnly} from 'workbox-strategies';
const bgSyncPlugin = new BackgroundSyncPlugin('myQueueName', {
maxRetentionTime: 24 * 60, // Retry for max of 24 Hours (specified in minutes)
});
registerRoute(
/\/api\/.*\/*.json/,
new NetworkOnly({
plugins: [bgSyncPlugin],
}),
'POST'
);
The easiest way to use Background Sync is to use the Plugin
that will automatically Queue up failed requests and retry them when future sync events are fired.
The BackgroundSyncPlugin
hooks into the fetchDidFail
plugin callback, and fetchDidFail
is only invoked if there' an exception thrown, most likely due to a network failure. This means that requests won't be retried if there's a response received with a 4xx
or 5xx
error status. If you would like to retry all requests that result in, e.g., 5xx
status, you can do so by adding a fetchDidSucceed
plugin to your strategy:
const statusPlugin = {
fetchDidSucceed: ({response}) => {
if (response.status >= 500) {
// Throwing anything here will trigger fetchDidFail.
throw new Error('Server error.');
}
// If it's not 5xx, use the response as-is.
return response;
},
};
// Add statusPlugin to the plugins array in your strategy.
Advanced usage
Workbox Background Sync provides a Queue
class, which you can instantiate and add failed requests to. The failed requests are stored in IndexedDB and are retrieved when the browser thinks connectivity is restored.
import {Queue} from 'workbox-background-sync';
const queue = new Queue('myQueueName');
self.addEventListener('fetch', event => {
// Add in your own criteria here to return early if this
// isn't a request that should use background sync.
if (event.request.method !== 'POST') {
return;
}
const bgSyncLogic = async () => {
try {
const response = await fetch(event.request.clone());
return response;
} catch (error) {
await queue.pushRequest({request: event.request});
return error;
}
};
event.respondWith(bgSyncLogic());
});
Once you've created your Queue instance, you can add failed requests to it. You add failed request by invoking the .pushRequest()
method. The code above catches any requests that fail and adds them to the queue.
Once added to the queue, the request is automatically retries when the service worker received the sync
event (which happens when the browser thinks connectivity is restored). Browsers that don't support the BackgroundSync API will retry the queue every time the service worker is started up. This requires the page controlling the service worker to be running, or it won't be quite as effective.
workbox-broadcast-update
The workbox-broadcast-update
package provides a standard way of notifying Window Clients that a cached response has been updated. This is most commonly used along with the StaleWhileRevalidate
strategy. Whenever the revalidate
step of that strategy retrieves a response from the network that differs from what was previously cached, this module will send a message (via postMessage()
) to all Window Clients within scope of the current service worker. Window Clients can listen for updates and take appropriate action, like automatically displaying a message to the user letting them know that updates are available.
Updates are determined by looking at the Content-Length
, ETag
, and Last-Modified
headers of the response.
This library is intended to be used along with the StaleWhileRevalidate
caching strategy, since that strategy involves returning a cached response immediately, but also provides a mechanism for updating the cache asynchronously.
Broadcast Updates
import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
import {BroadcastUpdatePlugin} from 'workbox-broadcast-update';
registerRoute(
({url}) => url.pathname.startsWith('/api/'),
new StaleWhileRevalidate({
plugins: [new BroadcastUpdatePlugin()],
})
);
Listen for Broadcast Update Events
import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
import {BroadcastUpdatePlugin} from 'workbox-broadcast-update';
registerRoute(
({url}) => url.pathname.startsWith('/api/'),
new StaleWhileRevalidate({
plugins: [new BroadcastUpdatePlugin()],
})
);
Broadcast Update Message Format:
{
"type": "CACHE_UPDATED",
"meta": "workbox-broadcast-update",
// The two payload values vary depending on the actual update:
"payload": {
"cacheName": "the-cache-name",
"updatedURL": "https://example.com/"
}
}
workbox-cacheable-response
When caching assets at runtime, there's no one-size-fits-all rule for whether a given response is valid
and eligible for being saved and reused. The workbox-cacheable-response
module provides a standard way of determining whether a response should be cached based on its own numeric status code, the presence of a header with a specific value, or a combination of both.
You can configure the Workbox strategy to consider a set of status codes as being eligible for caching by adding a CacheableResponsePlugin
instance to a strategy's plugins
parameter:
import {registerRoute} from 'workbox-routing';
import {CacheFirst} from 'workbox-strategies';
import {CacheableResponsePlugin} from 'workbox-cacheable-response';
// This configuration tells workbox that when processing responses for requests against
// https://third-party.example.com/images/, cache any requests with a status code of 0
// or 200
registerRoute(
({url}) =>
url.origin === 'https://third-party.example.com' &&
url.pathname.startsWith('/images/'),
new CacheFirst({
cacheName: 'image-cache',
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
}),
],
})
);
// When processing for request URLs containing /path/to/api/, take a look
// at the header named X-Is_Cacheable (which would be added to the response by the server).
registerRoute(
({url}) => url.pathname.startsWith('/path/to/api/'),
new StaleWhileRevalidate({
cacheName: 'api-cache',
plugins: [
new CacheableResponsePlugin({
headers: {
'X-Is-Cacheable': 'true',
},
}),
],
})
);
If you use one of Workbox's built-in strategies without explicitly configuring a cacheableResponse.CacheableResponsePlugin
, the following default criteria is used to determine whether a response received from the network should be cached:
staleWhileRevalidate
andnetworkFirst
: Responses with a status of0
(i.e. opaque responses) or200
are considered cacheablecacheFirst
: Responses with a status of200
are considered cacheable.
An opaque-redirect filtered response is a filtered response whose type ifopaqueredirect, status is 0, status message is the empty byte sequence, header list is<< >>
, body is null, and body info is a new response body info.
workbox-core
Workbox has been built to be modular, allowing developers to select the pieces they want to use without forcing them to download everything in a single file. There is, however, overlap between modules. To avoid each module implementing the same logic, workbox-core
contains this common code which each module relies on. workbox-core
offers internal logic to each module, rather than the end developer.
View and Change the Default Cache Names
Workbox defines it's caches via cacheNames
:
import {cacheNames} from 'workbox-core';
console.log(cacheNames.precache);
console.log(cacheNames.runtime);
console.log(cacheNames.googleAnalytics);
These cach3 names are constructed in the format of a prefix, a name and a suffix, where the name changes based on the use of the cache. <prefix>-<cache-id>-<suffix>
You can change these default names by altering all or some of the values passed to setCacheNameDetails()
.
import {cacheNames, setCacheNameDetails} from 'workbox-core';
setCacheNameDetails({
prefix: 'my-app',
suffix: 'v1',
precache: 'install-time',
runtime: 'run-time',
googleAnalytics: 'ga',
});
// Will print 'my-app-install-time-v1'
console.log(cacheNames.precache);
// Will print 'my-app-run-time-v1'
console.log(cacheNames.runtime);
// Will print 'my-app-ga-v1'
console.log(cacheNames.googleAnalytics);
The main use for the prefix and suffix is that if you use Workbox for multiple projects and use the same localhost port for each project, setting a custom prefix for each module will prevent the caches from conflicting with each other.
workbox-expiration
Workbox provides the ability to put restrictions on a cache in terms of how long it should allow items to be stored in a cache or how many items should be kept in a cache. Workbox provides this functionality through the workbox-expiration
plugin that allows you to limit the number of entries in a cache and / or remove entries that have been cached for a long period of time. You can restrict the number of items in cache and how long they are there for.
workbox-google-analytics
If you're building an application that works offline, then understanding how users are interacting with your app when they don't have connectivity is crucial to optimizing that experience. Analytics providers like Google Analytics require a network connection to send data to their servers, which means if connectivity is unavailable, those requests will fail and those interactions will be missing from your analytics reports. It'll be like they never happened. Workbox Google Analytics solves this problem for Google Analytics users by leveraging Service Worker's ability to detect failed requests.
workbox-navigation-preload
workbox-navigation-preload
will handle checking at runtime to see if the current browser supports navigation preload, and if it does, it will automatically create an activate
event handler to enable it. The shared code inside of orkbox-core
that handles making requests across all of Workbox has also been updated to automatically take advantage of a preload response, if it's available. This means that any of the built-in strategies can automatically take advantage of navigation preload, once it's enabled. Developers who are already handling navigations by responding with precached HTML (potentially configured with an App Shell fallback) do not need to enable navigation preload! This feature is intended to reduce navigation latency for developers who can't precache their HTML, but still want to use Workbox to handle caching of ither assets on their sites.
workbox-precaching
One feature of service workers that is the ability to save a set of files to the cache when the service worker is installing. This is often referred to as precaching
, since you are caching content ahead of the service worker being used.
The main reason for doing this is that it gives developers control over the cache, meaning they can determine when and how long a file is cached as well as serve it to the browser without going to the network, meaning it can be used to cache web apps that work offline.
When a web app is loaded for the first time, workbox-precaching
will look at all the assets you want to download, remove any duplicates and hook up the relevant service worker events to download and store the assets. URLs that don't include versioning information have an extra URL query parameter appended to their cache key representing a hash of their content that Workbox generates at build time.
workbox-precaching
does all of this during the service worker's install
event.
Calling precacheAndRoute()
and addRoute()
will create a route that matches requests for the precached URLs.
workbox-precaching
expects an array of objects with a url
and revision
property. This array is sometimes referred to as a precache manifest:
import {precacheAndRoute} from 'workbox-precaching';
precacheAndRoute([
{url: '/index.html', revision: '383676'}, // by passing a revision property to precacheAndRoute, Workbox can know when the file has changed and update it accordingly
{url: '/styles/app.0c9a31.css', revision: null}, // revision property set to null because revision iformation is in the URL iteslf - this is best practice for static assets
{url: '/scripts/app.0d5770.js', revision: null},
// ... other entries ...
]);
Workbox comes with tools to help with generating this list:
workbox-build
: This is a node package that can be used in a gulp task or as an npm run scriptworkbox-webpack-plugin
: webpack users can use this pluginworkbox-cli
: The CLI can also be used to generate the list of assets and add them to the service worker.
workbox-range-requests
When making a request, a range
header can be set that tells the server to return only a portion of the full request. This is useful for certain files like a video file, where a user might change where to play the video. There may be scenarios where you want to serve a cached file but the browser has set a range
header.
workbox-recipes
A number of common patterns, especially around routing and caching, are common enough that they can be standardized into reusable recipes. workbox-recipes
makes these available in an easy-to-consume package, allowing you to get up-and-running with a highly functional service worker quickly.
Each recipe combines a number of Workbox modules together, building them into commonly used patterns. The recipes will show the recipe you use when using this module, and the equivalent pattern it's using under the hood, should you want to write it yourself.
- Offline fallback
- The offline fallback recipes allows your service worker to serve a web page, image, or font if there's a routing error for any of the three, for instance if a user is offline and there isn't a cache hit.
- Warm Strategy Cache
- The warm strategy cache recipe allows you to load provided URLs into your cache during the service worker's
install
phase, caching them with the options of the provided strategy. This can be used as an alternative to precaching if you know the specific URLs you'd like to cache, want to warm the cache of a route, or similar places where you'd like cache URLs during installation.
- The warm strategy cache recipe allows you to load provided URLs into your cache during the service worker's
- Page Cache
- The page cache recipe allows your service worker to respond to a request for an HTML page (through URL navigation) with a network first caching strategy, optimized to, ideally, allow for the cache fallback to arrive quick enough for a largest content paint score of less than 4.0 seconds.
- Static Resources Cache
- The static resource cache recipe allows your service worker to respond to a request for static resources, specifically CSS, JavaScript, and Web Worker requests, with a stale-with-revalidate caching strategy so those assets can be quickly served from the cache and be updated in the background.
- Image Cache
- The image cache recipe allows your service worker to respond to a request for images with a cache-first caching strategy so that once they're available in cache a user doesn't need to make another request for them.
- Google Fonts Cache
- The Google Fonts recipe caches the two parts of a Google Fonts request:
- The stylesheet with the
@font-face
definitions, which link to the font files - The static, revisioned font files
- The stylesheet with the
workbox-routing
A service worker can intercept network requests for a page. It may respond to the browser with cached content, content form the network, or content generated in the service worker. workbox-routing
is a module which makes it easy to route
these requests to different functions that provide responses.
How Routing is Performed
When a network request causes a service worker fetch event, workbox-routing
will attempt to respond to the request using the supplied routers and handlers.
A route
in workbox is nothing more than two functions: a matching
function to determine if the route should match a request and a handling
function, which should handle the request and respond with a response. A match callback function is passed a ExtendableEvent
, Request
, and a URL
object you can match by returning a truthy value.
const matchCb = ({url, request, event}) => {
return url.pathname === '/special/url';
};
A handler callback function will be given the same ExtendableEvent
, Request
, and URL
object along with a params
value, which is the value returned by the match
function.
const handlerCb = async ({url, request, event, params}) => {
const response = await fetch(request);
const responseBody = await response.text();
return new Response(`${responseBody} <!-- Look Ma. Added Content. -->`, {
headers: response.headers,
});
};
You can register callbacks like so:
import {registerRoute} from 'workbox-routing';
registerRoute(matchCb, handlerCb);
workbox-strategies
When service workers were first introduced, a set of common caching strategies emerged. A caching strategy is a pattern that determines how a service worker generates a response after receiving a fetch event.workbox-strategies
provides the most common caching strategies so its easy to apply them in your service worker.
- Stale-While-Revalidate
- The stale-while-revalidate pattern allows you to respond to the request as quickly as possible with a cached response if available, falling back to the network request if it's not cached. The network request is then used to update the cache.
- Cache First (Cache Falling Back to Network)
- Offline web apps will rely heavily on the cache, but for assets that are non-critical and can be gradually cached, a cache first is the best option. If there is a Response in the cache, the Request will be fulfilled using the cached response and the network will not be used at all. If there isn't a cached response, the Request will be fulfilled by a network request and the response will be caches so that the next request is served directly form the cache.
- Network First (Network Falling Back to Cache)
- For requests that are updating frequently, the network first strategy is the ideal solution. By default, it will try to fetch the latest response from the network. If the request is successful, it'll put the response in the cache. If the network fails to return a response, the cached response will be used.
- Network Only
- If you require specific request to be fulfilled from the network, the network only is the strategy to use.
- Cache Only
- The cache-only strategy ensures that responses are obtained from a cache. This is less common in workbox, but can be useful if you have your own precaching step.
Stale-While-Revalidate
import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
registerRoute(
({url}) => url.pathname.startsWith('/images/avatars/'),
new StaleWhileRevalidate()
);
Cache First
import {registerRoute} from 'workbox-routing';
import {CacheFirst} from 'workbox-strategies';
registerRoute(({request}) => request.destination === 'style', new CacheFirst());
Network First
import {registerRoute} from 'workbox-routing';
import {NetworkFirst} from 'workbox-strategies';
registerRoute(
({url}) => url.pathname.startsWith('/social-timeline/'),
new NetworkFirst()
);
Network Only
import {registerRoute} from 'workbox-routing';
import {NetworkOnly} from 'workbox-strategies';
registerRoute(({url}) => url.pathname.startsWith('/admin/'), new NetworkOnly());
Cache Only
import {registerRoute} from 'workbox-routing';
import {CacheOnly} from 'workbox-strategies';
registerRoute(({url}) => url.pathname.startsWith('/app/v2/'), new CacheOnly());
Window Packages
workbox-window
The workbox-window
package is a set of modules that are intended to run in the window
context, which is to say, inside of your web pages. They're a complement to the other workbox packages that run in the service worker. The goals of workbox-window
are:
- To simplify the process of service worker registration and updates by helping developers identify the most critical moments in the service worker lifecycle, and making it easier to respond to those moments
- to help prevent developers from making the most common mistakes
- To enable easier communication between code running in the service worker and code running in the window.
If using webpack, it is possible to use webpack to load webpack-window
.
$ npm install workbox-window
import { Workbox } from 'workbox-window';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
Event though workbox-window
is quite small, there's no reason it needs to be loaded with your site's core application logic, as service workers, by their very nature, are a progressive enhancement.
Node.js Modules
workbox-cli
The Workbox command line interface (contained in the workbox-cli
package) consists of a Node.js program called workbox that can be run from a Windows, macOS, or UNIX-compatible command line environment. Under the hood, workbox-cli wraps the workbox-build module, and provides an easy way of integrating Workbox into a command line build process, with flexible configurations.
$ npm install workbox-cli --global
The CLI has four different modules:
wizard
: A step-by-step guide to set up Workbox for your project- Asks a series of questions about your local directory setup and which files you want precached. Your answers are used to generate a configuration file which can then be used when running in
generateSW
mode. - To run:
npx workbox-cli wizard
- Asks a series of questions about your local directory setup and which files you want precached. Your answers are used to generate a configuration file which can then be used when running in
generateSW
: Generates a complete service worker for you.- Generates a complete service worker using a configuration file
- To run:
npx workbox-cli generateSW path/to/config.js
- Don't use
generateSQ
if you want to : - use other Service Worker features (i.e. Web Push)
- .Import additional scripts, or add additional logic for custom caching strategies.
injectManifest
: Injects the assets to precache into your project.- For developers who want more control of their final service worker file. For developers who want to precache files and would like to use the service worker with other platform features.
Whenworkbox injectManifest
is run, it looks for a specific string (precacheAndRoute(self.__WB_MANIFEST)
by default) in your source service worker file. It replaces the empty array with a list of URLs to precache and writes the service worker file to its destination location, based on the configuration options inconfig.js
. The rest of the code in your source service worker is left untouched.
copyLibraries
: Copy the Workbox libraries into a directory
workbox-build
The workbox-build
module integrates into a node-based build process and can generate an entire service worker, or just generate a list of assets to precache that could be used within an existing service worker. The two modes that most developers will use are generateSW
and injectManifest
.
// Inside of build.js:
const {injectManifest} = require('workbox-build');
// These are some common options, and not all are required.
// Consult the docs for more info.
injectManifest({
dontCacheBustURLsMatching: [new RegExp('...')],
globDirectory: '...',
globPatterns: ['...', '...'],
maximumFileSizeToCacheInBytes: ...,
swDest: '...',
swSrc: '...',
}).then(({count, size, warnings}) => {
if (warnings.length > 0) {
console.warn(
'Warnings encountered while injecting the manifest:',
warnings.join('\n')
);
}
console.log(`Injected a manifest which will precache ${count} files, totaling ${size} bytes.`);
});
This will cerate a precache manifest based on the files picked up by your configuration and inject it into your existing service worker file.
workbox-webpack-plugin
Workbox provides two webpack plugins: one that generates a complete service worker for you and one that generates a list of assets to precache that is injected into a service worker file.
Webpack Service Worker
Progressive Web Applications (PWAs) are web apps that deliver an experience similar to native applications. The most significant ability for an app to be able to function when offline. This is achieved through the use of a technology called Service Workers.
$ npm install workbox-webpack-plugin --save-dev
Comments
You have to be logged in to add a comment
User Comments
There are currently no comments for this article.