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

  • PWAConf organizer [coming soon]

  • 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

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?

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

  • Build and deploy - automatically

  • Globally distributed hosting

  • Major frameworks or w/o framework

  • Free preview now

Thank you!

Maxim Salnikov

@webmaxru

Questions?

Maxim Salnikov

@webmaxru

Made with Slides.com