Building a Progressive Web Apps- CodeLab

Hi Folks, In this codelab we are going to build a progressive web apps from scratch.This codelab is divided into 7 section:

Lets Start:



1. Introduction

In this codelabs, you will be learning what is a Progressive Web Application, how it works and how to build and deploy one.

What is a Progressive Web Application?


A progressive web application (pwa) uses modern web technologies to deliver native app like experience even when user is offline.

Features:

  • Responsive - Fits any form factor and works across the devices.
  • Secure - Served via https ðŸ”’.
  • App like - Using app shell architecture to provide native app like experience.
  • Fresh - Always up-to-date due to service worker.
  • Connectivity independent - Serves content even when its offline or in slow connections.
  • Discoverable - Manifest and service workers allows search engine to find them.
  • Installable - Add to homescreen.
  • Linkable - Easy to share.

What you will learn

  • What is a App Shell Architecture?
  • Service Worker and its lifecycle.
  • Caching static resources.
  • Native app like features.

Things you need

  • Chrome Browser 46 or above.
  • A Text Editor.
  • Sample code.
  • Basic knowledge of HTML, CSS, Javascript and DevTools.
  • Node (for build process & deployment).

2. Setup

To setup the codelabs, follow the below steps.

Steps

  • Clone the repository via command line.
  • $ git clone -b sample https://github.com/code-kotis/pwa-codelabs.git
  • If you don't have nodeJS installed in your system. Download and install it from below link.
  • Download Node
  • Install the dependencies.
  • $ npm install
  • To run the sample code, type below command in your terminal.
  • $ npm run server
  • Open the localhost server url in your browser.
  • http://127.0.0.1:8000
That's it. Setup is done.

Step 3 - App Shell Architecture

An app shell architecture is minimal level HTML, CSS, Javascript required to power the application user interface.

Components for App Shell

  • Header with icon and title.
  • Hamburger menu.
  • Main section.

HTML for App Shell

<!-- Header  -->
<header class="header">
  <div class="header__container">
    <div class="header__icon">
      <!-- Header Icon  -->
    </div>
    <h1>Title</h1>
  </div>
</header>

<!-- Menu  -->
<div class="menu">
  <div class="menu__header"></div>
  <ul class="menu__list">
    <li><a href="/">Home</a></li>
  <ul>
</div>

<!-- Main Section -->
<div class="main"></div>
Screenshot:
app shell

Why you should use App Shell Architecture?

Performance

By caching the app shell, repeated visits on the application were loading fast. To measure the performance of the app shell, we did some series of tests.
Test 1: Using DevTools
We emulated 3G connection in DevTools Network Panel. After this on the repeated visits, the application were loading within few milli seconds.
Screenshot:
devTools network panel
Test 2: Using Webpagetest
In webpagetest, we measured the same site in Chrome under 3G connection. Load time for repeat visits were 3.015s.
Screenshot:
web page test
See the full result here.
It is clear from the above results, using app shell made the application faster.

Applications using app shell in production


Step 3 - Service Worker

A service worker is a event driven worker which runs in the background and sits in between your application and the browser. It can intercept and handle the network requests for the registered domain.
Tips: Service worker will work only when the page is served via https.
Know more about service worker here.

How to register a service worker?

A service worker is registered by passing the service-worker file (should be in root directory) in the register method which returns a promise like below.
/* In index.html */

// If service worker is supported, then register it
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('./service-worker.js', {scope: './'}) // Scope of the service worker
    .then(function(registration) {
      console.log('service worker is registered!');
    })
    .catch(function(error) {
      console.log('service worker registration failed ', error);
    });
}
else {
  console.log('service worker is not supported.');
}

Service Worker Lifecycle

After successful service worker registration.
  • Install - First event and happens only once.
  • Activate - To clean up unwanted and old caches.
  • Fetch - Triggers for every network request made by your application.

1. Install Event

After registering the service worker, install event is fired. But don’t expect service worker to take control of the page on the first visit, you need to refresh the page to see the effects.
/* In service-worker.js */

var cacheName = 'cache-v1'; //Cache Name

//Files to cache
var filesToCache = [
  './index.html',
  './index.html?utm=homescreen', //query strings are treated as seperate page
  './css/styles.css',
  './js/menu.js',
  'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700', //3rd party resource
];

//Adding 'install' event listener
self.addEventListener('install', function (event) {
  console.log('Event: Install');
  
  // waitUntil method extends the lifetime of an event
   event.waitUntil(
    //Open the cache
    caches.open(cacheName)
      .then(function (cache) {
        //Adding the files to cache
        return cache.addAll(filesToCache)
          .then(function () {
            console.log("All files are cached.");
          })
      })
      .catch(function (err) {
        console.log("Error occurred while caching ", err);
      })
  );
});

2. Activate Event

After successful install event, activate event is fired. Also it can happen on various cases, some of them are.

When an activate event is triggered?

  • If there is no current active service worker.
  • On navigating to a page which is in service worker scope.
  • During the pushsync event etc,.
/* In service-worker.js */

//Adding 'activate' event listener
self.addEventListener('activate', function (event) {
  console.log('Event: Activate');
  
  //Delete unwanted and old caches here
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cache) {
          if (cache !== cacheName) {
            return caches.delete(cache); //Deleting the cache
          }
        })
      );
    })
  );
});

3. Fetch Event

After activate event, whenever the browser requests a resourse within the service worker scope, fetch events is triggered.
/* In service-worker.js */

//Adding 'fetch' event listener
self.addEventListener('fetch', function (event) {
  console.log('Event: Fetch');

  var request = event.request; // request made by the app

  //Tell the browser to wait for network request and respond with below
  event.respondWith(
    //If request is already in cache, return its response
    caches.match(request).then(function(response) {
      if (response) {
        return response;
      }

      //else make a request and add it to cache and return the response
      return fetch(request).then(function(response) {
        var responseToCache = response.clone(); //Cloning the response stream in order to add it to cache
        caches.open(cacheName).then(function(cache) {
            cache.put(request, responseToCache); //Adding to cache
          });

        return response;
      });
    })
  );
});

Browser Support?

Service worker is currently supported in Google ChromeMozilla FirefoxOpera and support for Microsoft Edge is in development. For Safari, it is still under consideration ðŸ¤–.
Browser support
More details about service worker support.

Article References



Step 4 - Offline Experience

Service worker allow us to use cache API to cache the resources and thus by providing offline experience. By caching the app shell, application loads faster on the repeated visits.
Note: Resources cached via cache API can be view in Chrome Dev Tools > Application > Cache Storage.
Refer the below image:
cached resources
More details about Cache API.

Offline/Online Events

By using offline/online events, we can let the user know when he is offline or call an API when he has connectivity again.
/* app.js */

var headerElement = document.querySelector('.header');
var menuElement = document.querySelector('.menu__header');

//Once the DOM is loaded, check for connectivity
document.addEventListener('DOMContentLoaded', function(event) {
  if (!navigator.onLine) {
    goOffline();
  }

  //Offline event listener
  window.addEventListener("offline", function () {
    goOffline();
  });

  //Offline Event
  function goOffline() {
    headerElement.style.background = '#9E9E9E';
    menuElement.style.background = '#9E9E9E';
  }

  //Online Event
  window.addEventListener("online", function () {
    headerElement.style.background = '';
    menuElement.style.background = '';
  });
});
Tips: Emulate offline in Devtools by opening Chrome Dev Tools > Network Tab > Offline.
Screenshot:
app offline

Step 4 - Native App Like

Using web manifest, we can bring the add to homescreensplash screen experience. Add to homescreen gives us the ability to install the web application quickly without having to worry about the size of the application.
manifest.json should contain the following criteria.
  • Site should be in HTTPS.
  • Should have a registered service worker.
  • Should contain a nameshort_name to display in banner and homescreen.
  • Icon should be PNG image and at least 144px in dimension.
  • Add to homescreen banner will show when user should visits your site at least twice with some time intervals in between.
Note: Above listed criertia's will change over the time, for more info Google Developers Site.
Sample Manifest
{
  "name": "Application Name",
  "short_name": "App Name",
  "icons": [{
    "src": "./images/touch/android-chrome-144x144.png",
    "sizes": "144x144",
    "type": "image/png"
  }],
  "start_url": "./index.html?utm=homescreen",
  "theme_color": "#2196f3",
  "background_color": "#fff",
  "display": "standalone",
  "orientation": "portrait"
}
More about manifest in W3C Specification.

Web Manifest Support

Add to homescreen, splash screen are supported in ChromeMozilla FirefoxOpera and Safari as well.
Screenshot:
Manifest File

Step 7. Final - Deploy

The final step of this codelabs is deploying our github cards using surge.sh.

Follow the below steps.

Step 1 - Install surge cli via npm.
$ npm install --global surge
Step 2 - Go to final directory in sample repository.
$ cd final
Step 3 - Type the below command to deploy.
$ surge
After successful deployment, you will get an url in your terminal. Copy it and open in your desktop or mobile browsers 😎.

What next?

Everything is perfect, except the deployed site is loading in HTTP unless we change the url to use HTTPS. So lets fix it by forcing HTTP to redirect to HTTPS.
$ surge --domain https://my-project.surge.sh
Tips: Lighthouse analyzes web apps and web pages, collecting modern performance metrics and insights on developer best practices. Our application score was 91/100.
Thats it all done!!

Comments

Popular posts from this blog

Evolution of Internet of Things(IoT)

Progressive Web Apps(Concept)

Optimizing Rich Content for Web.