Maxim Salnikov

@webmaxru

Diving deep, deep offline –

the web can do it!

What is an offline-ready web application

And how to build it today

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

  1. The World Wide Web is the New Software Platform

  2. The Web Browser is the New Operating System

  3. JavaScript is the de facto Programming Language of the Web 

January, 2008

Web as an app platform is amazing

Browsers

  • Almost on every device with UI

  • Evergreen

  • APIs to access device hardware

Web as an app platform is amazing

JavaScript

Browsers

  • Versatile language

  • Powerful tooling

  • Evolving in a smart way

Web as an app platform is amazing

JavaScript

JS Engines

Browsers

  • Focus on the performance

  • Embedding possibilities

Web as an app platform is amazing

JavaScript

JS Engines

UI Layer

Browsers

  • Convenient tools to build responsive UIs

  • Focus on accessibility

  • Variety of high-quality components and libraries

Web as an app platform is amazing

Community

JavaScript

JS Engines

UI Layer

Browsers

69.7%

Web as an app platform is amazing

Community

JavaScript

JS Engines

UI Layer

Browsers

Issues?

Historically depends on the "connection status"

Solutions

Caching

Installing

  • HTTP Cache?

  • AppCache

  • Save page as... (complete)

  • Chrome Apps

  • Electron

  • NativeScript, React Native

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

Offline-

ready  

<

>

=

Installation as a native app

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

Managing the cache

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

})
self.addEventListener('activate', event => {
  
    // Clean the cache from the obsolete 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

  • ...

Serving from the cache

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

  • ...

  • Define assets

  • Put in the cache

  • Serve from the cache

  • Manage versions

}

Is there a helper?

# 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

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

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-pwa": "npm run build-app &&
                  node workbox-build.js"
  }
}

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

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?

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

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

src/workbox-service-worker.js

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

Full control over downloads

Background fetch

  • Pause/resume download/upload automatically

  • Aware of fetch status/progress

  • No need to keep the app open

  • UI for the user to pause/cancel and track progress

const registration = await navigator.serviceWorker.ready;
await registration.backgroundFetch.fetch(
  'my-series',
  ['s01e01.mpg', 's01e02.mpg'],
  {
    title: 'Downloading My Series',
    downloadTotal: 600 * 1024 * 1024
   }
);

main.js

addEventListener('backgroundfetchsuccess', event => {
  event.waitUntil(
    (async function() {
      try {
        // Put the responses to Cache Storage
        ...
        await event.updateUI({ title: `Downloaded!` });
      } catch (err) {
        await event.updateUI({ title: `Fail: ${err}` });
      }
    })()
  );
});

src/service-worker.js

More details

Schedule your updates

Periodic background sync

  • Like a "Cron" in the browser

  • Connection independent

  • Cadence set by the developer

  • Another API for that

  • Works only online

  • Browser decides

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(
    	// [Maybe] Do the job
    );
  }
});

src/service-worker.js

  • Network connection type?

  • Data saver mode?

  • Available storage space?

  • 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

Thank you!

Maxim Salnikov

@webmaxru

  • 2000+ developers

  • Major browsers/frameworks/libs reps

  • Все о PWA на русском языке

chrome://flags/#enable-devtools-experim

Debugging background services

More platform tools

Native File System API

Badging API

Contact Picker API

Features flying soon

Almost 100 new 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

  • Все о PWA на русском языке

Thank you!

Maxim Salnikov

@webmaxru

Questions?

Maxim Salnikov

@webmaxru

Diving deep, deep offline — the web can do it

By Maxim Salnikov

Diving deep, deep offline — the web can do it

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,806