Cross Platform Progressive Web Apps

Simon MacDonald

@macdonst

What is a Progressive Web App?

Progressive Web Apps use modern web capabilities to deliver an app-like user experience.

They evolve from pages in browser tabs to immersive, top-level apps, maintaining the web's low friction at every moment.

So they're just web pages?

Progressive

Works for every user, regardless of browser choice because it's built with progressive enhancement as a core tenet.

Responsive

Fits any form factor: desktop, mobile, tablet, or whatever is next.

Connectivity Independent

Enhanced with service workers to work offline or on low-quality networks.

© Google

App Like

Feels like an app, because the app shell model separates the application functionality from application content.

Shell

Content

Safe

Served via HTTPS to prevent snooping and to ensure content hasn't been tampered with

NOT!

Re-engagable

Makes re-engagement easy through features like push notifications.

Installable

Allows users to add apps they find most useful to their home screen without the hassle of an app store.

Linkable

Easily share the application via URL, does not require complex installation.

Discoverable

Is identifiable as an "application" thanks to W3C manifest and service worker registration scope, allowing search engines to find it.

// index.html
<link rel="manifest" href="/manifest.json">

// manifest.json
{
  "short_name": "AirHorner",
  "name": "Kinlan's AirHorner of Infamy",
  "icons": [
    {
      "src": "launcher-icon-1x.png",
      "type": "image/png",
      "sizes": "48x48"
    },
    {
      "src": "launcher-icon-2x.png",
      "type": "image/png",
      "sizes": "96x96"
    },
    {
      "src": "launcher-icon-4x.png",
      "type": "image/png",
      "sizes": "192x192"
    }
  ],
  "start_url": "index.html?launcher=true"
}

It all starts with

<link rel="manifest" href="manifest.json">

{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

manifest.json

{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

manifest.json

{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

manifest.json

{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

manifest.json

{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html?start=a2hs",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}
{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

manifest.json

{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

manifest.json

{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

manifest.json

{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

manifest.json

App Shell Architecture

© Google

Service Workers

A service worker is a script that your browser runs in the background, separate from a web page

Registration

Installation

Activation

Fetch

.3

Beta 3

index.html

{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

manifest.json

<meta 
 name="apple-mobile-web-app-title" 
 content="PWA Test">

Supported in iOS 11.3

index.html

{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

manifest.json

<meta 
 name="apple-mobile-web-app-capable" 
 content="yes">

Supported in iOS 11.3

index.html

{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

manifest.json

<meta name=
 "apple-mobile-web-app-status-bar-style" 
 content="black">

index.html

{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

manifest.json

<link 
 rel="apple-touch-icon" 
 href="img/icons/apple-touch-icon.png">

Still needed in iOS 11.3

index.html

{
  "short_name": "PWA Test",
  "name": "PWA Test",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "theme_color": "#2196F3",
  "description": "A sample PWA.",
  "icons": [
    {
      "src": "img/logo-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "img/logo-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

manifest.json

<link 
 rel="apple-touch-startup-image" 
 href="img/Default-Portrait.png">

Still needed in iOS 11.3,

no spashscreen support

  • Breaks file type in input tag
  • Breaks integration with camera
  • No support for getUserMedia
  •  

Workaround: Remove

<meta 
 name="apple-mobile-web-app-capable" 
 content="yes">

“To keep only the stored information that is useful to the user, WebKit will remove unused service worker registrations after a period of a few weeks. Caches that do not get opened after a few weeks will also be removed. Web Applications must be resilient to any individual cache, cache entry or service worker being removed.”


https://webkit.org/blog/8090/workers-at-your-service/

"Every time you get out of the PWA, you will lose the context, and when the user goes back, the PWA will load from scratch."

Weird Behaviour

cordova plugin add https://github.com/phonegap/phonegap-plugin-service-worker

 <preference name="ServiceWorker" value="service-worker.js" />
 <preference name="CacheCordovaAssets" value="false" />
cordova create myApp --template=<PATH TO ASSETS>

Create an App from your assets

Add the Service Worker plugin

Modify config.xml

cordova run ios

Run your app

npm install -g cordova

Install the Cordova CLI

You're Awesome

The End

Recommended Reading

Cross-Platform-PWA

By Simon MacDonald

Cross-Platform-PWA

ConFoo Montreal 2018

  • 3,708