PWAs

The Store-free, Installable Native App

(Progressive Web Apps)

slides.com/brianrichins/pwa/live

Photos courtesy of Unsplash

Very stock, much exciting

Sorry, dinosaurs

*not under 'dinosaurs' on Unsplash

  • Benefits and Features

  • Browser / OS Support

  • Notable Adopters

  • Technical Requirements

  • Coding Demo

Outline

Benefits and Features

Quick History

  • Term Coined in 2015

  • HTML, CSS, JS

  • A combination of existing and emerging tools and standards

  • PWAs are websites - with "app-like characteristics"

"App-like" Characteristics

  • Progressive Enhancement

  • Responsive Layout

  • Offline functionality

  • Full / Splash Screen

  • App-style UI

  • Fresh content

  • Secure

  • Discoverable

  • Engagable (Push)

  • Installable

  • Linkable

Benefits

  • Improved Performance

  • Faster Loading

  • Less Data Use

  • SEO

  • Reliability

  • Cross Platform

  • User Retention

  • Cheaper to Build

Browser Support

Users Are Everywhere

PWAs install natively on:

Mobile (50%)

  • iOS

  • Android

  • Samsung
    ​Internet

  • UC Browser

Desktop (36%)

  • Windows

  • Mac

  • Linux

Smartphones: 700 Million
Total Web: 3,700 Million

Top 10 Screen Sizes - July 2019

1 640x360 16.18%
2 1920x1080 11.24%
3 1366x768 9.68%
4 1024x768 6.46%
5 667x375 5.69%
6 800x600 3.36%
7 720x360 3.27%
8 760x360 3.10%
9 1440x900 2.71%
10 736x414 2.30%

Native Experience - iOS

  • Add to Homescreen

  • Removes Browser UI

  • No sharing tabs

  • No 'install' events

  • No derived browsers

Native Experience - Android

  • Install prompt

  • Removes Browser UI

  • No sharing tabs

  • 'Install' events

    • beforeinstallprompt
    • prompt
    • appinstalled
  • OS / Store presence

    • WebAPK

Native Experience - Windows

(And Linux)

Native Experience - Windows

Native Experience - Windows

Native Experience - MS Office

Native Experience - macOS

Normalized Experience

  • Several libraries exist for normalizing
    and showing prompts (e.g. iOS)

  • Custom prompt / button text

  • Align to browser control

love2dev.com/pwa/
add-to-homescreen-library/

Notable Adopters

Twitter

  • 75% increase in Tweets sent

 

  • 20% decrease in bounce rate

 

  • 65% increase in pages per session

Uber

  • Installable on 2G devices

 

  • 3 second load time on 2G

 

  • Core app of only 50kB

Starbucks

  • 99.84% smaller than iOS app

  • 2x number of web users who place orders each day

  • Desktop users now ordering at about the same rate as mobile users

AliExpress (China)

  • 104% increase in conversions for new users

  • 2x more pages visited per session

  • 74% increase in time spent per session

Flipkart (India)

  • 70% increase in conversions

  • 40% higher
    re-engagement

  • 3x more time spent on site

  • 3x lower data usage

OLX (Global Classifieds)

  • 23% decrease in time taken for a page to be interactive

  • 250% increase in re-engagement

  • 80% decrease in bounce rates

  • 146% increase in CTR on Ads

Pinterest

  • 40% increase in time spent on mobile web

  • 60% increase in engagement

  • 50% increase in ad click-throughs

  • 44% increase in user-generated ad revenue

Spotify

  • 54% increase in one-day plays

 

  • 14% increase in daily active users

 

  • 30% of logins came from churned users

More Examples

Example Deep Dive

PWAs vs
Native / Hybrid / Web

App Stores aren't Bad

  • User Discovery

  • Ratings

  • Advertising platform

  • User trust and security

  • Resource hosting

  • Payment automation

PWA vs App Stores

  • No annual license or 15-30% purchase fees

  • External purchase restrictions

  • No 3rd-party approval process

  • Users don't install apps anyways

    • Average of 0 per month for last 5 years

    • 60% of apps in the Play Store have never been downloaded

    • 3/4 of users who start the install process don't finish it

PWA vs App Stores

  • PWAs now included in all major App Stores

    • Google Play

      • auto-creation of a WebAPK

    • Microsoft

      • auto-creation and Windows Store install / uninstall

    • Apple

      • ...no store support! Waa waa waaah :(

      • Safari does allow PWA installs, but won't promote

PWA vs Native/Hybrid Apps

  • No platform-specific languages

  • ​(Swift, Objective-C, Java)

  • 100% code reuse on all platforms

    100% cross-platform install

  • Total control over updates / deprecation

  • Less ceremony than Hybrid frameworks

(PhoneGap, Ionic, Xamarin, Electron)

PWAs vs Native/Hybrid Apps

  • Nearly identical look and performance

  • Less install friction - 20% loss at each step

PWAs vs Native/Hybrid Apps

  • Less storage space and bandwidth

  • Less reason to uninstall

PWA vs 'Standard Web'

  • Standard web sites are becoming web apps

 

  • You're building a website anyways, app or not - why not start with best practices?

 

  • The web can (most likely) do everything you need anyways

PWA Interest

What can PWAs do?

What can PWAs do?

What can PWAs do?

  • W3C specifications & https://caniuse.com

  • Convenient feature grouping

  • Descriptions and <code snippets>

  • In-browser support checks

Accept All Input

Audio/Video Recording

Some advanced Output

Respond to Movement

Talk to other devices

Check device status

Access OS Resources

Send user updates

Provide seamless experiences

So what makes a PWA different from a Web App?

Technical Requirements

PWAs
must:

  • Originate from a secure origin

  • Have a png icon

(144 x 144 minimum)

  • Web manifest

  • Load while offline

  • HTTPS (TLS / SSL)
     

  • <app_logo>.png

 

  • manifest.json

  • Service Worker

Which they
do via:

The web manifest

Technical Requirements

manifest.json - Defines:

  • Name of the web app

  • Links to the icon(s) and other images

  • Preferred URL to launch on startup

  • Configuration data

  • Default orientation (portrait vs landscape)

  • Display mode preference, e.g. full screen

  • Other settings / preferences

manifest.json - Referencing

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

Note that "/manifest.json" is requested without any credentials, even on the same domain. If you require credentials:

<head>
    <link rel="manifest" href="/manifest.json"
         cossorigin="use-credentials" />
</head>

manifest.json - Implementation

{
  "name": "Google Maps",
  "icons": [
    {
      "src": "/images/icons-192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/images/icons-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": "/maps/?source=pwa",
}

manifest.json - Options

 

  • name

  • short_name 

  • start_url

  • background_color

  • display

 

  • scope

  • orientation

  • theme_color

  • service_worker

manifest.json - Store Options

  • description

  • screenshots

  • categories

  • iarc_rating_id

  • native_applications

  • prefer_related_applications 

The service worker

Technical Requirements

Service vs Web Workers

Web
Workers
Service Workers
Tab control Many per tab One for all tabs
Lifespan Same as tab Independent
Good for Parallelism Offline

Service Workers

  • Run in the background on own thread

  • Proxy network requests and caching

Service Workers - Gotchas

  • Run in a separate (global) context and thread from the window and UI

  • Can run in iframes

  • Have no direct DOM access

  • Can import scripts from any origin

  • Require HTTPS

Service Workers - Gotchas

  • Have a life cycle separate from the page - persistent background process

  • Are closed when not in use and all tasks have completed

  • Can take control of existing pages in same domain

  • There is no limit on how many SWs an origin can spawn

Service Workers - Registering

//check for functionality
if ('serviceWorker' in navigator) {    
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/sw.js')
    .then(function(reg) {
      // Registration was successful
      console.log('Registered with: ', reg.scope);
    }, function(err) {
      // registration failed :(
      console.error('Registration failed: ', err);
    });
  });
}

Service Workers - Lifecycle

  • Registration scope is set by location-based pattern matching

  • /sw.js has root scope

  • /subarea/sw.js matches:

    • /subarea/page1

    • /subarea/foo/bar

    • /other

SW Lifecycle

Service Workers - Using

  • Event driven / asynchronous

  • Make heavy use of Promises

  • Pass messages via:

    • push
    • fetch

 

Service Workers - APIs

Network Requests

  • XMLHttpRequest
  • Fetch API
  • Server Sent Events (SSE / EventSource)
  • Web Sockets

Storage

  • Local Storage
  • Session Storage
  • Web SQL
  • Cache
  • IndexedDB

Service Workers - Other Uses

  • Background sync with server

  • Handle / cache cross-origin requests

  • Centralized storage and sharing of 'expensive' operations 

  • Client-side compilation of development assets

  • Hooks for background services

  • Pre-fetching assets (eager loading)

 

Coding Demo

Generator Examples

What is                        ?

  • "A set of libraries and Node modules that make it easy to cache assets and take full advantage of features used to build Progressive Web Apps."

- Precaching
- Runtime caching
- Pre-built Strategies
- Request routing
- Offline Analytics
 
- Background sync
- Helpful debugging
- Successor to
    sw-precache and
    sw-toolbox

Workbox Caching Strategies

  • Cache Only

  • Cache first, falling back to network

  • Cache with network update

  • Network Only

  • Network first, falling back to cache

Front-End Support

Live Coding Workshop

Workshop Highlights

  • Create & register the manifest 

<link rel="manifest" href="/manifest.json">
  • Inspect the contents in the Dev Tools Application Tab

  • Alternate iOS meta tags

<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Weather PWA">
<link rel="apple-touch-icon" href="/images/icons/icon-152x152.png">

Workshop Highlights

  • Register the manifest within index.html

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
        .then((reg) => {
          console.log('Service worker registered.', reg);
        });
  });
}

Workshop Highlights

  • Configure caching in sw.js > install event

const FILES_TO_CACHE = [
  '/offline.html',
];

evt.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      console.log('[ServiceWorker] Pre-caching offline page');
      return cache.addAll(FILES_TO_CACHE);
    })
);

Cache the 'offline' page

  • Maintain cache in sw.js > activate event

const CACHE_NAME = 'mycache.v2'

evt.waitUntil(
    caches.keys().then((keyList) => {
      return Promise.all(keyList.map((key) => {
        if (key !== CACHE_NAME) {
          console.log('[ServiceWorker] Removing old cache', key);
          return caches.delete(key);
        }
      }));
    })
);

Display the 'offline' page

  • Handle network failures in sw.js

if (evt.request.mode !== 'navigate') {	//bail
  return;
}
evt.respondWith(
    fetch(evt.request)
        .catch(() => {
          return caches.open(CACHE_NAME)
              .then((cache) => {
                return cache.match('offline.html');
              });
        })
);

Tandem Cache/Network in app.js

// function getForecastFromCache()
const url = `${window.location.origin}/forecast/${coords}`;
return caches.match(url)
    .then((response) => {
      if (response) return response.json();
      else return null;
    }).catch((err) => {
      console.error('Error getting data from cache', err);
      return null;
    });
// within updateData:
getForecastFromCache(location.geo)
    .then((forecast) => {
      renderForecast(card, forecast);
    });
getForecastFromNetwork(location.geo)
    .then((forecast) => {
      renderForecast(card, forecast);
    });
    

Precaching in sw.js

const CACHE_NAME = 'static-cache-v2';	 //note updated name
const DATA_CACHE_NAME = 'data-cache-v1'; //new dynamic cache

const FILES_TO_CACHE = [
  // root files and core scripts
  '/',
  '/index.html',
  '/scripts/app.js',
  '/scripts/install.js',
  '/scripts/luxon-1.11.4.js',
  '/styles/inline.css',
  // common images
  '/images/add.svg',
  '/images/clear-day.svg',
  '/images/clear-night.svg',
  ... 
];

Precaching in sw.js > fetch

if (evt.request.url.includes('/forecast/')) {	//data API calls
  console.log('[Service Worker] Fetch (data)', evt.request.url);
  evt.respondWith(
      caches.open(DATA_CACHE_NAME).then((cache) => {
        return fetch(evt.request)
          .then((response) => {
            // Clone & cache good results
            if (response.status === 200) {
              cache.put(evt.request.url, response.clone());
            }
            return response;
          }).catch((err) => {
            // Network request failed, try the cache.
            return cache.match(evt.request);
          });
      }));
  return;
}	//else static asset; return cached or request if null

Adding Install

  • Use HTTPS

  • web.manifest has required elements

  • Register a SW that handles fetch events

Handle prompt event

  • Register install.js in index.html and sw.js

 

  • Intercept install prompt event

 

  • Attach event to a UI element & display it

<script src="/scripts/install.js"></script>
window.addEventListener('beforeinstallprompt', saveInstallPrompt);
// function installButton.onClick()
deferredInstallPrompt.prompt();
evt.srcElement.setAttribute('hidden', true); //hide after install
// function saveInstallPrompt()
deferredInstallPrompt = evt;
installButton.removeAttribute('hidden');

Handle install response

  • Respond to the user's choice in install.js

deferredInstallPrompt.userChoice
    .then((choice) => {
      if (choice.outcome === 'accepted') {  // installed
        console.log('Unicorns and kittens for all!', choice);
      } else {  // not installed
        console.log('Release the hounds!', choice);
      }
      deferredInstallPrompt = null;
    });
  • Users can also install from menu

window.addEventListener('appinstalled', (installEvt) => {
  console.log("Magically delicious!", installEvt) });

Tweaks for installed apps

  • Adjusting for standalone mode

// CSS
@media all and (display-mode: standalone) {
  body {
    background-color: yellow;
  }
}

// JS
if (window.matchMedia('(display-mode: standalone)').matches) {
  console.log('display-mode is standalone');
}

// JS Safari
if (window.navigator.standalone === true) {
  console.log('display-mode is standalone');
}

Push Notifications

// Ask the user for permissions
Notification.requestPermission((status) => {
    console.log('Notification permission status:', status);
});

// Show a notification
const btn = document.getElementById('btn');
btn.addEventListener('click', () => {
    if (Notification.permission == 'granted') {
        navigator.serviceWorker.getRegistration()
            .then(reg => reg.showNotification('Hello world!'))
    }
})

Notifications Options

  • Images and animated icons

  • Custom Timestamps

  • Persistence Time

  • User Interaction events

  • Custom Device Vibration

References / Reading

Business Cases

Training and Examples

Tech Evaluation Resources

References

References

Questions?

PWAs - the Store-free Installable Native Web App

By Brian Richins

PWAs - the Store-free Installable Native Web App

The browser is becoming a powerful platform for delivery, and websites are now spilling directly into the OS. PWAs can be installed on mobile devices and the desktop alongside native apps, and can access hardware, send notifications, and make all your dreams come true.* * Wish fulfillment not guaranteed, your results may vary.

  • 1,825