Thoughts grows up by feeding itself with its own words.

Takeaways from Google Developer Days 2017: PWA Progressive Web App

谷歌开发者大会2017心得:PWA 渐进式网页应用

This is the second post in the Google Developer Days 2017 Take Ways series. Progressive Web App (PWA) was a recurring technology at this year's GDG, and I participated in at least two talks on the topic of PWA.

The main problem that PWA is trying to solve is the ability to make a Web App nearly as efficient as a Native App. In addition, PWA gives developers an entry point to their apps where people are less inclined to download new apps, and users can access PWA through search engines. In addition, PWA can be saved to the desktop by users just like a Native App, a process that is as easy as saving bookmarks in a mobile browser.

Basic concepts about PWA



In short, Web Application Manifest is a business card for a Web App. It is a JSON file in which the developer can declare some meta information about his Web App, such as

  • name
  • description
  • theme_color
  • start_url

Here is an example of manifest.json:

  "background_color": "white",
  "description": "Your description",
  "display": "standalone",
  "name": "Your title",
  "short_name": "Your short name",
  "start_url": "/",
  "scope": "/",
  "lang": "en",
  "theme_color": "white",
  "icons": [
      "src": "icon/lowres.png",
      "sizes": "64x64"
    }, {
      "src": "icon/hd_hi",
      "sizes": "128x128"

Service Workers


Web workers have been around for a while, but service workers are still a relatively new concept to me. Both are designed to run some complex logic outside of the main JS process, thus freeing up the main process and increasing the efficiency of the app's operation. And service workers can have the ability to cache offline, in addition to running independently. These are the service worker characteristics:

  • Life cycle: The life cycle of service workers is relatively complex, with different stages of registration, installation, activation, and so on.

  • Registration: service worker must be registered before it can be used

  • Communication: like a web worker, a service worker can also use postMessage() to communicate with the main process; In addition, service worker has richer APIs to call, such as Fetch, Cache, and Push, so it can be used to intercept requests and cache files

  • Persistence: service worker exists forever after it is installed unless it is uninstalled manually

  • Compatibility: Not all browsers are compatible with service workers now, the current compatibility list can be found here, I believe the major browser vendors will provide better support later.


Push and Notification

Push and Notification are indispensable features of an app. The concepts of Push and Notification are not the same.

  • Push: The server pushes new information to the App, which in the PWA mainly means pushing to the service worker
  • Notification: The service worker pops up the updated information and gives the user an alert

PWA with Ruby on Rails

It's a good idea to try PWA yourself.

My usual web framework is Ruby on Rails, and here I'm going to do a little PWA example based on Rails. In the example, I'm going to keep things simple (mainly because Rails has its own assets packaging mechanism, which varies a lot from version to version of Rails, so I'm going to use the most straightforward approach). If you are using Webpacker, try this gem serviceworker-rails.

Start with Manifest

We write the file manisfest.json in the public directory, and for the icons part I'll simply write:

  "description":"Hegwin's Blog",

Write a service worker

In public/service-worker.js:

function onInstall(event) {
  console.log('[ServiceWorker]', "Installing!", event);

function onActivate(event) {
  console.log('[ServiceWorker]', "Activating!", event);

function onFetch(event) {
  console.log('[ServiceWorker]', "Fetching!", event);
self.addEventListener('install', onInstall);
self.addEventListener('activate', onActivate);
self.addEventListener('fetch', onFetch);

You may be wondering what self is here, it is a global variable in the Service Worker. We generally have these global variables:

  • self: denotes the Service Worker scope, also a global variable
  • caches: means cache
  • skipWaiting: means force the script currently in waiting state to activate state
  • clients: indicates the pages that the Service Worker takes over

Initialize the service worker

In app/views/layouts/application.html.erb:

<!DOCTYPE html>
    <link rel="manifest" href="/manifest.json">
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <%= javascript_include_tag "application", defer: true %>
    <%= csrf_meta_tags %>

In app/assets/javascripts/application.js.

//= require worker-init

Now let's write the file app/assets/javascripts/worker-init.js:

if (navigator.serviceWorker) {
  navigator.serviceWorker.register("/service-worker.js", { scope: "/" })
    .then(() => navigator.serviceWorker.ready)
    .then((registration) => {
      if ("SyncManager" in window) {
      } else {
        window.alert("This browser does not support background sync.")
    }).then(() => console.log("[WorkerInit]", "Service worker registered!"));

Look at the result!


It seems to be working fine, the logs are successfully printed out. But let's check again with Lighthouse.


Although the check thinks that PWA is supported, we can see that there are still some flaws: !


The main problems are:

  1. Does not set a theme color for the address bar.Failures: No <meta name="theme-color"> tag found.

  2. Manifest doesn't have a maskable icon


Add to app/views/layouts/application.html.erb:

<!DOCTYPE html>
    <link rel="manifest" href="/manifest.json">
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <meta name="theme-color" content="#ffffff">

In the icons of manifest.json add

  "icons": [
    {"src":"/apple-touch-icon.png", "sizes": "180x180", "type": "image/png", "purpose": "any maskable"},

Again, take a look.


That's much better!

If you follow along step by step, you will find that the previous statement "service worker is always there after it is installed, unless you uninstall it manually" is not a lie, you can always see the logs printed in the Console of DevTool. So how to uninstall it? Here's how:

navigator.serviceWorker.getRegistrations().then(function(registrations) {
    for(let registration of registrations) {

Let's call it a day!

< Back