Beginner’s Guide to Converting a Javascript App to a Progressive Web App (PWA)

Nigel
hiCode
Published in
8 min readMar 31, 2018

No internet? No problem! Say Hi to Progressive Web Apps!

So Goodbye T-Rex?..

Gone are the days where no internet meant wasting time trying to score highest score on Google Chrome’s T-Rex ( downasaur ) game to distract yourself from the imminent frustration. Why, you ask? Because Progressive Web Apps is changing the web that we are used to since the last few decades. Progressive Web Apps, abbreviated as PWAs, focus on delivering reliable, fast and engaging user experience even on the slowest ( or lack of ) internet. This is achieved with the help of Service Workers and modern web browsers like Google Chrome.

In this guide, I will demonstrate how to convert a simple javascript app to a Progressive Web App. By the end of this article, you will learn to set up your javascript app that behave like a native app on your phone like this Tic Tac Toe PWA I made for this guide.

The processes that we need to do to convert javascript app to a Progressive Web App can be summarised into three simple steps:

  1. Caching & Serving
  2. Manifest
  3. Updation

Before we get into the details, make sure you have the prerequisites ready with you:

  • A Code Editor. I use VS Code.
  • A sample javascript project. For this guide, I am going to use a basic javascript based tic tac toe game. You can it on my repository here.
  • A localhost server. ( Web Server for Chrome extension may suffice )
  • HTTPS ready domain. For development purposes, localhost has an exception. Thankfully, Github Pages offer free HTTPS and I’m using it to host my javascript apps.

So, let’s get started with the guide!

Caching & Serving

The key differentiating features of a PWA is that it is fast and reliable. This is achieved most efficiently and effectively by taking advantage of the Cache Storage of the browser. We make sure to cache all the important pieces of code and files that our javascript app relies on.

But simply caching is not enough, we also need a way to intercept the network requests made by the javascript app to serve from the cache, if it is there.

Enter Service Workers!

Service Workers to the rescue!

A service worker is a script that browsers runs in the background. Service Worker is pretty new to the scene and right now only Chrome and Firefox fully supports them.

Let’s get to coding and implement Service Workers in our javascript project.

First, create a service worker javascript file in the root folder of the javascript app, which is where index.html lives. Let’s name this file sw.js. Our service worker code will live inside this file.

Now, register this service worker. This process basically tells the browser where the service worker file lives. Registering is usually done from main javascript file. Let’s do it on the existing script.js and add this following piece of code to register our service worker.

This piece of code first checks if service worker is supported in the browser and if it is supported then goes ahead and registers the service worker.

You can check if the service worker got registered successfully by starting localhost server on the project folder and visiting it on the browser. Look out for logs on DevTools console.

Now that we have successfully registered service worker, let’s move on to next step - caching the necessary files.

You can find all the resources requested by the javascript app going to Network tab on DevTools. The required resources are added to an array variable.

Our service worker has various lifecycle events. The caching process is done inside the install event of our service worker.caches.open() will open a cache bucket with the passed name and cache.addAll() will attempt to add the passed resources to that cache bucket.

One important thing to note is that, if any one of the URLs listed in the array is unreachable, then the caching and service worker installation process will fail.

Next, we make our service worker to listen to fetch events and respond with the requested resources from the cache if it is there. caches.match() will match to the requested and serve it. If no match is found, we will make a new fetch request from server worker.

If everything worked out without any errors, our javascript app is now offline ready! To check it out, disconnect your device from internet and the javascript app should still load fine.

Manifest

Now, to make our offline ready javascript app a Progressive Web App is just a matter of adding manifest.json file. It is a JSON file that gives us the ability to control how our app appears to the user.

Using this manifest file, our web app can:

  • be added to user’s Android home screen and have icons we defined
  • be launched in full-screen mode on Android with no URL bar, hence providing a native app like experience
  • control the screen orientation for optimal viewing
  • define a “splash screen” launch experience

This is the manifest.json I used for my Tic Tac Toe PWA.

Link the manifest.json inside <head> of index.html like this:

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

If your manifest.json is valid, Chrome should detect it successfully and you will be able to see it under Application > Manifest in the DevTools.

Now, when app is visited from Google Chrome mobile, visitors will also see Add to Home Screen banner.

Add to Home Screen prompt

Additionally, if you want to also enable Add to Home Screen for Safari on iOS or Tile Icon for Windows, add the following code inside <head> .

<!-- Add to home screen for Safari on iOS --><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="Simon Game"><link rel="apple-touch-icon" href="icons/icon-152x152.png"><!-- Tile icon for Windows --><meta name="msapplication-TileImage" content="icons/icon-144x144.png"><meta name="msapplication-TileColor" content="#2F3BA2">

Our Progressive Web App is kind of ready. It works offline and the web app can be added to Android home screen and act as if it is a native Android app.

However, we also need to acknowledge the situation when we make new changes to our web app. We need to deliver updates to the user without disrupting their experience. It would be better if we inform user about the pending update and let them take the action voluntarily instead of updating the web app forcefully.

annoying right?

Updation

Suppose we made some new changes to our web app and we want to deliver those changes to the existing users. To do that, we also need to update our service worker file i.e sw.js. Just bumping version of CACHE_NAME is enough.

var CACHE_NAME = 'tic-tac-toe-game-cache-v2';

When the user visits our app, browser will detect the new service work file and initiate the install event for the updated service worker. Since we caching inside the install event, it will also create a new cache bucket with the bumped up version name. However, the new service worker won’t get activated immediately. It will be in waiting state until all the currently open tabs of the web app is closed.

Since we want to notify our users about the pending update, this is a perfect opportunity for that. To do this, we need to modify our service worker registration code.

The alertBox and updateButton are referencing the HTML element we add on index.html. By default, we have hidden this element since we only want them to be displayed when there is a pending service worker update.

<div class="alert alert-primary" role="alert" id="updateNotification" style="display:none;">New version available! <a class="btn btn-primary" id="updateButton" href="#" role="button">Update</a></div>

Inside service worker registration, first we need to check if current page was loaded via service worker. If navigator.serviceWorker.controller is false, it means the page was not loaded via service worker and the user already has the latest version of service worker.

Otherwise, we go ahead and check if there is service worker waiting to take over. If there is waiting service worker, we call updateReady() to display the alert and also add click event listener to the updateButton.

If service worker is installing, we track it’s progress so that we can show update notification to the user once installation is complete. The trackInstalling() does that. It listens for statechange event of the new service worker and calls updateReady().

What if a new service worker arrives after page is loaded? In that case also we need to inform the user. For that we listen for the updatefound event from service worker and do trackInstalling().

When the user clicks on updateButton it calls postMessage on the waiting service worker. The postMessage can be literally anything but for the sake of readability, we are going to send a javascript object with property action and value skipWaiting.

Back in the service worker script sw.js, we add the following code to listen for this message and call self.skipWaiting() to make new service worker take over.

self.addEventListener('message', function (event) {   if (event.data.action == "skipWaiting") {     self.skipWaiting();    }})

We also want to make the page reload when new controller takes over so that the updates are reflected. To do this, we listen for controllerchange event with the following code in script.js.

navigator.serviceWorker.addEventListener("controllerchange", function () {
window.location.reload();
})

Once the new service worker is active, we want to clear the old caches created by old service workers. For that, we listen for activate event on the service worker script sw.js and execute the code to delete all cache buckets except the current cache bucket.

self.addEventListener('activate', function(event) {let OLD_CACHE="tic-tac-toe-game-cache-";event.waitUntil(   caches.keys().then(function(cacheNames) {     return Promise.all(       cacheNames.map(function(cacheName) {       if (cacheName.includes(OLD_CACHE)&&cacheName!==CACHE_NAME) {       return caches.delete(cacheName);       }       }
));
}));});

Notice that, instead of deleting all the caches, we are only looking for cacheName entries which starts with tic-tac-toe-game-cache- that is not CACHE_NAME (the latest cache ). This is important because if there are multiple web apps sharing same origin (such as GitHub Pages site), those other apps may also have caches stored in the same Cache Storage.

So that’s it! Our simple javascript app is now a Progressive Web App that can receive latest updates non-disruptively. You can find the live version of Tic Tac Toe PWA here.

The code is also available on my GitHub repository.

Conclusion

This guide was intended to show you basic procedure to convert your ordinary javascript app to a Progressive Web App. To conclude, these are basic steps to follow:

  • Use Service Workers & Cache Storage for offline availablility.
  • Use manifest.json to provide native app experience
  • Deliver updates non-disruptively and also clear old caches.

If you would like to explore more on this topic, Udacity has a great course on offline web applications.

Links to refer:

--

--