Pardon the interruption in the Alexa Skill and serverless series, but a couple of things happened. First, I started using the next version of the Alexa SDK for node.js. Second, I saw an excellent Alexa Skill Developer promotion that I had to write a new skill for, so that took up my free time in October, other than working on my kid’s Halloween costume; I’ll be updating my next post in the series to reflect the new API. Lastly, I’ve started diving into improved configurations of service workers, which has led me to be a firm proponent of workbox.
- the “is service worker ready” page
- MDN’s page on the Service Worker API
- the “Can I Use” page for serice workers; spoiler alert, yes you can
Why Use One
The service worker API relies on a few things, such as the
fetch API and
Promise API. That being said, what it means at a functional level is that the service worker, once registered, can listen in on and, potentially, intercept failing network requests. As I’m sure you can imagine, this can be immensely powerful, once they’re configured, registered, and working correctly.
Service workers require being loaded/registered over HTTPS. For local testing, you can either implement a development server that supports HTTPS and, as is preferable, a certificate configured for your localhost environment.
Since it executes outside the context of the main page, it must be within a separate file.
Ever since iOS 11.3, service workers are supported with some rather minor caveats. Android Chrome has been fully ready for a while. On the desktop, Firefox and Chrome have been playing ball for a while and MS Edge has supported for a little while. This means that unless you’re locked into an abusive relationship with yet another version of IE (11), we can play with the cool toys.
Feature detection, and environment detection, can be pretty easily done, especially when you’re building your source application files. I have a preference of late to parcel, which allows for making use of the pretty common node.js convention of checking whether the
NODE_ENV environment variable is set to
production, this means that, once it is set up, I can avoid loading it in my local environment, for simplicity. All that’s left is to create the service worker itself, here referenced as
*note: since I’m using parcel, it’s intelligent enough to try and find all my referenced js files, such as
sw.js. As I’m not creating the “real” version of this file until the production build is created, and parcel isn’t checking the if condition of
'serviceWorker' in navigator, that means I’ve created a stubbed, but otherwise emtpy, file called
sw.js in the source.
Workbox describes itself as “…a library that bakes in a set of best practices and removes the boilerplate every developer writes when working with service workers.” Workbox describes itself as being the successor to both sw-precache and sw-toolbox.
My experience has been pretty close to eliminating the need for manual creation of a service worker, and it has been easier than my experience with either above mentioned library. This is likely why I’ve enjoyed it so much, I don’t need to do the manual work while keeping any specifics to a simple configuration.
There are a few flavors of workbox which you can use. These generally amount to a node module, a webpack plugin, and a cli tool. Each is relatively easy to get started with, but I’m going to focus on using the CLI tool.
Install and Configuration
I’ve found the workbox-cli tool works rather well in an agnostic fashion when used with parcel. There do appear to be a couple parcel plugins which aim to achieve the same, but one is for sw-precache, and the one I found on GitHub targeting workbox isn’t even published to npm. So here I am, thankfully it’s an easy path to follow.
npm install --save-dev workbox-cli, so simple, just about any dev should be able to do it
- run the wizard, which will prompt you with questions to assist in creating your configuration file
- if you’ve installed workblox globally, you can call it with the name workbox,
- if you’ve installed it as a development dependency, as I outline in step 1, you can access it in the
.bindirectory inside of
- that script by name will work in an npm script, which includes the nested
.bindirectory in its PATH
- if you’ve installed workblox globally, you can call it with the name workbox,
- add the build of the service worker to your build pipeline, such as npm scripts
Creating the Configuration File
The wizard will ask you things like “where is your build app deployed from?” I’m building an application to the
dist/ directory in my project repo. This is the directory I have defined. Then, having scanned the contents of my
dist/ directory, it prompts me on what to cache, by file type (extension). This is an interactive list, so you can de-select as the case may be, but that’s probably unlikely. Lastly it will ask you what you want your file to be named. For consistency with the script I have to register it, it will be
sw.js. Here’s my config file:
Updating The Build Process
I have been using an npm script
build to perform my build of a production version of the app with
npm run build. This is preserved and only added to, as I then created a
build:sw script in my
scripts block. Now, in my deploy config, it calls the unit tests, build, and build of the service worker as such:
- npm test - npm run build - npm run build:sw
The Service Worker Build Script
You can use one of a couple options in generating the service worker. The CLI tool outlines the following as actions the CLI tool can take:
wizard: a step-by-step guide to set up Workbox for your project
generateSW: generates a complete service worker for you
injectManifest: injects the assets to precache into your project
copyLibraries: copy the Workbox libraries into a directory
I’ve already discussed what the
wizard task will prompt for setting up a configuration file.
If you have basic pre-caching needs without any advanced configuration, you’ll want to use
generateSW, as it’s the simplest. The example project I mention below uses this approach, and the invocation is
workbox generateSW <path-to>/workbox-config.js. It uses its
precacheAndRoute implementation out of the box.
This is perhaps the most interesting one, as it’s certainly powerful. When your needs exceed simple precaching of assets, you’ll want to specify a few things in a source service worker file. In that
src/sw.js file that was a stub earlier in my described example, now it becomes closer to the final version of the generated service worker; don’t worry though, we won’t need to copy in any files.
First, we update our
workbox-config.js file to accomodate the source for the
sw.js file; it’s the
Now that that’s set, it’s time to fill out our
src/sw.js file. We begin it with the
importPackage of the latest workbox-sw script, in this case from Google’s CDN. Then we define the precache strategy that we’ll use as the base, leaving an empty array. This will let the glob matching defined in the config file build and populate that array with what’s needed.
Lastly, we update the command to generate the service worker with the task passed to the workbox cli;
workbox injectManifest <path-to>/workbox-config.js.
*note: for more on workbox caching strategies, there’s plenty to read up on them
Finally, we have the two big exceptions that drove me to use
injectManifest in this example. In the repo for my GitHub user page, I pull down both the JSON response for my user account that drives the information in the corresponding vue component. This is the
https://api.github.com/users/<username> call. This also has a link to an avatar image,
https://avatars3.githubusercontent.com/.... Those two specific responses I want to include as cached, or at least cacheable, for this site. Hence, the following in the
The strategy I used is cahce first, which will cache and prefer the cahced copy of this/these assets. This is a choice on my part and, if I expected these items to change with any great frequency, I would probably want to give ita network first strategy, so the cache would be the fail over, not the norm.
copyLibraries task will do just that, copying the needed libraries that the generated service worker will use, to your server, as opposed to pulling them from the Google CDN. You can invoke it like so:
workbox copyLibraries <path-to>/workbox/
If you’re looking for an example, I recently put together a simple app in vue.js which uses each of the above configs and build/deploy tasks, with one change, the workbox config also bundles up some small audio files. The application is a pointing sound board, in that it is a sound board geared for a humorous addition to pointing poker. It works well in the browser, is installable to a mobile device’s home screen (Android and iOS), and works entirely offline. The app is simple, but the tooling around it is pretty impressive, and it’s not that hard to set up.
The pointing sound board app is created off of a boiler plate I put together to be the foundation for such applications. It’s called vue-parcel-starter, and it simplifies a couple of the minor things I had to do to get the tooling I wanted working harmoneously. I also published an npm initializer, so you can create an instance of the latest of that starter file with either:
npm init vue-parcel <app-name>
npx create-vue-parcel <app-name>
I hope this has been informative, or at least a bit insightful, as to how to go about setting things up. I didn’t cover some of the other PWA basics, such as a manifest JSON file, meta headers in your
index.html, or icon files. Otherwise, it’s all there and you can find that other information in my example repository.
Until next time, let’s build better apps!