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
-
PWAdvocate
-
PWA Oslo / PWA London meetups organizer
-
ngVikings and Mobile Era conferences organizer
-
Google Dev Expert in Web Technologies
Developer Audience Lead at Microsoft
-
The World Wide Web is the New Software Platform
-
The Web Browser is the New Operating System
-
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
<
>
=
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([])
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?
chrome://flags/#enable-devtools-experim
Debugging background services
More platform tools
Native File System API
Badging API
Contact Picker API
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
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,179