Maxim Salnikov
Webアプリをオフラインに
(いい意味で)
マキシム サルニコフ
@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 (London, April 19) igniter
-
PWA Oslo / PWA London meetups organizer
-
ngVikings / Mobile Era conferences organizer
-
Google Dev Expert in Web Technologies
Developer Audience Lead at Microsoft
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
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
// Importing Workbox itself from Google CDN
importScripts('https://googleapis.com/.../workbox-sw.js');
// Precaching and setting up the routing
workbox.precaching.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-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'
)
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?
Features flying soon
More than 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
Thank you!
Maxim Salnikov
@webmaxru
Maxim Salnikov
@webmaxru
Questions?
Taking your web app offline (in a good sense)
By Maxim Salnikov
Taking your web app offline (in a good sense)
アプリケーションプラットフォームとしてのWebは驚くべきものです。 UIを備えたほぼすべてのデバイスには、ブラウザーまたは何らかのWebビューが存在しています。今ではハードウェアにアクセスするためのネイティブAPIがあり、優れたツールを備えた汎用言語を使用されています。結局のところ、現在の開発者コミュニティ巨大で史上最大のサイズです!しかしWebはこれまで常にネットに接続されているのが前提となっていました。 私のセッションでは、最新のWeb APIといくつかのベストプラクティスにより、オフライン優先のWebアプリを構築できるケースを紹介します。常に利用可能、データ消費に配慮、保存と同期、接続に関係なく、ネイティブアプリに匹敵する(あるいはさらに優れている)UXを提供します。 さらにツールの現在の状態により、これらの機能を高速かつ信頼性の高い方法で追加できます。オフライン優先アプリを作成して、非常に実用的な方法でオフラインWebを実現しましょう!
- 5,214