Maxim Salnikov
@webmaxru
Workbox v4:
a brand new
workbox-window
module
Maxim Salnikov
-
PWA Slack organizer
-
PWA Oslo / PWA London meetups organizer
-
PWA speaker and trainer
-
Google Dev Expert in Web Technologies
Azure Developer Technical Lead at Microsoft
What is PWA at all?
Progressive web apps use modern web APIs along with traditional progressive enhancement strategy to create cross-platform web applications.
These apps work everywhere and provide several features that give them the same user experience advantages as native apps.
works everywhere*
* but not everything**
natively
** use progressive enhancement strategy
PWA news
#WSH?
"Progressive Web Apps State of the Union" by Dominick Ng at BlinkOn 10
"Progressive Web Apps State of the Union" by Dominick Ng at BlinkOn 10
BlinkOn
Microsoft & Samsung PWA Roundtable
Google I/O
Microsoft Build
April 8th
April 9-10th
May 6-8th
May 6-8th
-
Application shell
-
Runtime caching
-
Replaying failed network requests
-
Offline Google Analytics
-
Broadcasting updates
Have our own service worker!
Working modes
-
Workbox CLI
-
Webpack plugin
-
Node module
# Installing the Workbox Node module
$ npm install workbox-build --save-dev
Also installs all Workbox libraries via dependencies
Application Shell
// Importing Workbox itself from Google CDN
importScripts('https://googleapis.com/.../workbox-sw.js');
// Precaching and setting up the routing
workbox.precaching.precacheAndRoute([])
src/service-worker.js
Injected by injectManifest() method of workbox-build module
Workbox manifest
[
{
"url": "index.html",
"revision": "34c45cdf166d266929f6b532a8e3869e"
},
{
"url": "favicon.ico",
"revision": "b9aa7c338693424aae99599bec875b5f"
},
...
]
Runtime caching
workbox.routing.registerRoute(
new RegExp('/app/v2/'),
workbox.strategies.networkFirst()
);
src/service-worker.js
workbox.routing.registerRoute(
new RegExp('/images/'),
workbox.strategies.cacheFirst({
plugins: [...]
})
);
Strategies
-
CacheFirst
-
CacheOnly
-
NetworkFirst
-
NetworkOnly
-
StaleWhileRevalidate
Plugins
-
Expiration
-
CacheableResponse
-
BroadcastUpdate
-
BackgroundSync
-
...your own plugin?
const postTweetPlugin =
new workbox.backgroundSync.Plugin('tweetsQueue', {
maxRetentionTime: 24 * 60 // Max retry period
})
src/service-worker.js
workbox.routing.registerRoute(
/(http[s]?:\/\/)?([^\/\s]+\/)post-tweet/,
new workbox.strategies.NetworkOnly({
plugins: [postTweetPlugin]
}),
'POST'
)
Background sync
Better app update UX
App version updates
v1
v2
v1
v1
v2
Deployed
Displayed
v2
A new version of the app is available. Click to refresh.
const updateChannel = new BroadcastChannel('app-shell');
updateChannel.addEventListener('message', event => {
// Inform about the new version & prompt to reload
});
Option #1: BroadcastChannel
updates.component.ts
workbox.precaching.addPlugins([
new workbox.broadcastUpdate.Plugin('app-shell')
]);
src/service-worker.js
// Feature detection
if ('serviceWorker' in navigator) {
// Postponing the registration for better performance
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js');
});
}
Option #2: Service worker lifecycle
app.js
Requirements
-
Feature detection
-
Registration after app fully loaded and UI rendered
-
Hook into service worker lifecycle update event
-
Was the service worker updated?
-
Was the app itself updated?
Workbox v4: workbox-window
import { Workbox } from 'workbox-window';
if ('serviceWorker' in navigator) {
const wb = new Workbox('service-worker.js');
// Event listeners...
wb.register();
}
main.js
$ npm install workbox-window
Alternative
<script type="module">
import {Workbox} from 'https://...cdn.com/workbox-window.mjs';
if ('serviceWorker' in navigator) {
const wb = new Workbox('service-worker.js');
wb.register();
}
</script>
index.html
Was service worker file updated?
wb.addEventListener('installed', event => {
if (event.isUpdate) {
// Show "Newer version is available. Refresh?" prompt
} else {
// Optionally: show "The app is offline-ready" toast
}
});
main.js
workbox.core.skipWaiting()
workbox.core.clientsClaim()
service-worker.js
Must have!
Other useful events
wb.addEventListener('activated', (event) => {
// Service worker activated
});
wb.addEventListener('waiting', (event) => {
// Service worker was installed but can't be activated
});
Same for the "external" service workers:
externalinstalled, externalwaiting, externalactivated
SW <-> Window communication
wb.addEventListener('message', (event) => {
if (event.data.type === 'CACHE_UPDATE') {
const {updatedURL} = event.data.payload;
console.log(`A new version of ${updatedURL} is available!`);
}
});
main.js
Alternative to "manual" BroadcastChannel API usage with workbox-broadcast-update package
Window <-> SW communication
wb.addEventListener('activated', (event) => {
const urlsToCache = [...];
// Send that list of URLs to your router in the service worker.
wb.messageSW({
type: 'CACHE_URLS',
payload: {urlsToCache},
});
});
main.js
Message format follows Flux standard action format
Sample code
-
1900+ developers
-
Major browsers/frameworks/libs reps
Thank you!
Maxim Salnikov
@webmaxru
Questions?
Maxim Salnikov
@webmaxru
Bundling
Deploying only what we use
import { precacheAndRoute }
from 'workbox-precaching/precacheAndRoute.mjs'
import { skipWaiting }
from 'workbox-core/skipWaiting.mjs'
import { clientsClaim }
from 'workbox-core/clientsClaim.mjs'
skipWaiting()
clientsClaim()
precacheAndRoute([])
src/service-worker-bundle.js
Using bundler
import resolve from 'rollup-plugin-node-resolve'
import replace from 'rollup-plugin-replace'
import { terser } from 'rollup-plugin-terser'
export default {
input: 'dist/angular-pwa/service-worker.js',
output: {
file: 'dist/angular-pwa/service-worker.js',
format: 'iife'
},
plugins: [...]
}
rollup.config.js
Needed plugins
plugins: [
resolve(),
replace({
'process.env.NODE_ENV': JSON.stringify('production')
}),
terser()
]
rollup.config.js / plugins
Build script
"build-pwa-bundle":
"ng build --prod &&
node workbox-build-inject.js &&
npx rollup -c"
package.json / scripts
Resulting service-worker.js:
Summary
-
Framework-agnostic
-
Rich functionality
-
Maximum flexible configuration
-
Full power of our own service worker
Setup -> Configure -> Code
Get what you want
Workbox v4: a brand new workbox-window module
By Maxim Salnikov
Workbox v4: a brand new 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 fulfill 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.
- 3,856