PWAs allow websites to behave like native applications through the use of technologies like web app manifests, service workers, and progressive enhancement. A web app manifest allows sites to define metadata like display properties, icons, and app functionality. Service workers act as proxies between web apps and the network to enable features like offline access and push notifications. PWAs aim to provide reliable, fast, and engaging experiences for users.
4. Our team mission
Move the Web Platform forward
➫ Make the web run super fast on Intel hardware
➫ Keep the open web relevant and make sure Intel has influence on the direction
➫ Align the web with Intel strategies, and take advantage of the web
➫ Identify industry trends and respond
➫ Ensure good collaboration with our partners such as Google and Apple
5. Today we will talk about something that will keep
the web relevant in years to come
And which might make Windows 10 and
Chromebooks much more relevant as well.
6. ➫ Intro to PWAs, and why it matters
➫ Manifest
➫ The Service Worker
➫ Ensuring a fast load time
➫ Push notifications
➫ Background sync
➫ Web Share
➫ Sign in and credential Management
Outline
9. The idea is that any site can over time with proper engagement,
can progressively turn into a more powerful and integrated
experience
Or people can manually opt in to that experience by installing
manually from eg. a store
The basic premise
10. Progressive Web Apps offers the best of the modern web
They work reliable, they load fast and are responsive
The basic premise
12. Progressive Web Apps and often abbreviated as PWAs
A community logo exists:
https://medium.com/samsung-internet-dev/we-now-have-a-community-approved-progressive-web-apps-logo-823f212f57c9
The basic premise
13. People the web because it is ephemeral
● Sites' resources (cache etc) gets removed when not visited regularly, depending on cache pressure
● No need to worry whether apps will update in the background even when you never use them, or just
fill up your storage
The basic idea
14. You visit many sites a month, most never again
But some you visit again and again, you engage with them
The basic idea
15. You can manually add sites to home screen
via a menu
But when you reach an engagement threshold,
sites can prompt you to install them*
* On Chrome, Opera, Firefox (mobile) and Samsung Internet so far
The basic idea
16. The added app now appears on home screen and in launcher
The basic idea
17. When you launch, the app starts as a separate app,
indistinguible from any other native apps
The basic idea
19. The best of the web!
Great reach, low friction
safe
ephemeral
(deep) linkable
indexable
composable
20. PWA are applications created with web technology, who look
and act like native applications, but are being run by the build
in browser.
Summary
21. PWAs can be installed in different ways, for instance via an
app store or from the browser when visiting a site.
The behavior depends on the OS and browser.
Summary
22. PWAs are just modern websites, no special API access, but
they do get special treatment
➫ Browser chrome is configurable
➫ More integrated with host OS (optional): orientation lock, icon in launcher, no chrome
➫ Additional rights due to engagement:
○ autoplay video with sound
○ ask for persistent storage
○ more to come
➫ Some features like being a share target can only work with installed apps
Summary
24. {
"short_name": "Flipkart",
"name": "Flipkart Lite",
"start_url": "/?start_url=homescreen",
"display": "standalone",
"orientation": "portrait",
"icons": [{
"src": "icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
}]
}
M A N I
F E S T_
101
25. ➫ W3C working draft standard
➫ Allows defining metadata associated with a web app
➫ https://www.w3.org/TR/appmanifest/
➫ Useful for packaging
Web App Manifest
26. Add site to home screen
➫ Has a unique icon and name in
the launcher / home screen
➫ Can display splash-screen to
the user when loading
➫ Hides the browser UI (if
desired) to feel like a native
app
29. ➫ start_url and scope
➫ serviceworker
➫ dir (direction) and lang (language)
➫ name, short_name and description
➫ icons, screenshots image resources
➫ categories and iarc_rating_id
➫ display and orientation
➫ theme_color and background_color
➫ related_applications and prefer_related_applications
Available values
30. ➫ start_url and scope Entry point and URLs which are considered part of the app
➫ serviceworker
➫ dir (direction) and lang (language)
➫ name, short_name and description
➫ icons, screenshots image resources
➫ categories and iarc_rating_id
➫ display and orientation
➫ theme_color and background_color
➫ related_applications and prefer_related_applications
Available values
31. ➫ start_url and scope
➫ serviceworker Service Worker associated with the app, especially for stores
➫ dir (direction) and lang (language)
➫ name, short_name and description
➫ icons, screenshots image resources
➫ categories and iarc_rating_id
➫ display and orientation
➫ theme_color and background_color
➫ related_applications and prefer_related_applications
Available values
32. ➫ start_url and scope
➫ serviceworker
➫ dir (direction) and lang (language) Metadata for text and iconography
➫ name, short_name and description
➫ icons, screenshots image resources
➫ categories and iarc_rating_id
➫ display and orientation
➫ theme_color and background_color
➫ related_applications and prefer_related_applications
Available values
33. ➫ start_url and scope
➫ serviceworker
➫ dir (direction) and lang (language)
➫ name, short_name and description Info for app launcher, install dialogs
➫ icons, screenshots image resources
➫ categories and iarc_rating_id
➫ display and orientation
➫ theme_color and background_color
➫ related_applications and prefer_related_applications
Available values
34. ➫ start_url and scope
➫ serviceworker
➫ dir (direction) and lang (language)
➫ name, short_name and description
➫ icons, screenshots image resources Icons and screens for app launcher, store
➫ categories and iarc_rating_id
➫ display and orientation
➫ theme_color and background_color
➫ related_applications and prefer_related_applications
Available values
35. ➫ start_url and scope
➫ serviceworker
➫ dir (direction) and lang (language)
➫ name, short_name and description
➫ icons, screenshots image resources
➫ categories and iarc_rating_id Store related information, like universal age rating
➫ display and orientation
➫ theme_color and background_color
➫ related_applications and prefer_related_applications
Available values
36. ➫ start_url and scope
➫ serviceworker
➫ dir (direction) and lang (language)
➫ name, short_name and description
➫ icons, screenshots image resources
➫ categories and iarc_rating_id
➫ display and orientation Display style (eg. fullscreen) and orientation lock
➫ theme_color and background_color
➫ related_applications and prefer_related_applications
Available values
37. ➫ start_url and scope
➫ serviceworker
➫ dir (direction) and lang (language)
➫ name, short_name and description
➫ icons, screenshots image resources
➫ categories and iarc_rating_id
➫ display and orientation
➫ theme_color and background_color UI color like for splashscreen & app switcher
➫ related_applications and prefer_related_applications
Available values
38. ➫ start_url and scope
➫ serviceworker
➫ dir (direction) and lang (language)
➫ name, short_name and description
➫ icons, screenshots image resources
➫ categories and iarc_rating_id
➫ display and orientation
➫ theme_color and background_color
➫ related_applications and prefer_related_applications Refer to store apps
Available values
41. ➫ Manifest can be extended by other specs to give more
functionality - check Web Share Target later
➫ The manifest entries and values are integrated in the new
Web Packaging spec:
○ https://tools.ietf.org/id/draft-yasskin-dispatch-web-packaging-00.html#rfc.section.2.4
○ https://amphtml.wordpress.com/2018/01/09/improving-urls-for-amp-pages/
○ The parsed manifest consists of the set of signing-certificates and the manifest CBOR item. The items in
manifest[“metadata”] SHOULD be interpreted as described in the [appmanifest] specification.
Web App Manifest
44. ➫ The Promise object is used for async calculations
➫ Represents a values which is available now, in the future or never
➫ Promise.resolve() returns a resolved promise, Promise.reject() a failed promise
➫ Much more readable code than callback hell
new Promise( /* executor */ function(resolve, reject) { ... }
);
Async code with promises
45. pending: initial state, not fulfilled or rejected
fulfilled: means that the operation is completed with success
rejected: means that the operation failed
Promises can be chained:
The three states
46. Creation and usage of promises
const p3 = new Promise(function (resolve, reject) {
if (/* condition */ ) {
resolve(/* value */ ); // fulfilled successfully
}
else {
reject(/* reason */ ); // error, rejected
}
});
function after(time) {
if (typeof time !== "number" )
return Promise.reject("Not a number!" ); // static method
return new Promise(resolve => {
setTimeout (_ => resolve(), time);
// resolve/reject can be async
});
}
after(1000).then(_ => console.log("hi 1 sec later" ))
// an immediately resolved promise
const p2 = Promise.resolve("foo");
// can get it after the fact, unlike events
p2.then(res => console.log(res));
const p = new Promise(function (resolve, reject) {
setTimeout (() => resolve(4), 2000);
});
// handler can't change promise, just value
p.then(res => {
res += 2;
console.log(res);
});
// still gets 4
p.then(res => console.log(res));
Examples
47. .then() and .catch()
// Throw is the same as calling reject
let p1 = new Promise((resolve, reject) => {
if (true)
throw new Error("rejected!");
else
resolve(4);
});
// A .catch() at the end catches all rejections
p1.then((val) => val + 2)
.then((val) => console.log("got", val))
.catch((err) => console.log("error: ",
err.message));
// => error: rejected!
p.then(val => console.log("fulfilled:", val),
err => console.log("rejected: ", err));
// The following two gives the same result:
p.then(val => console.log("fulfilled:", val))
.catch(err => console.log("rejected:", err));
p.then(val => console.log("fulfilled:", val))
.then(null, err => console.log("rejected:", err));
Examples
48. ➫ Promise.all([p1, p2, ...]) -> resolves if all individual promises resolve and first at
that point
➫ Promise.race([p1, p2, ...]) -> resolves when the first promise resolves
Notice that .then() always returns a promise!
Async with promises
const p = new Promise.resolve().then(_ => console.log("hi")).then(_ => …)
49. Pros
➫ Write async code in a synchronous way, without much indentation and with exception handling in one
place
➫ Helps unify async APIs and wrap existing APIs with promises
➫ Guarantees that there will be no race-conditions and that future values represented by the promise are
immutable (which is not the case with callbacks and events)
"Cons"
➫ Promises cannot be cancelled
➫ If you don't handle their rejects/exceptions, they get swallowed
➫ Multiple .then() mixes with lots of arrow functions can be hard to read - more about async/await later
Summary
50. ➫ Easier way to work with promises
➫ Functions can be marked "async" and then you can "await" on functions
returning promises
➫ Doesn't work top-level as you cannot mark it async
Async / await
51. Without and with async / await
function logFetch (url) {
return fetch(url)
.then(response => response .text())
.then(text => {
console.log(text);
}).catch(err => {
console.error('fetch failed' , err);
});
}
async function logFetch (url) {
try {
const response = await fetch(url);
console.log(await response .text());
}
catch (err) {
console.log('fetch failed' , err);
}
}
Examples
More examples
// wait ms milliseconds
function wait(ms) {
return new Promise(r => setTimeout (r, ms));
}
async function hello() {
await wait(500);
return 'world';
}
// Careful! Avoid going too sequential
async function parallel () {
const wait1 = wait(500); // Start a 500ms timer asynchronously…
const wait2 = wait(500); // …meaning this timer happens in
parallel.
await wait1; // Wait 500ms for the first timer…
await wait2; // …by which time this timer has already finished.
return "done!";
}
52. async function f() {
return 1;
}
async means :
➫ An async function always returns a promise.
➫ If the code has return <non-promise> in it, then automatically wraps it
into a resolved promise with that value. (the same happens in
promise chains with .then())
// fails!
let value = await f;
await means :
➫ JavaScript wait until that promise settles and returns its result
➫ Doesn’t work in top level code as stated before
More about async / await
53. Prerequisite: fetch
fetch(url).then(r => r.json())
.then(data => console.log(data))
.catch(e => console.log("Booo"))
Fetch is:
➫ a massive improvement on XMLHttpRequest
➫ Support request options (e.g. CORS, headers)
➫ Can send credentials together with the request
54. Proxy between web apps and the network (when available)
Web
Browser
Service
Worker
Remote
Server
/api /api
55. Service Workers are flexible!
➫ They are a specialized web worker
➫ Programmable!
➫ Choose your own caching mechanism
➫ Even different one depending on URL!
56. Service Workers work on second load
➫ Progressive enhancement
➫ Site must work without
➫ Provides an update mechanism
57. Registering a Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ',
registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
58. Install a Service Worker
self.addEventListener('install', function(event) {
// Perform install steps
});
Inside the handler you need to:
➫ Open a cache
➫ Cache the files
➫ Confirm whether all the required assets are cached or not
62. Ideal for
➫ CSS, Images, Fonts, JS
➫ Mandatory stuff to make your site
functional
On install
63. Ideal for
➫ Clean up and migration
➫ If you have a previous SW, good time
to cleanup old caches, db…
➫ Keep the code lean
On activate
64. Ideal for
➫ If the whole site can't be taken offline, you may allow the user to select the content
they want available offline (e.g. video, article…)
➫ Give the user “Read later”, “Save for offline”
➫ Fetch what you need and put in the cache
As a result of user interaction
65. Ideal for
➫ Frequently updating resources such as a user's inbox, or article contents.
➫ If a request doesn't match anything in the cache, get it from the network, send it to
the page and add it to the cache at the same time.
As a result of network response
66. Ideal for
➫ Frequently updating resources where having the very latest version is non-essential
○ Eg. avatars can fall into this category
➫ If there's a cached version available, use it, but fetch an update for next time
Stale-while-revalidate
67. Ideal for
➫ Content relating to the notification
➫ Update cache here before the user
clicks to provide a great offline
experience
As a result of a push message
68. Ideal for
➫ Non-urgent updates such as social timelines or news articles.
➫ Cache when your SW is woken up by the sync event
As part of background sync
70. ➫ Lots of data down the wire
➫ Multiple entries to your app (main page, deep link, embedding in social media)
➫ Not all resources are needed always (per view, per language, per log in)
➫ Many small requests are bad for performance
➫ Some resources are more expensive byte-wise (ie JS has a high parsing cost)
The problem
71. ➫ Optimize images - https://images.guide/
➫ Know what libraries you use and why #usetheplatform
○ Newer libraries like preact and lit-html-(element) are quite tiny
○ Often the platform does what you need, and if not, tiny polyfills often exist
➫ Compress and tree-shake your deployment code
The problem
72. ➫ Push critical resources for the initial URL route
➫ Render the initial route
➫ Pre-cache remaining routes.
➫ Lazy-load and create remaining routes on demand.
Focus on minimum time to interactive and maximum caching
PRPL Pattern (Pillars)
73. export function sayName(name) {
console.log(`Hello ${name}`);
}
➫ Separate code into modules
➫ Import and export symbols
➫ Support for sync and async loading
➫ Improves code organization
➫ Great for external dependencies
mymodule.js
import { sayName } from './mymodule.js';
sayName('Sam');
// Hello Sam
app.js
ES Modules
74. export function sayName(name) {
console.log(`Hello ${name}`);
}
➫ Load on demand
➫ Good for lazy loading
mymodule.js
<script type="module">
async function greet() {
const myModule = await import('./mymodule.js');
myModule.sayName('Sam');
}
greet(); // Hello Sam
</script>
app.js
ES Modules - dynamic loading
75. ➫ ES modules work for JavaScript only
➫ You can distribute HTML and CSS as JavaScript!
○ Strings and string templates in JS bypass the JS parser (and its slowness)
○ HTML and CSS is represented as strings anyway, so not a problem
○ JSX (as used by React) is converted to function calls, so slower than text
○ New approaches like lit-html and hyperHTML use string template literals to do the same as
JSX and still be as fast as HTML/CSS (except the optional JS parts)
ES Modules
80. ➫ Bundles code together
➫ Can hook out to other tools and analyze code to eliminate dead code
➫ Often does code-splitting into separate JS files
➫ Often requires a lot of configuration
○ Entry points, chunking etc
Bundlers
81. ➫ Different bundlers today allows to analyze code and do dead code elimination
➫ By providing entry points* tools can automatically split code into modules and even
identify common chunks
➫ Some downsides
○ As things are up front, it is hard to adjust the splitting when things change
○ Re-chunking means throwing away code that didn't change from browser cache
➫ On the fly splitting could be better, but requires the dev to modularize the code
○ Good use-case for web components
○ Be aware with tree-shaking
○ You might tree-shake libraries and hash encode them, but components should be small and not
tree-shaken
*www.mysite.org vs www.mysite.org/profile etc
Code splitting and tree-shaking
82. ➫ There is a protocol overhead for each request compared to a single
concatenated file
➫ The compression of the single large file is better than many small files
➫ Servers are slower serving many small files than a single large file
Realization: Servers are faster at sending few, large files
83. Server
Request index.html
Receive index.html
Request style.css
Receive style.css
Request index.html
Receive index.html
Receive style.css
Receive app.js
HTTP 1.x HTTP 2.0
➫ Server Push lets the server bundle and send assets alongside a request
○ Requires server logic
○ Allows the server to do on-demand code splitting/bundling with ES modules
○ Doesn't require everything to be distributed like JS
➫ Better caching in the browser
1
2
3
4
1
2
HTTP 2.0 Server Push
Browser Browser Server
84. ➫ Entrypoint needs to be very small
➫ Shell or app-shell includes the top-level app logic, router, and so on
➫ Lazily loaded fragments of the app (e.g. additional views, menus…)
index.html
app.js
view1.js view2.js
view1 dependencies shared dependencies view2 dependencies
Shell
Entrypoint
Fragment
Dependency tree
85. Shell bundle Fragment bundle
index.html
app.js
view1.js view2.js
view1 dependencies shared dependencies view2 dependencies
Shell
Entrypoint
Fragment
Bundled build
Fragment bundle
86. ➫ Rely on CSS, HTML and ES modules
➫ Load on demand
➫ Create your own web components
➫ Share resources with hashes in names because of caches
➫ Have the server and service worker work together
➫ Do code splitting and bunding on demand
○ unbundled at received so better for caching
The dream
87. https://github.com/Polymer/prpl-server-node
➫ Does exactly all of this
➫ Originally written for Polymer, and HTML imports (now dead standard)
➫ Works with Service Worker and ES Modules
➫ Works as a Node server or via Cloud Functions
➫ Still not battle tested enough, gone through changes due to deprecation of
HTML modules
PRPL Server Node
98. ➫ Avoid asking early unless
necessary (chat, mail apps)
➫ Double permission pattern:
Allows the user to decide
without permanently blocking
your site
When to prompt?
103. const subscriptionObject = {
endpoint: pushSubscription.endpoint,
keys: {
p256dh: pushSubscription.getKeys('p256dh'),
auth: pushSubscription.getKeys('auth')
}
};
Send subscription to server
104. function sendSubscriptionToBackEnd (subscription ) {
return fetch('/api/save-subscription/' , {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify(subscription )
})
.then(function(response) {
if (!response.ok) {
throw new Error('Bad status code from server.' );
}
return response.json();
})
.then(function(responseData ) {
if (!(responseData .data && responseData .data.success)) {
throw new Error('Bad response from server.' );
}
});
}
Send subscription to server
105. app.post('/api/save-subscription/' , function (req, res) {
if (!isValidSaveRequest (req, res)) {
return;
}
return saveSubscriptionToDatabase (req.body)
.then(function(subscriptionId ) {
res.setHeader('Content-Type' , 'application/json' );
res.send(JSON.stringify({ data: { success: true } }));
})
.catch(function(err) {
res.status(500);
res.setHeader('Content-Type' , 'application/json' );
res.send(JSON.stringify({
error: {
id: 'unable-to-save-subscription' ,
message: `The subscription was received but
we were unable to save it to our database.`
}
}));
});
});
On the server
107. ➫ Make a POST request to a push notification service (e.g. APNS, Firebase Cloud
Messaging)
➫ Use Voluntary Application Server Identification (VAPID)
➫ Encrypt the data to pass to the application
➫ Send!
- Node.JS server -> NodeJS Web Push Library
- Cloud functions (e.g. Firebase)
Sending push notifications - Prerequisite
112. self.addEventListener('push', function(event) {
if (event.data) {
console.log('This push event has data: ', event.data.text());
} else {
console.log('This push event has no data.');
}
});
➫ Browser doesn’t need to be opened, your SW will be woken up
➫ You can get the notification data with event.data.json() or event.data.text()
Inside your Service Worker
113. self.addEventListener('push', function(event) {
const promiseChain = self.registration.showNotification('Hello, World.');
event.waitUntil(promiseChain);
});
➫ showNotification() returns a promise that resolve when notification is displayed
➫ Use waitUntil to tell the browser to keep the SW running until the promise has resolved
Show your notification
114. <ServiceWorkerRegistration>.showNotification(<title>, <options>);
{
"//": "Visual Options",
"body": "<String>",
"icon": "<URL String>",
"image": "<URL String>",
"badge": "<URL String>",
"vibrate": "<Array of Integers>",
"sound": "<URL String>",
"dir": "<String of 'auto' | 'ltr' | 'rtl'>",
...
➫ Title is a string
➫ Options can be any of the following:
Customize your notification
117. self.addEventListener('notificationclick', function(event) {
const clickedNotification = event.notification;
clickedNotification.close();
// Do something as the result of the notification click
const promiseChain = doSomething();
event.waitUntil(promiseChain);
});
➫ Notification that was clicked can be access with event.notification
➫ Remember to use waitUntil while your code is busy
Handling user interactions with the notification
122. What is background sync?
➫ A way to not rely on having a stable internet
connection
➫ A way to defer network related operations to
when the connection is available
➫ Depends on Service Worker
➫ Can operate even when application is closed
124. self.addEventListener('sync', function(event) {
if (event.tag == 'outbox') {
event.waitUntil(sendEverythingInTheOutbox());
}
});
➫ sync will fire when the user agent believes the user has connectivity.
➫ Use waitUntil to tell that the sync event is ongoing to keep the service worker
alive if possible
Respond a sync
126. Web Share API
➫ Super simple API allowing to share text (a text string) and/or a URL
➫ Allows setting a title as well, but it might be ignored by the client
➫ https://wicg.github.io/web-share/
127. if (navigator.share) {
navigator.share({
title: 'Web Fundamentals',
text: 'Check out Web Fundamentals',
url: 'https://developers.google.com/web',
})
.then(_ => console.log('Successful share'))
.catch(err => console.log('Error:', err));
}
➫ Invokes the native share dialog so that the
user can select who to share with
➫ Only work on HTTPS, following a user
action (e.g. click)
Using the Web Share API
128. Web Share Target API
➫ Make the PWA become a share target
➫ Application has to be added by user to system
○ eg. add to homescreen / store install
➫ https://wicg.github.io/web-share-target/
➫ Extension to Web App Manifest
130. What is the sign in and credential management API?
➫ Removes friction from sign-in flows - Users can be automatically signed back
➫ Allows one tap sign in with account chooser - Users can choose an account in a
native account chooser.
➫ Stores credentials - Your application can store either a username and password
combination or even federated account details. These credentials can be synced
across devices by the browser.
139. Google is a big supporter
➫ Very good support on Android
➫ Working on desktop + Chrome OS support
➫ Working on extending standards and matching Electron
features on desktop
140. PWAs on Android
➫ Requires HTTPS, Offline experience, manifest
➫ Engagement criteria
➫ Add to homescreen dialog
➫ Installs generated Web APK from Google server
141. PWAs in the Play Store?
➫ Not directly supported
➫ New Trusted Web Activity
○ Abide to Play store rules (payment etc)
○ Needs a file on server linking to app to show relationship
○ Runs in Chrome (not WebView) like Custom Tabs
○ Can be extended in Java with a onmessage/postmessage API
146. Early preview of Windows support
https://mobile.twitter.com/kirupa/status/959175077836111872/video/1
147. Microsoft PWA support is strategic
➫ Deeply integrated into Windows
➫ Web Assembly allows native code to run at near native
speed on the web
➫ PWAs in store have access to all WinRT APIs
○ Polyfills will be available for some not-implemented APIs like Web
Bluetooth