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.

Date Created:
Last Edited:
1 44

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 and networkFirst: Responses with a status of 0 (i.e. opaque responses) or 200 are considered cacheable
  • cacheFirst: Responses with a status of 200 are considered cacheable.
An opaque-redirect filtered response is a filtered response whose type if opaqueredirect, 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 script
  • workbox-webpack-plugin: webpack users can use this plugin
  • workbox-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.

  1. Offline fallback
    1. 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.
  2. Warm Strategy Cache
    1. 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.
  3. Page Cache
    1. 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.
  4. Static Resources Cache
    1. 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.
  5. Image Cache
    1. 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.
  6. Google Fonts Cache
    1. The Google Fonts recipe caches the two parts of a Google Fonts request:
      1. The stylesheet with the @font-face definitions, which link to the font files
      2. The static, revisioned font files


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.

How Routing is Performed

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.

  1. Stale-While-Revalidate
    1. 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.
  2. Cache First (Cache Falling Back to Network)
    1. 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.
  3. Network First (Network Falling Back to Cache)
    1. 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.
  4. Network Only
    1. If you require specific request to be fulfilled from the network, the network only is the strategy to use.
  5. Cache Only
    1. 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:

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:

  1. wizard: A step-by-step guide to set up Workbox for your project
    1. 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.
    2. To run: npx workbox-cli wizard
  2. generateSW: Generates a complete service worker for you.
    1. Generates a complete service worker using a configuration file
    2. To run: npx workbox-cli generateSW path/to/config.js
    3. Don't use generateSQ if you want to :
      1. use other Service Worker features (i.e. Web Push)
      2. .Import additional scripts, or add additional logic for custom caching strategies.
  3. injectManifest: Injects the assets to precache into your project.
    1. 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.
When workbox 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 in config.js. The rest of the code in your source service worker is left untouched.
  1. 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

Insert Math Markup

ESC
About Inserting Math Content
Display Style:

Embed News Content

ESC
About Embedding News Content

Embed Youtube Video

ESC
Embedding Youtube Videos

Embed TikTok Video

ESC
Embedding TikTok Videos

Embed X Post

ESC
Embedding X Posts

Embed Instagram Post

ESC
Embedding Instagram Posts

Insert Details Element

ESC

Example Output:

Summary Title
You will be able to insert content here after confirming the title of the <details> element.

Insert Table

ESC
Customization
Align:
Preview:

Insert Horizontal Rule

#000000

Preview:


View Content At Different Sizes

ESC

Edit Style of Block Nodes

ESC

Edit the background color, default text color, margin, padding, and border of block nodes. Editable block nodes include paragraphs, headers, and lists.

#ffffff
#000000

Edit Selected Cells

Change the background color, vertical align, and borders of the cells in the current selection.

#ffffff
Vertical Align:
Border
#000000
Border Style:

Edit Table

ESC
Customization:
Align:

Upload Lexical State

ESC

Upload a .lexical file. If the file type matches the type of the current editor, then a preview will be shown below the file input.

Upload 3D Object

ESC

Upload Jupyter Notebook

ESC

Upload a Jupyter notebook and embed the resulting HTML in the text editor.

Insert Custom HTML

ESC

Edit Image Background Color

ESC
#ffffff

Insert Columns Layout

ESC
Column Type:

Select Code Language

ESC
Select Coding Language

Insert Chart

ESC

Use the search box below

Upload Previous Version of Article State

ESC