There is no doubt that 2018 is the year when Progressive Web Apps will get the really broad adoption and recognition by all the involved parties: browser vendors (finally, all the major ones), developers, users. And the speed and smoothness of this process heavily depend on how correctly we, developers, use the power of new APIs. In my session based on the accumulated experience of developing and maintaining PWAs we go through the list of advanced tips & tricks, showcase best practices, learn how to avoid common pitfalls and have a look at the latest browser support and known limitations.
2. “ How to use what serviceHow to use what service
worker already knowsworker already knows
And what else will it learn?And what else will it learn?
3. Maxim SalnikovMaxim Salnikov
@webmaxru@webmaxru
"PWAdvocate""PWAdvocate"
PWA Oslo / PWA London meetups,PWA Oslo / PWA London meetups,
PWA slack organizerPWA slack organizer
Mobile Era / ngVikings conferencesMobile Era / ngVikings conferences
organizerorganizer
“ Products from the future
UI Engineer at ForgeRock
4. Predictable cachingPredictable caching
Postpone networking while offlinePostpone networking while offline
Receiving and showing notificationsReceiving and showing notifications
Service Worker API
Is there anything REALLY new?Is there anything REALLY new?
Adding payment methods JITAdding payment methods JIT
Full-scale offline modeFull-scale offline mode
Networking optimizationsNetworking optimizations
install, activate,
fetch,
backgroundfetchsuccess,
backgroundfetchfail,
backgroundfetchclick
sync
push, notificationclick
paymentrequest
6. Similar to SharedWorkerSimilar to SharedWorker
Works in its own global contextWorks in its own global context
Works in a separate threadWorks in a separate thread
Isn’t tied to a particular pageIsn’t tied to a particular page
Has no DOM accessHas no DOM access
https://github.com/w3c/ServiceWorker/blob/master/explainer.md
7. Different from SharedWorkerDifferent from SharedWorker
Can run without any page at allCan run without any page at all
Works only with HTTPS (localhost is an exception)Works only with HTTPS (localhost is an exception)
Can be terminated by the browser anytimeCan be terminated by the browser anytime
Has specified lifecycle modelHas specified lifecycle model
https://github.com/w3c/ServiceWorker/blob/master/explainer.md
10. After all, what is PWA?After all, what is PWA?
Progressive web apps use modern web APIs along
with traditional progressive enhancement strategy
to create cross-platform web applications.
https://developer.mozilla.org/en-US/Apps/Progressive
These apps workThese apps work everywhereeverywhere and provideand provide
several features that give them the sameseveral features that give them the same
user experience advantagesuser experience advantages as native apps.as native apps.
12. Let's build an App ShellLet's build an App Shell
My App
Define the set of assets required to show the minimum
viable UI
New version is
available.
Reload page?
Service worker
install: put the assets into Cache Storage
activate: clear Cache Storage from the previous app
version assets
fetch: if the asset is in Cache Storage serve it from there.
Otherwise — download and serve it (and cache it)
Build time
Register service worker and listen to its lifecycle events
(updates in particular)
Website/webapp
26. Inconvenient Truth #1Inconvenient Truth #1
The service workerThe service worker will not improvewill not improve the first-loadthe first-load
experienceexperience
On the first load, the user agentOn the first load, the user agent downloadsdownloads resourcesresources
from the Application Shell setfrom the Application Shell set twicetwice
In some cases, the service worker willIn some cases, the service worker will slow downslow down even even
return visitsreturn visits
28. Know your toolsetKnow your toolset
Service Worker APIService Worker API
Cache APICache API
IndexedDBIndexedDB
FetchFetch
Clients APIClients API
Broadcast Channel APIBroadcast Channel API
Push APIPush API
Notifications APINotifications API
Local StorageLocal Storage
Session StorageSession Storage
XMLHttpRequestXMLHttpRequest
DOMDOM
31. Chrome <6% of free space
Firefox <10% of free space
Safari <50MB
IE10 <250MB
Edge Dependent on volume size
https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/offline-for-pwa
Storage is not unlimitedStorage is not unlimited
if ('storage' in navigator && 'estimate' in navigator.storage) {
navigator.storage.estimate().then(({usage, quota}) => {
console.log(`Using ${usage} out of ${quota} bytes.`);
});
}
32. const appShellFilesToCache = [
'./styles.css',
...
'./styles.css'
]
https://www.chromestatus.com/feature/5622587912617984
Duplicate resources in addAll()Duplicate resources in addAll()
35. Caching fromCaching from other originsother origins
Get ready for opaqueGet ready for opaque
TIP #4TIP #4
36. Opaque responses limitationsOpaque responses limitations
TheThe statusstatus property of an opaque response is property of an opaque response is always setalways set
to 0to 0, regardless of whether the original request, regardless of whether the original request
succeeded or failedsucceeded or failed
The Cache API'sThe Cache API's add()add()//addAll()addAll() methods will both methods will both rejectreject
if the responsesif the responses resulting from any of the requests haveresulting from any of the requests have
a status code that isn't in the 2XX rangea status code that isn't in the 2XX range
https://stackoverflow.com/questions/39109789/what-limitations-apply-to-opaque-responses/39109790#39109790
39. Solution for no-corsSolution for no-cors
fetch(event.request).then( response => {
if (response.ok) {
let copy = response.clone();
caches.open('runtime').then( cache => {
cache.put(request, copy);
});
return response;
}
})
https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests
40. Solution for no-corsSolution for no-cors
fetch(event.request).then( response => {
if (response.ok || response.status === 0) {
let copy = response.clone();
caches.open('runtime').then( cache => {
cache.put(request, copy);
});
return response;
}
})
https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests
41. Possible issuesPossible issues
We do not know what we get as a response, so there isWe do not know what we get as a response, so there is
a chance to cachea chance to cache errors 404, 500errors 404, 500, etc., etc.
Each cached resource takesEach cached resource takes at least 7 MBat least 7 MB in Cachein Cache
StorageStorage
44. Following the updatesFollowing the updates
In the main script via theIn the main script via the registration statusregistration status of theof the
service workerservice worker
navigator.serviceWorker.register('sw-handmade.js')
.then(registration => {
if (registration.waiting) {
// Show "App was updated" prompt to reload
}
})
In the service worker. After the update happened weIn the service worker. After the update happened we
inform the app viainform the app via BroadcastChannel APIBroadcastChannel API oror
postMessagepostMessage
45. Inconvenient Truth #2Inconvenient Truth #2
The architecture of the Application ShellThe architecture of the Application Shell goes againstgoes against
the idea of the web about "always fresh"the idea of the web about "always fresh"
We can onlyWe can only slightly improveslightly improve the user experiencethe user experience
I'm a PWA and I'm always fresh. But what you see is the outdated version.
Click here to reload
46. Sometimes service workerSometimes service worker
boots up not immediatelyboots up not immediately
Don't waste this time!Don't waste this time!
TIP #6TIP #6
47. "Cold start" problem"Cold start" problem
https://developers.google.com/web/updates/2017/02/navigation-preload
SW Boot Navigation request
SW Boot
Navigation request
If the service worker isIf the service worker is unloaded from memoryunloaded from memory
AND the response of this request isAND the response of this request is not cachednot cached
AND the service worker includes aAND the service worker includes a fetch eventfetch event
49. addEventListener('fetch', event => {
event.respondWith(async function() {
// Best scenario: take if from the Cache Storage
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse;
// OK scenario: use navigation preload response
const response = await event.preloadResponse;
if (response) return response;
// Worst scenario: fetching from the network :(
return fetch(event.request);
}());
});
Using the preload resultUsing the preload result
50. Not only caching andNot only caching and
push notificationspush notifications
Use the full potential of theUse the full potential of the
service workerservice worker
TIP #7TIP #7
52. Load balancerLoad balancer
Intercept the requests to the resources andIntercept the requests to the resources and select theselect the
proper content providerproper content provider
To choose a server with theTo choose a server with the least loadleast load, to, to test newtest new
featuresfeatures, to do, to do A/B testingA/B testing
https://serviceworke.rs/load-balancer.html
55. App shellApp shell
Runtime cachingRuntime caching
Offline GAOffline GA
Replay failed requestsReplay failed requests
Broadcast updatesBroadcast updates
Build integrationsBuild integrations
Possibility toPossibility to extendextend your own serviceyour own service
worker instead of using generated oneworker instead of using generated one
https://workboxjs.org
56. https://webhint.io
npm install -g hint
hint https://airhorner.com
npm install -g lighthouse
lighthouse https://airhorner.com
https://github.com/GoogleChrome/lighthouse
57. Inconvenient Truth #3Inconvenient Truth #3
Even well-known, well-supported open sourceEven well-known, well-supported open source
librarieslibraries may contain bugsmay contain bugs
Even they do not always fast enough in following theEven they do not always fast enough in following the
specification updatesspecification updates
58. 1. Loaded ./index.html
2. Were redirected by 301 to ./ with
Content-Type: text/plain
3. Fetched and cached the content of
./ (which is text/html), but the
resulting Content-Type was taken
from the request from p.2
Update Workbox to 3.6.3Update Workbox to 3.6.3
Invalidate the existing cache by explicit namingInvalidate the existing cache by explicit naming
workbox.core.setCacheNameDetails({precache: 'new-name'});
WhatWhat WhyWhy
Recent caseRecent case
59. In case ofIn case of emergencyemergency
Implement a Kill SwitchImplement a Kill Switch
TIP #9TIP #9
60.
61. UnregisterUnregister service worker? service worker?
DeployDeploy fixed or no-opfixed or no-op service workerservice worker
Make sure that browser will not serve service workerMake sure that browser will not serve service worker
file(s) fromfile(s) from HTTP cacheHTTP cache
Rescue planRescue plan
63. Update service workerUpdate service worker
Cache-Control: no-cache
Assets from importScripts()Assets from importScripts()
Main service workerMain service worker
https://github.com/w3c/ServiceWorker/issues/893
Byte-difference check - addByte-difference check - add versioning via file contentversioning via file content
Spec was updated
Spec was updated
navigator.serviceWorker.register(`/sw.js?v=${VERSION}`);
Byte check doesn't work -Byte check doesn't work - add versioning via filename ofadd versioning via filename of
imported SW or main SWimported SW or main SW
65. importScripts() optimizationsimportScripts() optimizations
https://www.chromestatus.com/feature/5748516353736704
Well-known scripts are retrieved and boot up evenWell-known scripts are retrieved and boot up even
beforebefore importScripts() importScripts()
They are stored asThey are stored as V8 bytecodeV8 bytecode
IssueIssue
Calling importScripts () atCalling importScripts () at
anan arbitrary placearbitrary place in thein the
service workerservice worker
SolutionSolution
Now — only before reachingNow — only before reaching
installedinstalled state. The spec was state. The spec was
updated.updated.
67. Quiz time!Quiz time!
No. Spec clearly mentionsNo. Spec clearly mentions the same originthe same origin
Yes. There is an experimentalYes. There is an experimental Foreign FetchForeign Fetch
Yes. Maybe someYes. Maybe some other optionsother options......
Can service worker be registeredCan service worker be registered
without visiting the website? (fromwithout visiting the website? (from
another origin)another origin)
68. Payment handlerPayment handler
A helper for Payment Request API specifically for theA helper for Payment Request API specifically for the
web payment appsweb payment apps
Registers someRegisters some payment instrumentspayment instruments (card payments,(card payments,
crypto-currency payments, bank transfers, etc)crypto-currency payments, bank transfers, etc)
On payment request user agent computes aOn payment request user agent computes a list oflist of
candidate payment handlerscandidate payment handlers, comparing the payment, comparing the payment
methods accepted by the merchant with thosemethods accepted by the merchant with those
supported by registered payment handlerssupported by registered payment handlers
https://w3c.github.io/payment-handler/
69. self.addEventListener("paymentrequest", event => {
// Open the window with the payment method UI
event.openWindow('https://my.pay/checkout')
});
const swReg = await navigator.serviceWorker.register("/sw.js");
await swReg.paymentManager.instruments.set(
"My Pay's Method ID",
{
name: "My Pay's Method",
method: "https://my.pay/my-method",
}
);
main.js /main.js / payment apppayment app
sw.js /sw.js / payment apppayment app
70. Just-In-Time registrationJust-In-Time registration
The method (via url) is set as supportedMethods in the
PaymentRequest of the merchant's website and at the time of payment this
method is selected
The HEAD request to this url returns the 200 + Link: <address of the
Payment Method Manifest>, where the link to Web App Manifest
specified in the default_applications
In the method's Web App Manifest, there is a serviceworker section with
src and scope
https://developers.google.com/web/fundamentals/payments/payment-apps-developer-guide/web-payment-apps
Service worker from this addressService worker from this address will be registeredwill be registered!!
ItsIts paymentrequestpaymentrequest event will be called event will be called
Only then...Only then...
71. Background fetchBackground fetch
Fetches (requests & responses)Fetches (requests & responses) are aliveare alive after userafter user
closes all windows & worker to the origincloses all windows & worker to the origin
Browser/OS shows UI toBrowser/OS shows UI to indicate the progressindicate the progress of theof the
fetch, and allow the user tofetch, and allow the user to pause/abortpause/abort
Dealing with poor connectivityDealing with poor connectivity by pausing/resumingby pausing/resuming
the download/uploadthe download/upload
App has anApp has an access to the fetched resourcesaccess to the fetched resources and to the and to the
status/progress of the fetch status/progress of the fetch