Maxim Salnikov

@webmaxru

Taking your web app offline (in a good sense)

What is an offline-ready web application

And how to build it today

Maxim Salnikov

  • PWA Oslo / PWA London meetups organizer

  • ngVikings / Mobile Era conferences organizer

  • Google Dev Expert in Web Tech / Capabilities & Installability

Developer Audience Lead at Microsoft

Web as an app platform

  • Historically depends on the "connection status"

  • Evergreen browsers

  • Versatile language

  • Performant JS engines

  • Excellent tooling

  • Huge community

Proper offline-ready web app

  • App itself

  • Online runtime data

  • Offline runtime data

  • Connection failures

  • Updates

  • Platform features

  • Always available

  • Thoughtfully collected

  • Safely preserved

  • Do not break the flow

  • Both explicit and implicit

  • For the win!

While keeping its web nature!

Application UI

Let's build an App shell

My App

  • Define assets

  • Put in the cache

  • Serve from the cache

  • Manage versions

}

Service worker

Logically

Physically

-file(s)

App

Service worker

Browser/OS

Event-driven worker

Cache

fetch
push
sync

Own service worker

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

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

self.addEventListener('fetch', event => {
    // Serve assets from cache or network
})

handmade-service-worker.js

It's only partially a joke

Because...

Redirects?

Fallbacks?

Opaque response?

Versioning?

Cache invalidation?

Spec updates?

Cache storage space?

Variable asset names?

Feature detection?

Minimal required cache update?

Caching strategies?

Routing?

Fine-grained settings?

Kill switch?

I see the old version!!!

  • Define assets

  • Put in the cache

  • Serve from the cache

  • Manage versions

}

Is there a helper?

While having our own service worker

  • Application shell

  • Runtime caching

  • Replaying failed network requests

  • Offline Google Analytics

  • Broadcasting updates

# Installing the Workbox Node module
$ npm install workbox-build --save-dev

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.js

Source service worker

import { precacheAndRoute } from "workbox-precaching";

// Precaching and setting up the routing
precacheAndRoute(self.__WB_MANIFEST)

src/workbox-service-worker.js

Caching, serving, managing versions

Build service worker

// 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.js

Build flow integration

{
  "scripts": {
    "build-sw": "node workbox-build.js && npx rollup -c",
    "build":    "npm run build-app && npm run build-sw"
  }
}

package.json

Runtime 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

import { registerRoute } from "workbox-routing";
import * as strategies from "workbox-strategies";

registerRoute(
  new RegExp('/api/breakingnews'),
  new strategies.CacheFirst()
);

src/workbox-service-worker.js

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

Strategies

  • CacheFirst

  • CacheOnly

  • NetworkFirst

  • NetworkOnly

  • StaleWhileRevalidate

Plugins

  • Expiration

  • CacheableResponse

  • BroadcastUpdate

  • BackgroundSync

  • ...your own plugin?

Save and sync offline actions

Background sync

  • One-off event for offline -> online

  • No active app tab required

navigator.serviceWorker.ready.then( swRegistration => {
  return swRegistration.sync.register('postTweet');
});

main.js

self.addEventListener('sync', event => {
  if (event.tag == 'postTweet') {
    event.waitUntil(
        // Do useful things...
    );
  }
});

handmade-service-worker.js

import {BackgroundSyncPlugin} from 'workbox-background-sync';

const postTweetPlugin =
    new BackgroundSyncPlugin('tweetsQueue', {
        maxRetentionTime: 24 * 60 // Max retry period
    })

src/workbox-service-worker.js

registerRoute(
  /(http[s]?:\/\/)?([^\/\s]+\/)post-tweet/,
  new strategies.NetworkOnly({plugins: [postTweetPlugin]}),
  'POST'
)

Schedule your updates

Periodic background sync

  • App installed

  • Online

  • Original network

  • Occasional interactions

  • No cadence control

  • Shortest break: 12 hours

Periodically run custom code without notifying user

const registration = await navigator.serviceWorker.ready;
if ('periodicSync' in registration) {
  try {
    registration.periodicSync.register('refreshTweets', {
      // An interval of one day.
      minInterval: 24 * 60 * 60 * 1000,
    });
  } catch (error) {
    // PBS cannot be used.
  }
}

main.js

self.addEventListener('periodicsync', (event) => {
  if (event.tag === 'refreshTweets') { 
    event.waitUntil(
    	fetchNewTweets() // Maybe
    );
  }
});

src/service-worker.js

  • Network connection type?

  • Data saver mode?

  • Available storage space?

Debugging background services

Chromium -> DevTools -> Application

More platform tools

  • Background Sync API

  • Background Fetch API

  • Native File System API

  • Badging API

  • Contact Picker API

  • Notification Triggers API

 + more than 100 other APIs

  • Full-fledged application platform

  • Offline-ready mechanisms are in production

  • Awesome tools are available

  • User experience & security is the key

And this is just the beginning!

Web platform today

  • 2000+ developers

  • Major browsers/frameworks/libs reps

Thank you!

Maxim Salnikov

@webmaxru

Questions?

Maxim Salnikov

@webmaxru

Taking your web app offline (in a good sense)

By Maxim Salnikov

Taking your web app offline (in a good sense)

There is no need to advocate for progressive web apps anymore. The idea of connection-independent applications has proven its viability and we see many projects following that path, making the offline-ready behavior a best practice, good manner of the web. In my session, based on the exploration of Service Worker API (Cache Storage, Background Fetch, Background Sync) we go through the history of the offline web, treating the network as an enhancement, current challenges, solutions, and proper tooling.

  • 3,086