Workbox 4: Implementing refresh-to-update-version flow using the workbox-window module
The next major version of the very popular PWA helper library was just released. Workbox 4 brings many interesting additions to the existing modules and only a few minor breaking changes. Also, it ships one totally new module called workbox-window, to fulfil the need of developers in a simple and powerful way to register the service worker, to hook into its lifecycle, and to provide a bi-directional communication channel with the app. This is the first module of Workbox to be used in the window context, i.e. in our application’s (not service worker’s) code.
Let’s explore this new module to check what will it take to build the well-known “refresh-to-update-version” technique — one of the UX best practice for PWA. As we use this flow often while building our applications, and Workbox exposes the corresponding tooling now, we just need to find a simple and robust code to build that flow. This article is my try to find that code: minimal and stable. But first, what is this flow I’m talking about?
Refresh-to-update-version 101
You open some website. And after a couple of seconds, it shows you some prompt/dialogue saying “A new version of this website is available. [Refresh the page to get it]”. In most cases that means:
- This is a service worker-driven origin (if it also has Web App Manifest we can call it PWA), and the UI you see was not fetched from the network but was taken from the Cache Storage of your browser
- You visited this website using this browser some time ago
- Between the previous and current visit, the deployed version was updated
The dialogue you see is a sort of trade-off between the possibility to show app UI immediately, without fetching it from the network (using precached version) and the need to deliver the actual version of the app to the user after all. Where is the compromise then? It’s in the fact that we still load the precached (“old”) version of the app from Cache Storage, but if the service worker knows that there is a new version available, it fetches the updated resources, updates cache and sends a message (using Broadcast Channel API or postMessage) to the app. Where we catch this message and show the notorious “The app was updated. Click refresh to upload” message to the user. Next page load — and we serve the “new” version from Cache Storage (of course, if our service worker performed all the listed above operations in a proper way).
Another variation of this technique — we do not send any signal from service worker but listen to the changes of its lifecycle in our app. For our case, the combination of onupdatefound and onstatechange events caused by the fetching of byte-different service worker could mean the change of hash sums of the resource(s) mentioned in “to precache” list injected in service worker. Which, in its turn, means that the new version of the app was built — so we can safely show “The app was updated” message.
Workbox v3 options to have refresh-to-update-version flow
First, let’s say thanks to the Workbox maintainers for the magic precacheAndRoute() method we could use in our own service worker. This method takes care of all the complexity of precaching, version maintaining, fetching updated resources, updating the cache etc. We just pass the object with resources and their hash sums (built by another helper from Workbox family — workbox-build module) and it works. Furthermore, another line of code in the service worker:
…and we can receive the signals about the precached resources were updated in our app code — exactly what we need to show the message to our user:
We can even add the same plugin to the resources we cache during runtime to follow their updates if needed.
Why do I tell about the option we use in Workbox 3 in the article about Workbox 4? Because it still works fine — your code from v3 related to this flow will not break.
What about the second option — when we rely on the service worker lifecycle events? In v3 we don’t have any helpers to actually register our Workbox-driven service worker in our app code and subscribe to its events. Of course, we always can write this ourselves or use the really nice register-service-worker library by Evan You, then the code in our app could look like:
But now we have way more powerful, flexible and truly Workbox-native way to achieve it: workbox-window module. As stated in the documentation, The key features/goals of this module 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.
Let’s implement the above UX trick using this module.
The refresh-to-update-version flow powered by workbox-build
Let’s start at the very beginning. To demo the flow, we need to implement a service worker with precaching and serving the resources forming our application shell.
The minimalistic version of the Workbox-powered service worker source file could look like:
Lines 8 and 9 are important in the context of this article. You will read later why do we need them
Why is this “source file”? Because we have to process it after every build of our application. To be precise — we have to inject the list of resources to precache and their hash sums as a parameter for precacheAndRoute() method (instead of this empty array). To save us from this boring task Workbox has 3 options to choose from: Workbox CLI, Webpack plugin, and Node module. The last one is my choice: it needs neither globally installed CLI nor Webpack configuration file exposed. Installing the workbox-build module:
npm install workbox-build --save-devNow the service worker build script:
And the final part — is to add the npm run script combining the build of our app and service worker, one after another:
As you might notice, I use an Angular app in my example (ng build --prod is a build command for it) but everything I describe in that article about Workbox modules and PWA techniques is applicable to any JavaScript application.
Get Maxim Salnikov’s stories in your inbox
Join Medium for free to get updates from this writer.
After I do npm run build-pwa I see something like
Generated dist/angular-pwa/service-worker.js, which will precache 6 files, totaling 735289 bytes.And the service worker in the distribution folder now contains all the info Workbox needs to know about our app:
It would be the same in Workbox 3. But now the difference starts: let’s register this service worker in our app using workbox-window. Installing the module first:
npm install workbox-windowHint: there are different scenarios of importing/using/bundling this module available.
Now in our application code:
Some important things to notice: