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:
If you don't have nodeJS installed in your system. Download and install it from below link.
Download Node
Install the dependencies.
To run the sample code, type below command in your terminal.
Open the localhost server url in your browser.
Refer the below image:
Screenshot:
Sample Manifest
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
$ npm install
$ npm run server
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:
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:
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:
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
- Inbox by Gmail
- Flipkar Lite
- Housing.com mobile site
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 push, sync 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 Chrome, Mozilla Firefox, Opera and support for Microsoft Edge is in development. For Safari, it is still under consideration 🤖.
More details about service worker support.
Article References
- Web Fundamentals - The Service Worker Lifecycle
- Smashing Magazine - Making a service worker
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.
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.
Step 4 - Native App Like
Using web manifest, we can bring the add to homescreen, splash 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 name, short_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.
{
"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 Chrome, Mozilla Firefox, Opera and Safari as well.
Screenshot: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
Post a Comment