Basic Progressive Web App (PWA) Setup

December 11, 2017

There are plenty of features and configurations for PWA, but we shall focus on the most basic.

PWA requirements

  • HTTPS/SSL is mandatory, do check out some of our SSL related tutorials.
  • Pages need to be responsive, do try Bootstrap.

Web App Manifest

Web App Manifest is a json file with basic info like name, icons, etc.

  • Display: standalone (full-screen) or browser
{
  "short_name": "My App",
  "name": "My Application",
  "icons": [
    {
      "src": "/static/img/icon-48.png",
      "type": "image/png",
      "sizes": "48x48"
    },
    {
      "src": "/static/img/icon-96.png",
      "type": "image/png",
      "sizes": "96x96"
    },
    {
      "src": "/static/img/icon-192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/static/img/icon-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": "/",
  "background_color": "#F2F2F2",
  "theme_color": "#2E2E2E",
  "display": "standalone",
  "orientation": "landscape"
}

Add link tag to the manifest file in your HTML.

<!DOCTYPE html>
<html lang="en">
<head>
    ...
    <link rel="manifest" href="/manifest.json">
</head>
<body>
    ...
</body>
</html>  

To verify if your manifest file is configured properly, open Chrome DevTools (Press F12 in Chrome) -> Application -> Manifest

NOTE: you probbaly want to get your manifest file absolutely correct before deploying to production as updating them later might be an issue (refer to this and this).

Service Worker for caching

A service worker is a type of web worker. It’s essentially a JavaScript file that runs separately from the main browser thread, intercepting network requests, caching or retrieving resources from the cache, and delivering push messages.

We need to setup Service Worker to cache the start_url in order to pass Lighthouse’s PWA Audit and enable Add to homescreen feature.

Add the following code to install and register a service worker. You can add it within your HTML file or put in within a javascript file (e.g. ./main.js).

Note: if your start_url is /, the service worker path must be /service-worker.js in order to cache it due to scope issues. /js/service-worker.js can’t cache / as it can only cache content from /js/.

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js')
  .then(function(registration) {
    console.log('Registration successful, scope is:', registration.scope);
  })
  .catch(function(error) {
    console.log('Service worker registration failed, error:', error);
  });
}

Add the following code in /service-worker.js for enable Service Worker to cache the start_url.

Note: if your start_url implement basic HTTP authentication, you need to implement cache.addAll(urls.map(url => new Request(url, {credentials: 'same-origin'}))) in the install event caching code.

// Listen for install event, set callback
self.addEventListener('install', function(event) {
  const CACHE_NAME = 'startup-v1';
  const urls = [
    '/'
  ];
  event.waitUntil(
    caches.open(CACHE_NAME).then(function(cache) {
      return cache.addAll(urls.map(url => new Request(url)));
      // use the following code if basic HTTP authentication is implemented
      // https://stackoverflow.com/questions/38193221/how-to-use-a-service-worker-with-basic-authentication-ntlm-negotiate
      // return cache.addAll(urls.map(url => new Request(url, {credentials: 'same-origin'})));
    })
  );
});

self.addEventListener('activate', function(event) {
  console.log('activate');
});

self.addEventListener('fetch', function(event) {
  console.log('fetch: ' + event.request.url);

  const ignoreUrls = [
    '/login',
    '/static/js/app.js'
  ];
  const url = new URL(event.request.url);
  if (ignoreUrls.indexOf(url.pathname) > -1) {
    console.log('ignore');
    return
  }

  // https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker
  event.respondWith(
    // Cache falling back to the network
    caches.match(event.request).then(function(response) {
      return response || fetch(event.request);
    })
  );
});

For deugging, open Chrome DevTools (Press F12 in Chrome) -> Application -> Service Workers. If the Service Worker is installed and registered correctly, you should see the following.

Service Worker

If the caching is working, goto Chrome DevTools -> Application -> Cache -> Cache Storage (Click to expand) and you should see the cache name startup-v1 with start_url content being cached.

Refreshing both Service Worker and the Cache during development could be quite tricky.

  • You can manually ungister the Service Worker at Chrome DevTools -> Application -> Service Workers, or enable Update on reload.
  • You can change CACHE_NAME from startup-v1 to startup-v2 and increment it if changes are made to the cache, or you can clear the cache manually at Chrome DevTools -> Application -> Cache -> Cache Storage (Click to expand) -> startup-v1, right click and click on Delete.

If the /service-worker.js is served from nginx, you might want to disable HTTP caching by adding the following to nginx configuration.

server {
        ...
        location /service-worker.js {
                alias /myapp/static/js/service-worker.js;
                expires -1;
        }
}

References:

Lighthouse

Use Lighthouse to audit your PWA app. Goto Chrome DevTools -> Audits and click on Perform an audit .... Check on Progressive Web App only at this moment.

It shall prompt you of any additional changes required.

  • You might get Invalid properties found: {"shrink-to-fit": "no"}, just ignore it based on this issue.

  • If you get Failures: No '<meta name="theme-color">' tag found., include <meta name="theme-color" content="#2E2E2E" /> in your HTML. Though you already specify theme_color in manifest, you do need to specify in HTML as well based on this issue.

If the manifest and service worker caching is configured properly, your PWA should be ready with a Add to homescreen prompt if tested on Android. On Desktop, you can test it at Chrome DevTools -> Application -> Manifest -> Add to homescreen.

Note: I did see the Add to homescreen prompt once while testing on Desktop (Chrome 62 on Linux) once, but nothing happens (no error log) on subsequence click on Add to homescreen at Chrome DevTools. The Add to homescreen prompt did work when I tested on Android.

Note: You might want to configure self-signed SSL for development testning.

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.