Slide 1

Slide 1 text

@maciejtreder

Slide 2

Slide 2 text

@maciejtreder • Kraków, Poland • Over 10 years of experience in IT, • Senior Software Development Engineer at Akamai Technologies, • Articles author • Open Source contributor (founder of @ng-toolkit)

Slide 3

Slide 3 text

• Cambridge, Massachusetts • Content Delivery Network • Over 240 00 servers deployed in more then 120 countries • Serves 15%-30% of the Internet traffic

Slide 4

Slide 4 text

Progressive Web Applications Future of web development

Slide 5

Slide 5 text

ARPANET • 1969 first package sent

Slide 6

Slide 6 text

HTTP • 1969 first website launch

Slide 7

Slide 7 text

HTML • 1993 initial release • 1994 Netscape Navigator • 1995 Internet Explorer

Slide 8

Slide 8 text

1995: LAMP & JavaScript • Linux • Apache • MySQL • PHP

Slide 9

Slide 9 text

Cascading Style Sheets • Introduced in 1996

Slide 10

Slide 10 text

In the meantime…

Slide 11

Slide 11 text

1999 • Initial release of Wireless Application Protocol • Asynchronous JavaScript and XML (AJAX) appears

Slide 12

Slide 12 text

RWD • 2002 - first draft of the CSS 2

Slide 13

Slide 13 text

Single Page App • Everything on one page • Content loaded asynchronously (AJAX calls) • No ‘reload’ • Share-able links (# - strategy, later on HTML5 history API) Native-like experience

Slide 14

Slide 14 text

! Reliable ! Engaging ! Fast Progressive Web App

Slide 15

Slide 15 text

Site is served over HTTPS Pages are responsive on tablets & mobile devices Site works cross-browser Page transitions don't feel like they block on the network Each page has a URL First load fast even on 3G All app URLs load while offline Metadata provided for Add to Home screen PWA checklist

Slide 16

Slide 16 text

Service Worker

Slide 17

Slide 17 text

JavaScript Lifecycle let tick = 0; setInterval(() => console.log(tick++), 1000); 0, 1, 2, 3, 4 0, 1, 2, 3, 4 0, 1, 2, 3, 4

Slide 18

Slide 18 text

Service Worker • Executed in separate thread • Persistent background processing • Enabling new features

Slide 19

Slide 19 text

ServiceWorker Lifecycle let tick = 0; setInterval(() => console.log(tick++), 1000); 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/service-worker.js'); }); } service-worker.js index.html , 11, 12, 13, 14, 15, 16, 17, 18, 19, 20

Slide 20

Slide 20 text

How to start? https://developers.google.com/web/tools/workbox/ ng add @angular/pwa home-grown

Slide 21

Slide 21 text

ng add @angular/pwa • ngsw-config.json • manifest.json

Slide 22

Slide 22 text

ngsw-config.json • App information • What to cache • How to cache { “index”: “/index.html”, “appData”: { “name”: “app name”, “description”: “app description”}, “ ”: [{}], “ “: [{}] } assetGroups dataGroups

Slide 23

Slide 23 text

assetGroups • external & internal parts of application { “assetGroups”: [{ “name”: “images_and_fonts”, “installMode”: “lazy”, “updateMode”: “prefetch”, “resources”: { “files”: [“/assets/img/**”], “urls”: [“https://fonts.googleapis.com/**"] } }] }

Slide 24

Slide 24 text

ngsw-config.json • What to cache • How updates should be performed { “index”: “/index.html”, “appData”: { “name”: “app name”, “description”: “app description”}, “ ”: [{}], “ “: [{}] } assetGroups dataGroups

Slide 25

Slide 25 text

dataGroups • API calls { “dataGroups”: [{ “name”: “api_calls”, “urls”: [“https://my-app.com/api/**”], “cacheConfig”: { “maxSize”: 10, “maxAge”: “1d”, “timeout”: “4s”, “strategy”: “freshness” } }] }

Slide 26

Slide 26 text

How to start? https://developers.google.com/web/tools/workbox/ ng add @angular/pwa home-grown

Slide 27

Slide 27 text

Workbox importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js'); if (workbox) { console.log(`Yay! Workbox is loaded `); } else { console.log(`Boo! Workbox didn't load `); } if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/service-worker.js'); }); } service-worker.js index.html

Slide 28

Slide 28 text

Workbox-CLI importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js'); if (workbox) { workbox.precaching.precacheAndRoute([]); } else { console.log(`Boo! Workbox didn't load `); } www/service-worker.js -> sw-dev.js npm install workbox-cli —-global workbox wizard

Slide 29

Slide 29 text

Workbox-CLI module.exports = { "globDirectory": "www/", "globPatterns": [ "**/*.{html,js}" ], "swDest": "www/service-worker.js", "swSrc": "sw-dev.js" }; workbox-config.js (created by workbox wizard) > workbox injectManifest Using configuration from /Users/mtreder/something/workbox-config.js. The service worker was written to www/service-worker.js 2 files will be precached, totalling 808 B.

Slide 30

Slide 30 text

Caching workbox.precaching.precacheAndRoute([ { "url": "index.html", "revision": "40f4848158e6e82701c4e9904c981449" }, { "url": "script.js", "revision": "85655e0fbf9a9093747877ee5270618e" } ]); workbox.precaching.precacheAndRoute([]); workbox injectManifest

Slide 31

Slide 31 text

Requests caching cache first network first network only cache only

Slide 32

Slide 32 text

workbox.routing.registerRoute( new RegExp('/images/avatars/'), new workbox.strategies.StaleWhileRevalidate() ); workbox.routing.registerRoute( new RegExp('/styles/'), new workbox.strategies.CacheFirst() ); Requests caching

Slide 33

Slide 33 text

workbox.routing.registerRoute( new RegExp('/admin/'), new workbox.strategies.NetworkOnly() ); workbox.routing.registerRoute( new RegExp('/app/v2/'), new workbox.strategies.CacheOnly() ); Requests caching

Slide 34

Slide 34 text

Service Worker ! Content caching ! Push notifications ! Background synchronization

Slide 35

Slide 35 text

Push notifications your server push service

Slide 36

Slide 36 text

Obtain Keys https://web-push-codelab.glitch.me

Slide 37

Slide 37 text

Use PushManager API Use importScripts section in your service-worker dev version importScripts: ['push-notifications.abcd1234.js'] https://developer.mozilla.org/en-US/docs/Web/API/Push_API

Slide 38

Slide 38 text

https://serviceworke.rs

Slide 39

Slide 39 text

import { SwPush } from '@angular/service-worker'; export class PushComponent implements OnInit { constructor(private swPush: SwPush) {} public ngOnInit(): void { this.swPush.requestSubscription({serverPublicKey: ‘key_obtained_by_push_companion'}) .then((pushSubscription: ) => { // save key on the server }); } } Register client PushSubscription

Slide 40

Slide 40 text

interface PushSubscription { readonly expirationTime: number | null; readonly options: PushSubscriptionOptions; getKey(name: PushEncryptionKeyName): ArrayBuffer | null; toJSON(): PushSubscriptionJSON; unsubscribe(): Promise; } PushSubscription readonly endpoint: string;

Slide 41

Slide 41 text

Engage Your Customers { title: “my notification”, content: “content” } https://push-service.com/ subscription_endpoint https://push-service.com/ subscription_endpoint your server push service

Slide 42

Slide 42 text

Engage Your Customers • https://github.com/web-push-libs/webpush-java
 Pure Java client • https://github.com/maciejtreder/aws-sns-webpush
 AWS Lambda + AWS SNS

Slide 43

Slide 43 text

Service Worker ! Content caching ! Push notifications ! Background synchronization

Slide 44

Slide 44 text

Background synchronization -Fi Wi

Slide 45

Slide 45 text

Background synchronization Workbox Background Sync Home-grown solution with the HttpInterceptor

Slide 46

Slide 46 text

Background synchronization const bgSyncPlugin = new workbox.backgroundSync.Plugin('myQueueName', { maxRetentionTime: 24 * 60 // Retry for max of 24 Hours }); workbox.routing.registerRoute( /\/api\/.*\/*.json/, workbox.strategies.networkOnly({ plugins: [bgSyncPlugin] }), 'POST' );

Slide 47

Slide 47 text

Service Worker ! ! Push notifications ! Background synchronization Content caching

Slide 48

Slide 48 text

ng add @angular/pwa • ngsw-config.json • manifest.json

Slide 49

Slide 49 text

manifest.json { "name": "Akamai Affinity", "short_name": "Affinity", "theme_color": "#f08b00", "background_color": "#363636", "display": "standalone", "scope": "/", "start_url": "/", "icons": [ { "src": "/assets/images/icons/favicon-32.png", "sizes": "32x32", "type": "image/png" } ] }

Slide 50

Slide 50 text

PWABuilder.com

Slide 51

Slide 51 text

• Server-Side Rendering • “Non-updatable” application Pitfalls

Slide 52

Slide 52 text

Server-Side Rendering GET / GET /anotherPage

Slide 53

Slide 53 text

Server-Side Rendering GET / GET /anotherPage

Slide 54

Slide 54 text

Server-Side Rendering • Problem: 
 Lack of the service-worker (navigator) in the NodeJS environment • Solutions: • Provide mock of ServiceWorkerModule in the server entry module • ng add @ng-toolkit/pwa

Slide 55

Slide 55 text

Pitfalls • Server-Side Rendering • “Non-updatable” application

Slide 56

Slide 56 text

“Non-updatable” application GET / GET / API calls

Slide 57

Slide 57 text

“Non-updatable” application • Problem: 
 Once installed app is “never” updated • Solutions: • Implement update mechanism using SwUpdate service • Use workbox-window SwUpdate

Slide 58

Slide 58 text

SwUpdate • @angular/service-worker • isEnabled • available - emits an event when new version of an app is available • checkForUpdate() - indicates check for update & updates application

Slide 59

Slide 59 text

SwUpdate public ngOnInit(): void { if (!isPlatformBrowser(this.platformId)) { return; } if (this.swUpdate.isEnabled) { this.swUpdate.available.subscribe((evt) => { setTimeout(() => {this.windowRef.nativeWindow.location.reload(true); }, 1); }); this.swUpdate.checkForUpdate().then(() => { // noop }).catch((err) => { console.error('error when checking for update', err); }); } }

Slide 60

Slide 60 text

workbox-window import { Workbox } from 'workbox-window'; if ('serviceWorker' in navigator) { const wb = new Workbox('service-worker.js'); wb.addEventListener('installed', event => { if (event.isUpdate) { if (confirm(`New content is available!. Click OK to refresh`)) { window.location.reload(); } } }); }

Slide 61

Slide 61 text

Tools

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

Browser support 85%

Slide 65

Slide 65 text

Safari Push • Apple developer membership required • Works only on Safari Desktop (lack of iOS support) • Written tutorial: https://apple.co/2A80fB4 • Video tutorial: https://apple.co/2S11wkJ

Slide 66

Slide 66 text

Add to desktop (iOS)

Slide 67

Slide 67 text

Add to desktop (iOS)

Slide 68

Slide 68 text

Add to desktop (iOS)

Slide 69

Slide 69 text

PWACompat • https://github.com/GoogleChromeLabs/pwacompat • Analyze manifest.json • Brings necessary html tags ‘on the fly’ (like splash screen for mobile safari or pinned sites features for IE/Edge)

Slide 70

Slide 70 text

Where we are? Offline capabilities Push notifications Add to homescreen

Slide 71

Slide 71 text

• 43% increase in session • 100% increase in engagement • 2.5 sec loading time • 9x increase in carts recovered

Slide 72

Slide 72 text

• 100% increase in placed orders

Slide 73

Slide 73 text

• 65% increase in pages per session • 75% increase in tweets sent • 20% decrease in bounce rate

Slide 74

Slide 74 text

• 103% increase of active users • 296% increase in session length • 406% increase in number of pins • 44% increase in ad revenue

Slide 75

Slide 75 text

• 50kB gzipped - loads in less than 3 second on 2G

Slide 76

Slide 76 text

• Cut load time from 11.91 to 4.69 seconds • 90% smaller than native Android app

Slide 77

Slide 77 text

Google found that Progressive Web App install banners convert 5-6 times more than native install banners

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

secure responsive fast linkable fresh installable app-like re-engageable cross device network independent

Slide 83

Slide 83 text

@maciejtreder