Maxim Salnikov

@webmaxru

For Production

In 1-2-3

Part3

Maxim Salnikov

@webmaxru

For Production

In 1-2-3

Part 2

Permanent link:

If you want to code during the session

We need a laptop/desktop with installed (latest stable versions):

  • Git
  • Node
  • NPM

 

Browsers (latest stable versions):

  • Chrome / Firefox / Edge

 

Quick start

npm install serve -g
git clone https://github.com/webmaxru/pwa-for-production
cd pwa-for-production
git checkout part-1
npm install
serve

Recommended to copy code snippets from

Optional

Maxim Salnikov

  • PWA Slack organizer

  • PWA Oslo / PWA Oslo meetups organizer

  • PWA speaker and trainer

Full-Stack Developer, PWAdvocate

Agenda

  • What are the cons and challenges of the manually written service workers

  • Introducing Workbox

  • Automating application shell lifecycle: from building to serving

  • Organizing a proper UX for the app update

  • Runtime caching: strategies

  • Proper registration of Workbox in your app

=

+

Application shell

Web App Manifest

Fast, responsive, mobile-first

Served via HTTPS

Application UI (App Shell)

Let's build an App shell

My App

  • Define the minimal set of resources we need, and keep versioning in mind

  • On the first app load, explicitly put these resources into the Cache Storage

  • On the next app loads, serve resources from this cache before going to the network

  • At the same time, check if the never version is available. If yes, update the cache

Logically

Physically

-file(s)

App

Service-worker

Browser/OS

Event-driven worker

Cache

fetch
push
sync

Service Worker 101

Managing cache

self.addEventListener('install', event => {
  
    // Use Cache API to cache html/js/css

})
self.addEventListener('activate', event => {
  
    // Clean the cache from the obsolete app shell versions

})

handmade-service-worker.js

In the real world

  • Can't add opaque responses directly

  • Redirected requests should be managed

  • Always creating a new version of cache and deleting the old one is not optimal

  • Control over cache size is required

  • Cache invalidation for runtime caching is complex

  • ...

Intercepting requests

self.addEventListener('fetch', event => {

  if (event.request.url.indexOf('/api') != -1) {
    event.respondWith(
      // Network-First Strategy (for API?)
    )
  } else {
    event.respondWith(
      // Cache-First Strategy (for app shell?)
    )
  }
})

handmade-service-worker.js

In the real world

  • All kinds of fallbacks needed for the strategies

  • There are more complex strategies like Stale-While-Revalidate

  • Good to have some form of the routing

  • Good to have the possibility to provide some extra settings for different resource groups

  • ...

  • 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

Build script

// We will use injectManifest mode
const {injectManifest} = require('workbox-build')

// Sample configuration with the basic options
var workboxConfig = {...}

// Calling the method and output the result
injectManifest(workboxConfig).then(({count, size}) => {
    console.log(`Generated ${workboxConfig.swDest},
    which will precache ${count} files, ${size} bytes.`)
})

workbox-build-inject.js

Build script configuration

// Sample configuration with the basic options
var workboxConfig = {
  globDirectory: 'dist/',
  globPatterns: [
    '**/*.{txt,png,ico,html,js,json,css}'
  ],
  swSrc: 'src/workbox-service-worker.js',
  swDest: 'dist/sw.js'
}

workbox-build-inject.js

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/workbox-service-worker.js

Caching, serving, managing versions

Precaching manifest

[
  {
    "url": "index.html",
    "revision": "34c45cdf166d266929f6b532a8e3869e"
  },
  {
    "url": "favicon.ico",
    "revision": "b9aa7c338693424aae99599bec875b5f"
  },
  ...
]

Build flow integration

{
  "scripts": {
    "build-pwa": "npm run build-app &&
                  node workbox-build-inject.js"
  }
}

package.json

Coding time

Application Data

Intercepting requests

self.addEventListener('fetch', event => {

  if (event.request.url.indexOf('/api/breakingnews') != -1) {
    event.respondWith(
      // Network-First Strategy
    )
  } else if (event.request.url.indexOf('/api/archive') != -1 {
    event.respondWith(
      // Cache-First Strategy
    )
  }
})

handmade-service-worker.js

Routes and strategies

workbox.routing.registerRoute(
  new RegExp('/api/breakingnews'),
  new workbox.strategies.NetworkFirst()
);

src/workbox-service-worker.js

workbox.routing.registerRoute(
  new RegExp('/api/archive'),
  new workbox.strategies.CacheFirst({
    plugins: [...]
  })
);

Strategies

  • CacheFirst

  • CacheOnly

  • NetworkFirst

  • NetworkOnly

  • StaleWhileRevalidate

Plugins

  • Expiration

  • CacheableResponse

  • BroadcastUpdate

  • BackgroundSync

  • ...your own plugin?

Coding time

Better UX for update flow

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

main.js

workbox.precaching.addPlugins([
    new workbox.broadcastUpdate.Plugin('app-shell')
]);

src/workbox-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

Coding time

In the next session...

All beyond caching: web push notifications, background sync/fetch, upcoming APIs

  1. Intro to push notifications, required parties, flow

  2. Important notice on notifications UX

  3. The latest stable PWA API for large resources downloading: background fetch

  4. APIs of the Project Fugu: let's code something that will be only available soon!

  5. Course summary: testing your PWA in Lighthouse

  6. Other famous PWA case studies for your inspiration

Thank you!

Maxim Salnikov

@webmaxru

Questions?

Maxim Salnikov

@webmaxru

PWA For Production In 1-2-3. Part 2

By Maxim Salnikov

PWA For Production In 1-2-3. Part 2

We take the latest version of the awesome Workbox library to see how easy is to automate all the offline-ready functionality we need for PWA while still keeping a full control on the service worker behavior. Agenda: What are the cons and challenges of the manually written service workers Introducing Workbox Automating application shell lifecycle: from building to serving Organizing a proper UX for the app update Runtime caching: strategies What is background sync and how to implement it using Workbox Proper registration of Workbox in your app

  • 3,961