Monday, November 27, 2017

Tutorial for Developing a Progressive Web App With ReactJS

In this tutorial, we'll learn how to create a simple React app, and use another tool to help us test it along the way. Let's get to it!

Progressive Web Apps (PWAs) have rapidly grown in popularity as essentially fast, performance-based web applications streamlined for delivering mobile app-like experiences. PWAs are built using HTML, CSS, and JavaScript to create a level of usability and performance that’s in parity to native mobile applications. They are responsive, consume lesser data and storage space, and supports push notifications and offline usage in the browser.
Building a progressive web app has now become a web development trend every enterprise wants to follow. Big players such as Twitter and Flipboard recently launched their progressive web apps to deliver mobile experiences to users, without requiring them to actually install the app. In this article, you will learn how to build a progressive web app using React. So, let’s get started.

Step 1 – Setting Up a React App

First, generate a React app with create-react-app. To do so, you need to run the following commands:
npm install -g create-react-app
create-react-app pwa-app
Now install React Router:
cd pwa-app
npm install --save react-router@3.0.5
You need to replace the “src/App.js” content using the below code to get a basic template with navigation.
import React, { Component } from 'react';
import { Router, browserHistory, Route, Link } from 'react-router';
import './App.css';
const NavBar = () => (
  <div className="navbar">
    <Link to="/">Feed</Link>
    <Link to="/profile">Profile</Link>
  </div>
);
const Template = ({ title }) => (
  <div>
    <NavBar />
    <p className="page-info">
      This is the {title} page.
    </p>
  </div>
);
const Feed = (props) => (
  <Template title="Feed"/>
);
const Profile = (props) => (
  <Template title="Profile"/>
);
class App extends Component {
  render() {
    return (
      <Router history={browserHistory}>
        <Route path="/" component={Feed}/>
        <Route path="/profile" component={Profile}/>
      </Router>
    );
  }
}
export default App;
Now, you will have to update the default styles by replacing your “src/App.css” with the below styles to make you app look clean.
.navbar {
  background-color: #01C8E5;
  text-align: center;
}
.navbar a {
  display: inline-block;
  padding: 10px;
  color: #fff;
  text-decoration: none;
}
.page-info {
  text-align: center;
  font-weight: bold;
}
Then run the “npm start” to test the app in the browser. This is a basic app with two routes. You will now convert it into a PWA.

Step 2 – Installing Lighthouse and Audit

Lighthouse is an automated, open source tool for testing applications against the PWA checklist. It facilitates audits for accessibility, performance, and more.

Test your app with Lighthouse. Click the Lighthouse icon from the top right corner in Chrome and then click on the ‘Generate Report’ button. The generated report will look like this:
Installing Lighthouse and Audit
You will have to fix all the failed audits. 

Step 3 – Registering a Service Worker

Service workers are the proxy servers that connect the app and network. With Service Worker, you will have to intercept network requests and save cached files. This will enable your app to work even when the network is unavailable. 

Create a blank worker.js file in your app’s public folder and add the following code to that file:
// Flag for enabling cache in production
var doCache = false;
var CACHE_NAME = 'pwa-app-cache';
// Delete old caches
self.addEventListener('activate', event => {
  const currentCachelist = [CACHE_NAME];
  event.waitUntil(
    caches.keys()
      .then(keyList =>
        Promise.all(keyList.map(key => {
          if (!currentCachelist.includes(key)) {
            return caches.delete(key);
          }
        }))
      )
  );
});
// This triggers when user starts the app
self.addEventListener('install', function(event) {
  if (doCache) {
    event.waitUntil(
      caches.open(CACHE_NAME)
        .then(function(cache) {
          fetch('asset-manifest.json')
            .then(response => {
              response.json();
            })
            .then(assets => {
              // We will cache initial page and the main.js
              // We could also cache assets like CSS and images
              const urlsToCache = [
                '/',
                assets['main.js']
              ];
              cache.addAll(urlsToCache);
            })
        })
    );
  }
});
// Here we intercept request and serve up the matching files
self.addEventListener('fetch', function(event) {
  if (doCache) {
    event.respondWith(
      caches.match(event.request).then(function(response) {
        return response || fetch(event.request);
      })
    );
  }
});
Now check if the browsers support the service workers and thereafter register worker.js. To do this, you need to add the following script to public/index.html (Note that “shrink-to-fit=no” from viewport meta tag has been removed).
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="theme-color" content="#000000">
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <title>React App</title>
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
    <script>
      if ('serviceWorker' in navigator) {
        window.addEventListener('load', function() {
          navigator.serviceWorker.register('worker.js').then(function(registration) {
            console.log('Worker registration successful', registration.scope);
          }, function(err) {
            console.log('Worker registration failed', err);
          }).catch(function(err) {
            console.log(err);
          });
        });
      } else {
        console.log('Service Worker is not supported by browser.');
      }
    </script>
  </body>
</html>
You must restart your app and reload the browser after which you will notice the “Worker Registration Successful” message in the developer console. Now regenerate the Lighthouse report.

Step 4 – Improving Progressive Nature of the App

Your app will render an empty root div until the JavaScript loads and React hooks the initial route. You must ensure your app works without any JS loading and displays a bit of CSS and HTML before React comes into play. Your updated index.html will look like this:
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="theme-color" content="#000000">
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <title>React App</title>
    <style type="text/css">
      body {
        margin: 0;
        padding: 0;
        font-family: sans-serif;
      }
      .navbar {
        background-color: #01C8E5;
        text-align: center;
      }
      .navbar a {
        display: inline-block;
        padding: 10px;
        color: #fff;
        text-decoration: none;
      }
      .page-info {
        text-align: center;
        font-weight: bold;
      }
    </style>
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root">
      <div class="navbar">
        <a href="/">Feed</a>
      </div>
      <p class="page-info">
        Loading an awesome app...
      </p>
    </div>
    <script>
      if ('serviceWorker' in navigator) {
        window.addEventListener('load', function() {
          navigator.serviceWorker.register('worker.js').then(function(registration) {
            console.log('Worker registration successful', registration.scope);
          }, function(err) {
            console.log('Worker registration failed', err);
          }).catch(function(err) {
            console.log(err);
          });
        });
      } else {
        console.log('Service Worker is not supported by browser.');
      }
    </script>
  </body>
</html>
Now use Lighthouse to re-audit your app and you will notice an improvement in the app's performance.

Step 5 – Adding Splash Icons

You’re required to add a 512x512 icon to show up on the splash screen. To do so, you will have to update the manifest.json and add the icon in the public folder.
{
  "short_name": "React App",
  "name": "Create React App Sample",
  "icons": [
    {
      "src": "icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {  
      "src": "icon-512x512.png",  
      "sizes": "512x512",  
      "type": "image/png"  
    }
  ],
  "start_url": "/",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}
Also, use the following meta tags to allow the browser to identify that your app is a PWA.
<!-- Tell the browser it's a PWA -->
<meta name="mobile-web-app-capable" content="yes">
<!-- Tell iOS it's a PWA -->
<meta name="apple-mobile-web-app-capable" content="yes">

Step 6 – Deploying the PWA

Now, just the HTTPS is missing and caching can be fixed after you deploy the app. Update the doCache flag with ‘true’ in the worker.js file. Create a new project in firebase-console and name it 'Pwa App.' Then run the following command in the project directory:
npm install -g firebase-tools
firebase login
firebase init
Your firebase.json should look like this:
{
  "hosting": {
    "public": "build",
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}
After initialization, build and deploy your app.
npm run build
firebase deploy
You will see the following outcome after you audit the app using Lighthouse on the deployed URL.
Deploying the PWA using Lighthouse
Finally, you’ve created your first progressive web app with ReactJS!