Maxim Salnikov
@webmaxru
What is an offline-ready web application
Azure Developer Technical Lead at Microsoft
Browsers
JavaScript
Browsers
JavaScript
JS Engines
Browsers
JavaScript
JS Engines
UI Layer
Browsers
Community
JavaScript
JS Engines
UI Layer
Browsers
Community
JavaScript
JS Engines
UI Layer
Browsers
Issues?
Progressive web apps use modern web APIs along with traditional progressive enhancement strategy to create cross-platform web applications.
* but not everything**
** use progressive enhancement strategy
My App
App
Service worker
Browser/OS
Event-driven worker
Cache
fetch
push
sync
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 => {
if (event.request.url.indexOf('/api') != -1) {
event.respondWith(
// Network-First Strategy (for API?)
)
} else {
event.respondWith(
// Cache-First Strategy (for app shell?)
)
}
})
# Installing the Workbox Node module
$ npm install workbox-build --save-dev
// 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'
}
// Importing Workbox itself from Google CDN
importScripts('https://googleapis.com/.../workbox-sw.js');
// Precaching and setting up the routing
workbox.precaching.precacheAndRoute([])
Caching, serving, managing versions
// 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.`)
})
{
"scripts": {
"build-pwa": "npm run build-app &&
node workbox-build.js"
}
}
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
)
}
})
workbox.routing.registerRoute(
new RegExp('/api/breakingnews'),
new workbox.strategies.NetworkFirst()
);
workbox.routing.registerRoute(
new RegExp('/api/archive'),
new workbox.strategies.CacheFirst({
plugins: [...]
})
);
navigator.serviceWorker.ready.then( swRegistration => {
return swRegistration.sync.register('postTweet');
});
self.addEventListener('sync', event => {
if (event.tag == 'postTweet') {
event.waitUntil(
// Do useful things...
);
}
});
const postTweetPlugin =
new workbox.backgroundSync.Plugin('tweetsQueue', {
maxRetentionTime: 24 * 60 // Max retry period
})
workbox.routing.registerRoute(
/(http[s]?:\/\/)?([^\/\s]+\/)post-tweet/,
new workbox.strategies.NetworkOnly({
plugins: [postTweetPlugin]
}),
'POST'
)
const registration = await navigator.serviceWorker.ready;
await registration.backgroundFetch.fetch(
'my-series',
['s01e01.mpg', 's01e02.mpg'],
{
title: 'Downloading My Series',
downloadTotal: 600 * 1024 * 1024
}
);
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}` });
}
})()
);
});
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.
}
}
self.addEventListener('periodicsync', (event) => {
if (event.tag === 'refreshTweets') {
event.waitUntil(
// [Maybe] Do the job
);
}
});
Maxim Salnikov
@webmaxru
chrome://flags/#enable-devtools-experim
Maxim Salnikov
@webmaxru
Maxim Salnikov
@webmaxru