The PWA Philosophy

Matt Wilber

@greenzeta

greenzeta.com

What is a PWA?

“Progressive Web Apps (PWAs) are web applications that load like regular web pages or websites but can offer the user functionality traditionally available only to native applications.”

-- The Book of Knowledge (Wikipedia)

Why Go PWA?

  • Improve web experience
  • App store politics

    • Crowded marketplace

    • Difficult to differentiate

    • Review process

  • Leverage existing search ranking

  • Low barrier to entry

    • Use existing web technology

    • Works on all platforms

  • Small Footprint

PWAs are

much more

than that

The PWA Philosophy

  • Accessibility

  • Performance

  • Native features

Progressive Web Apps make an adaptive experience that fits as diverse an audience as possible.

Focusing on:

Accessibility means

bigger audience

  • People consume information in different ways.

    • Text, Images, Sound.

  • Accessibility is not an add-on.

    • Present your information in as many ways as possible.

    • Don't accommodate disabilities, embrace different perspectives.

  • Leverage established techniques:

    • Responsive design

    • Semantic HTML

Performance saves users & bandwidth

  • You have 4 seconds to get their attention.

  • Prioritize features.

    • Basic functionality available right away.          

    • ​Lazy load everything else.

  • Cache static files.

    • Pull new content in the background.

  • Optimize graphics.

    • Use SVG where possible.

    • Limit web fonts.

Modern browsers afford native features

  • Web Manifest (Installation)

  • Service Worker

    • Fetch API

    • Cache API

    • Sync API

    • Push Notifications

  • LocalStorage / IndexedDB

  • Location API

  • GetUserMedia()

3 Minimum Requirements to qualify as a PWA

  • SSL

  • Service Worker

  • Web Manifest

Turn any website into a PWA

in 10 minutes

( assuming SSL )

Add Web Manifest

{
	"name": "GreenZeta Progressive Web App Demo",
	"short_name": "GZ PWA",
	"theme_color": "#7bb951",
	"background_color": "#111313",
	"display": "fullscreen",
	"Scope": "/",
	"start_url": "/",
	"icons": [
	  {
		"src": "/assets/icons/icon-192x192.png",
		"sizes": "192x192",
		"type": "image/png"
	  }
	],
	"splash_pages": null
  }

Add Metadata Alternative

<meta name="viewport" content="width=device-width">
<meta name="apple-mobile-web-app-capable" content="yes" />

<meta name="theme-color" content="#7bb951">
<meta name="mobile-web-app-capable" content="yes">
  
<link rel="apple-touch-icon" href="assets/icons/icon-512x512.png">
<link rel="apple-touch-icon" sizes="72x72" href="assets/icons/icon-72x72.png">
<link rel="apple-touch-icon" sizes="144x144" href="assets/icons/icon-144x144.png">
<link rel="apple-touch-icon" sizes="512x512" href="assets/icons/icon-512x512.png">

Register Service Worker

if ('serviceWorker' in navigator) {
	window.addEventListener('load', () => {
		navigator.serviceWorker.register('service-worker.js')
                    .then(registration => {
		    	console.log('SW registered: ', registration);
		    }).catch(registrationError => {
		    	console.log('SW registration failed: ', registrationError);
		    });
	});
}

Add A Caching Strategy

self.addEventListener('install', function(event){
    console.log('[SW] installing...');
    event.waitUntil(caches.open('static')
        .then(function(cache){
            console.log('[SW] precaching');
            cache.addAll([
                '/',
                '/index.html'
            ]);
        }));
});

self.addEventListener('fetch', function(event){
    event.respondWith(
        caches.match(event.request)
            .then(function(response){
                if(response){
                    return response;
                }else{
                    return fetch(event.request);
                }
            })
    );
});

Install Prompt
(optional)

let deferredPrompt;

window.addEventListener('beforeinstallprompt', (e) => {
	// Store the install prompt event
	deferredPrompt = e;
	console.log('install prompt ready');
});

document.getElementById('install').addEventListener('click', (e)=>{
	// Show the prompt
	deferredPrompt.prompt();
	// Wait for the user to respond to the prompt
	deferredPrompt.userChoice
		.then((choiceResult) => {
		if (choiceResult.outcome === 'accepted') {
			console.log('User accepted the prompt');
		} else {
			console.log('User dismissed the prompt');
		}
		deferredPrompt = null;
		});
});

How to approach building a PWA

War of the Worlds Broadcast

  • 2012 - Began as HTML5 audio demo

  • 2018 - Rebuilt as a PWA

Rethinking the website as a PWA

  • App Shell

    • In-line svgs

    • In-line javascript

    • Minimal necessary CSS

  • Eliminate jQuery

    • document.QuerySelector()

    • fetch()

Responsive Design

  • Graphically rich layout needed to look good at any resolution.

  • Combination of scaling and re-positioning

  • Moving previous control overlay to an action button + slide-out menu

Desktop

Phone

App Shell

  • index.html - 10k in size. Contains everything to display a minimally usable app.
  • First meaningful paint - inline svg, action button, menu w/ description

Lazy Loading

  • Don't make user wait for entire site to load.
    • Audio streams while images are loading
  • Provide indication that more is coming.
    • Entertaining progress bar

Native Features

  • Offline Access

  • WebAudio API

  • Chromecast

  • Push Notification

Optimization

  • Lighthouse, available through Chrome dev tools.
  • Don't get hung up with the scores.

Source Code

github.com/mwilber/gz-10-minute-pwa

Check Out

waroftheworldsbroadcast.com

Follow @greenzeta on Twitter

Slides

slides.com/greenzeta/pwa-philosophy

greenzeta.com

Google PWA Checklist

developers.google.com/web/progressive-web-apps/checklist

Made with Slides.com