Skip to main content

Progressive Web Apps

I went to an all day tutorial on Progressive Web Apps (PWAs). Here are my notes:

The tutorial was by Maximiliano Firtman, @firt.

Here are the slides.

Wow, he's written a lot of books!

He's not going to stick to Google's party line.

He mentioned the 10 year history of the web vs. native.

Funny: https://www.wired.com/2016/04/wait-web-isnt-really-dead-google-made-sure/

Web sites are instantly available. You don't have to install them.

Google is the one pushing PWAs. They were followed by Firefox, Opera, and Samsung. Microsoft is coming.

Apple isn't there, but you can still do some stuff on iOS. They created some stuff about 6 years ago that's still pretty useful. Apple hasn't announced any plans to implement various Progressive Web App features.

On iOS, Safari can save a bookmark to the homescreen, but you can't in Chrome on iOS.

Remember, Chrome on iOS is not Chrome. The rendering engine is WebKit, not Blink. Even the UserAgent has "CriOS" instead of "Chrome" in it.

His definition: A Progressive Web App is a model for creating app-like experiences using the latest web technologies progressively.

Important: These are the features of a Progressive Web App, per Google:

  1. Instant loading
  2. Discoverable
  3. Network indepedent
  4. Responsive (although he's not a fan of using exactly the same code for mobile and non-mobile)
  5. Installable (although Chrome doesn't let you do this on desktop by default, except on ChromeOS)
  6. Secure (you must use HTTPS, but you can use HTTP for localhost)
  7. Linkable
  8. Re-engageable (i.e. push notifications)
  9. Works everywhere (???)
  10. Fast

Progressive enhancement: don't design your website for only the latest browsers. Start with a core, and then add features for more advanced browsers and situations. He called this a design pattern.

  1. It's a website!
  2. Add native installation
  3. Add web push notifications
  4. Add hardware and platform access

PWAs vs. hybrid applications:

Hybrids are things built on Apache Cordova, PhoneGap, etc.

  1. There are big differences
  2. A PWA doesn't have distribution via the app store
  3. A PWA doesn't require packaging and signing like a native app
  4. A PWA can't access native plugins

Some people think that hybrids (like PhoneGap) will go away over time and be replaced by PWAs.

He doesn't consider React Native a hybrid. It's a native app that borrows some things from the web platform.

He wrote a book 7 years ago for O'Reilly called "Programming the Mobile Web". Hence, trying to build apps for mobile using web technologies is not a new idea. There have been numerous attempts to build what we are now trying to achieve with PWAs.

iOS came out with Home Screen Web Apps about 8 years ago. He said that this kind of inspired Google.

Firefox had Open Web Apps for WebOS. The project was cancelled, and they're now promoting PWAs.

Chrome had Chrome Home Screen Web Apps. These were inspired by iOS.

He talked about The Extensible Web Manifeso. The idea is to give web developers powerful, low-level APIs so that developers can extend the platform by themselves.

He's using Vysor in order to show his phone through his laptop.

He talked about flipkart.com, which is the poster child of PWAs. You can add the app to your homescreen. Once you re-open it, it is full-screen. It doesn't have the browser navigation bar, etc. It can run offline. It switches to grayscale as a hint that you're offline.

The Washington Post also has a PWA.

Instead of "Add to home screen", the newest Chrome has "Install web app" in the menu. The browser can invite the user to install the app using a banner.

Opera has something called "ambient badging" that they're working on. There will be an icon near the URL that's a hint to the user that you can install the web app.

Twitter built a PWA as well.

So did aliexpress.com. Their PWA has a banner that tells you to install their native app.

On iOS, in Safari, you can click "Add to homescreen". If you do so, and then click on the icon, there won't be any UI from the browser. It's treated more like an app.

https://pwa.rocks has a list of PWAs.

PWA compatibility:

  • Chrome on Android (not on iOS and not fully on desktop)
  • Opera Mobile
  • Samsung Internet Browser
  • Firefox (partial)
  • Edge (in the future; they'll be the first to support desktop)

Chrome on desktop has service workers, but it doesn't let you add an icon to the user's desktop.

Whether you are a PWA or not is not a boolean. It's an open definition with a lot of features that you could or could not be using.

Best of the web:

  1. Linkable
  2. Discoverable
  3. Easy to deploy
  4. Easy to update
  5. Web standards

But also with the best of native:

  1. Offline access
  2. Installed icon
  3. Push notifications
  4. Full screen experience
  5. Fast UI

Flipkart saw users spending 3x more time on their web experience since implementing a PWA. They saw a 40% increase in their re-engagement rate. They saw a 70% increase in their conversion rate for home screen users.

AliExpress saw a 104% increase in users, 2x increase in page loads, and 74% longer web sessions.

Interesting: Note that Flipkart and AliExpress both have native apps as well. No one big is building a PWA instead of a native app.

User acquisition is much, much cheaper for PWAs compared to native apps. Selio found that it was 10x cheaper to acquire users for their PWA compared to their native app.

Abilities:

  • HTML5
  • Works under any network situation
  • Access to sensors and hardware (USB, bluetooth, VR, etc.)
  • Multimedia
  • Icon on the home screen (on Android)

Android came out with Android Instant Apps, which kind of made the Chrome team angry. Android Instant Apps give you a native app without the installation.

Limitations that PWAs have compared to native apps:

  • They don't work on every platform
  • Responsive design is painful
  • They're not first class citizen
  • You don't have access to Android intents
  • You can't distribute your app in the app store

Interesting: Google is working on something called a Web APK. It's currently in Chrome beta under a flag. It will create an APK on the fly on Google's servers and then let you install it. The app will now be a first class citizen. He thinks this will be launched in 6 months.

Right now, you have to enable unsigned sources, but this restriction is going away.

I kind of think of this as an on-the-fly, less powerful version of PhoneGap. Although, keep in mind that PWAs are based on Chrome, whereas PhoneGap is based on webviews.

Brand new article: The New and Improved Add to Home screen

This is for Chrome and Android only. It's not for Opera, Samsung Browser, iOS, etc.--at least not yet.

Microsoft said that PWAs will also be available in the Microsoft store.

Web App Manifest (WAM)

It's a standard from the W3C. It's not new.

Without a manifest, you're not a PWA.

Ideally, you should serve it as application/manifest+json.

Basics:

  • name
  • short_name
  • start_url
  • lang
  • dir (language direction: rtl, ltr, auto)
  • orientation (any, portrait, landscape, natural, etc.)
  • display (required; browser, minimal-ui, standalone, fullscreen)
  • background_color (used for the splash screen)
  • theme_color (the color of the status bar)
  • icons (an array of icon objects)
    • src (32 bit PNGs with an alpha channel)
    • sizes (do a search for Android icon sizes)
    • type

There are several online web app manifest generators that you can use, such as: https://app-manifest.firebaseapp.com/

Article: Don’t use iOS meta tags irresponsibly in your Progressive Web Apps

He's using Visual Studio Code.

(I talked with him about this during a break. He said that he's using it because it's free and easy. He couldn't think of any killer features it has compared to IntelliJ Ultimate.)

index.html

<!doctype html>
<title>My first PWA</title>
<link rel="manifest" href="/manifest.json">
 
<h1>First PWA!</h1>

manifest.json

He used the generator mentioned above.

{
    "name": "...",
    ...
}

If you want to see flipkart's manifest.json, configure your Chrome DevTools to simulate a mobile device. Then, load https://www.flipkart.com/manifest.json.

You can tell it's working if you load Chrome DevTools, and there's an "Application" tab near the "Sources" tab.

Part of the magic is just having a manifest file.

Advanced techniques

Advanced manifest options:

  • related_applications (an array of app objects)
    • platform
    • url (URL of the Play store app)
    • id
  • prefer_related_applications
  • scope (e.g. "/"; not very useful today; what's owned by the PWA instead of by the browser)
<script>
window.addEventListener("install", function(e) {...});
</script>

You can use the display-mode media query to do something different if you were launched as a standalone app vs. if you were launched within the browser:

@media(display-mode: standalone) { /* vs. browser */
    body () /* The user is using the icon. */
}

Architecture

It's just web development. HTML, CSS, JavaScript, etc. You can use things like React, Angular, Polymer, Ionic, etc.

Ionic is a UI framework based on Angular 2 and TypeScript, but it also has a useful command line tool as well. Ionic tries to mimic the UI in Android or iOS.

Material design is just an approach to design. It's not just for Android.

Polymer apps don't try to mimic the native UI like Ionic apps do.

For the data layer:

  • Web storage
  • IndexDB
  • Web sockets
  • Server sent events
  • XHR
  • The fetch API
  • Service workers
  • Background sync

Device APIs:

  • Geolocation
  • Sensors
  • Multimedia
  • Bluetooth

You have to keep web performance in mind.

You must use TLS. HTTP/2 is recommended for performance reasons, but not required.

You can use service workers.

A Web App Install Banner is a little popup from the browser to prompt the user to install your app.

How to discover PWAs:

  • SEO techniques
  • Social networks
  • Etc.

Danger: If you open up a URL from within Facebook, you're stuck with a webview instead of actual Chrome. Hence, distributing a link to your PWA via social networks just doesn't work that well.

Chrome Custom Tabs let you send the user to Chrome with some arguments, etc. to control the experience. You can use this instead of using a webview. Service workers will work. However, Facebook and Twitter don't use these.

Since Android 5, the webview is based on Chromium. Remember that Chromium is open source, whereas Chrome is commercial. You can upgrade your webview, but no one does.

iOS has SFSafariViewController. Flipboard uses it. Facebook and Twitter don't.

A bluetooth beacon with 2 AAA batteries can stream a URL for about a year on a single set of batteries. It only costs about $20. This is the basis of the "Physical Web".

Right now, the web Bluetooth API won't let you automatically connect to Bluetooth beacons.

On iOS, you can add the Chrome widget, and it can scan for Physical Web widgets.

To get a Web App Install Banner, you need:

  1. A service worker
  2. A web app manifest
  3. The user to visit multiple times

On Android, there are no Chrome extensions.

There's a vibration API so that you can vibrate the user's phone.

You can't trigger the Web App Install Banner to happen exacty when you want, but you can customize how it appears once the requirements have been met. See the beforeinstallprompt event.

Ambient badging is the idea of having an icon near the URL in the location bar that shows you that the site is a PWA that can be installed. Opera has proposed this idea. However, no one has implemented it.

There's a downside to running full screen: if you've configured your PWA to run full screen, how can the user share the URL?

A lot of the big PWAs are in Africa or India. PWAs use less storage. They don't require a big download. Users in those places can't install too many apps, and they routinely uninstall apps.

The promise is that one day, users will install your app, and they won't know or care whether it's native or a PWA.

If you uninstall Chrome, it'll also uninstall all the shortcuts on your homescreen that Chrome created. However, on most phones, you can't actually uninstall Chrome because it comes with the device.

Remember, Google might be telling everyone to build PWAs, but their own big apps aren't PWAs.

Global browser stats: http://gs.statcounter.com/

In Asia:

  • Chrome: 44%
  • UC Browser (a Chinese browser): 27%
  • Safari: 10%
  • Opera: 6%
  • Samsung Internet Browser: 6%

UC Browser is a proxy-based browser built for performance. It runs through China. It runs partially in the cloud in order to make the experience better for people with crappy phones and network connections.

UC Browser has 50% market share in India.

In Africa, Chrome finally just passed up Opera.

api.fixer.io is an API for getting current currency rates.

This creates a number keypad:

<input placeholder="Value in USD" type="number" pattern="[0-9]*">

Interesting: By default, mobile browsers emulate desktop browsers that are 980px wide. iOS was the first to do this, and everyone else followed suit. To turn this off, add this to the head:

<meta name="viewport" content="width=device-width">

You could also pick an actual width instead of using device-width. The default, as I mentioned above, is 980px. However, using device-width is probably what you want. You need this in a PWA.

You used to have to add initial-scale=1, but as of later versions of iOS, this is ignored.

You'll also want:

<meta charset="utf-8">

The mobile-optimized frameworks like Ionic take care of a lot of CSS stuff for you to make it more mobile friendly.

This is CSS to turn off copy-and-paste:

user-select: none;

Although, I hate it when people do this.

-webkit-tap-highlight-color: red;

In HTML, there's an <output> tag to represent the output of a calculation.

Let's just use ES6 for now:

document.querySelector("input[type=button]").addEventListener("click", () => {
});

Service Workers

Important: Code snippets (I won't bother copying them all).

It's a little like a web worker, but it's detached from any tab. It's specific to your origin.

It can intercept network requests and replace them with something else. It can respond with a fake response or return something from a cache.

From your main HTML file:

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js');
}

It has no window, no document, etc.

self.addEventListener("install", e => {});

Remember, in Chrome DevTools, in the Application tab, there's a Service Workers section in the left-hand navigation.

If you go to the Console, there's a drop down that lets you switch from top to sw.js to switch from the main context to the service worker context. In the service worker context, you don't have a window, but you do have self.

chrome://serviceworker-internals/

You can prefetch URLs upon install.

From the Application tab, you can unregister your service worker. You'll probably also want to click "Update on reload".

You can also access your cache. During development, you may need to delete things from the cache.

You can also tell it to simulate being offline.

You can communicate between the service worker and the window. You can tell the window that there might be an updated version of the content that was requested earlier. However, you kind of have to implement that yourself.

Within the main window, you have access to the same caches:

caches.match("/")

The service worker can raise an exception after a timeout and return something from the cache.

When you're serving the service worker, you can use caching headers to control how the browser caches it.

The browser will cache a service worker for at most 24 hours. After that, it'll try to download it again. If it's exactly the same, it'll let the previous copy keep running. Otherwise, it'll start shutting it down and run the new version for new page loads.

In a service worker, you can use:

importScripts(["...", "..."]);

This is a web workers API. Alternatively, you can use a something like Webpack to generate a JavaScript bundle.

If you change an importScript, but you don't change the main service worker, the browser won't know that you have a new version of it. You might want to add a version comment at the top of the file. Hence, everytime you change the import script, you should change the version comment.

Data storage:

  • Web Storage:
    • It's a key/value store.
    • localStorage and sessionStorage are not available from SW because they're synchronous.
    • You're limited to 5MB, but in reality, since they use UTF-16, you probably only have 2.5MB to work with.
  • Cookies:
    • You can see the cookies in the requests and responses, but since you're not connected to any particular tab, you don't have access to global cookies.
  • IndexDB:
    • NoSQL.
    • It's an awful API.
    • Stores objects.
    • This is what we should be using.
    • It takes a lot of code to do anything.
  • Web SQL:
    • Deprecated a very long time ago.
    • It's in Chrome and iOS.
  • Cache storage:
    • Cache HTTP responses.

Funny: http://filldisk.com. It fills your disk with images of cats. It uses DNS aliases (SOME_NUM.example.com). Each subdomain is a different origin, and each has their own 5MB.

A service worker can see a request to another origin. You can cache the response, but you can't see it, and you can't generate it on the fly.

localStorage.setItem("data", JSON.stringify(d));

localStorage is easy, but you can't use it from the service worker. IndexDB is really painful to use.

If you want an easy-to-use wrapper for IndexDB, see localForage.

Web workers are available almost everywhere, but they're basically just like processes. They're attached to a tab.

Interesting: There's a new background sync API. It's in Chrome. Not in Opera. Not in Samsung Browser. Will be in Edge. Works even if the user doesn't come back to the tab.

navigator.onLine lets you know if you're offline. If it's false, you know you're offline. However, if it's true, you may be in an area where it's not working. You may be on a hotel network and unable to get outside of it.

Offline.js can help you with more advanced network detection.

Alternatively, you can just try to do a fetch and catch the exception.

This is how to make your website gray if you're offline:

window.addEventListener("offline", function() {
    document.documentElement.style.filter = "grayscale()";
}

You need a manifest and a service worker to get a web app install banner, and you need the user to access your site a second time (1 hr < 2nd visit < 30 days).

Push notifications

Web Push is a new API on top of Service Workers.

Remember, see the code snippets link above.

You need to generate a public key so that you can confirm with the server who you are. There's a code snippet to show you how to generate such a key.

Use pushSubscribeUser() to subscribe the user to pushes. It returns you an endpoint URL that you can use to push messages to the user.

In the service worker, you can register for push events. Imagine, the user isn't even using your site right now. However, your service worker can receive the event.

Within Chrome DevTools, in the Application tab, there's a Push link that you can use to simulate pushes.

It works even better on phones than on desktop. On desktop, if you close the browser, you can't receive messages. On Android, if the message is received, it'll wake up the browser--even if you just turned on your phone and never even started Chrome.

You can query all your client tabs in a service worker.

You can receive a notification in a service worker, updated IndexDB, and then use the client API to tell all the tabs that you have new data.

The notification can only be 1.5k. However, you can do a fetch in response to a notification.

Important: You cannot have silent notifications. You must notify the user:

self.registration.showNotification('Push title', { body: event.data.text() })

If you don't this, the user will get a generic notification.

Facebook and Twitter use this.

They're working on another API that, with the user's permission, you'll be able to update data in the background.

With Web APK, notifications will look like they're from your app, not necessarily from Chrome. It'll look "more native".

Lighthouse

Lighthouse is a Chrome extension that you can use to see how well you've built your app, including the PWA features of your app.

It's available as a Chrome extension or as a command line tool via npm.

iOS Home Screen Web Apps

Here's what's available on iOS:

  1. You can use a meta tag that lets users create an app launcher icon.
  2. You can use AppCache, which is an older, somewhat crappy API for offline apps.
  3. There's no way to do push notifications.
<meta name="app-mobile-webapp-capable" content="yes">
<link rel="apple-touch-icon" href="...">

This will add an "Add to Homescreen" icon somewhere in the menus. Then, you'll have an icon on the homescreen.

https://github.com/firtman/iWAM is an experiment he threw together to take the web manifest file and translate it into meta tags for Apple.

Here's an article he wrote: Don’t use iOS meta tags irresponsibly in your Progressive Web Apps

AppCache is available all over the place. It's terrible. However, it's the only thing you have on iOS.

In closing

  1. Instant loading
  2. Discoverable
  3. Network indepedent
  4. Responsive
  5. Installable
  6. Secure
  7. Linkable
  8. Re-engageable
  9. Works everywhere (???)
  10. Fast

He doesn't think there are good resources / references for Service Workers. This is probably the best he can offer: The Service Worker Cookbook.

The best practices haven't really shaken out yet. It's new. The API is changing. The best practices are changing.

We've only covered a small part of the service workers and web push APIs.


Comments

Outweb said…
https://outweb.io is also a great source to find Progressive Web Apps.
Anonymous said…
Nice summary and very useful notes!