Skip to content

PWA

Image 01

Video: PWA Introduction

Introduction

PWA (Progressive Web App) is said to be a the next step in mobile apps and web. PWA is actually a web site which can implement similar features as a native application. PWA can be a "usual" web page or for example SPA (single page application). Progressive web application holds technoligies which can move PWA's near to native applications, for example: very fast loading times because main building files can be cached, nice capabilities like push notifications, and PWA's can be installed to devices home screen.

Remember that PWA is done with web technologies so all modern web based technologies can be used. All nice responsive frameworks, programming frameworks (like React) can be used to develop PWAs. Application will/need work from HTTPS, so there are no need to publish apps to any stores. It will be really easy to update, because developer only need to modify code or assets files in the server side.

But there are also some challenges in PWA. It is mainly supported by Chrome, Opera and Samsung's Android browser. IE, Edge and Safari are yet to extend their support, so there are some limitations. There are also some limitations with functionality, PWA doesn’t have support for any hardware that is not supported by HTML5. PWA apps also can't communicate with other installed apps in device.

Google has made a Baseline Progressive Web App Checklist which help teams create the best possible experiences with PWA.

  • Main site: PWA
  • Mozilla's site: PWA

Main building blocks

Web App Manifest

The web app manifest is a simple JSON file that tells the browser about your web application and how it should behave when 'installed' on the user's mobile device or desktop. Having a manifest is required by Chrome to show the Add to Home Screen prompt. Web app manifests are deployed in your HTML pages using a element in the of a document:

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

Web manifests can contain many different keys, but note that some of them are required to show the add to home screen prompt:

  • short_name or name
  • icons must include a 192px and a 512px sized icons
  • start_url
  • display must be one of: fullscreen, standalone, or minimal-ui

In below, there is just a one example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
    "name": "My First PWA",
    "short_name": "First PWA",
    "description": "This is just a test application.",
    "icons": [
      {
        "src": "icons/icon-192x192.png",
        "sizes": "192x192",
        "type": "image/png"
      },
      {
        "src": "icons/icon-512x512.png",
        "sizes": "512x512",
        "type": "image/png"
      }
    ],
    "start_url": "./",
    "display": "standalone",
    "background_color": "yellow",
    "theme_color": "#685f85"
}

Service Worker

Service Worker is just a JavaScript file which runs separately from the main browser thread, intercepting network requests, caching or retrieving resources from the cache, and delivering push messages. Remember that Service Worker doesn't have a direct access to DOM, usually developer uses postMessage() method to send data to main thread. Same goes with synchronous XHR and localStorage, those can't be used because Service Worker is designed to be fully asynchronous.

Service workers enable applications to control network requests, cache those requests to improve performance, and provide offline access to cached content. Service worker depends Fetch and Cache API's. Fetch is just used to get data from the net and cache is persistent content storage for the application data. Caching resources will make content load faster under most network conditions, which is just a one main point of PWA.

Service workers provide the starting point for features that make web applications work like native apps:

A service worker goes through three steps in its lifecycle: Registration, Installation and Activation.

Registration

To install a service worker, developer need to register it in main JavaScript code use in main HTML file. Note that you might need to modify where your service worker is - is it in your web server root or some other "path" level. In below server root is used.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<script>
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);
  });
}
</script>

Installation

Once the the browser registers a service worker, installation can be attempted. A service worker installation triggers an install event in the installing service worker. Usually this install event is used to precache parts of a web app so that it loads instantly the next time a user opens it.

In a below, you can see a small crop of service-worker.js file. This includes install event listener and caches a few application files when installation occurs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
var cacheName = 'myfirstpwa-1';
var filesToCache = [
  './',
  './index.html',
  './offline.html',
  './styles.css'
];
self.addEventListener('install', function(e) {
  console.log('[ServiceWorker] install');
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      console.log('[ServiceWorker] caching app');
      return cache.addAll(filesToCache);
    })
  );
});

Activation

Once a service worker has successfully installed, it transitions into the activation stage. When the new service worker activates, an activate event is triggered in the activating service worker. The primary use of activate is for cleanup of resources used in previous versions of a Service worker script.

If a static cached files need to be replaced as a newer ones, then activate event is useful. Develop need to update Service Worker file which will cause the install event trigger again. Next a new version of cache name will be used which caused to create a new cache object. When a new Service Worker is activate, older cache files can be deleted.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var cacheName = 'myfirstpwa-2';

...
self.addEventListener('activate', function(event) {
  console.log('[ServiceWorker] Activate');
   event.waitUntil(
     caches.keys()
       .then(function(cacheNames) {
         console.log('[ServiceWorker] Activate cacheNames = ' + cacheNames);
          return Promise.all(
            cacheNames.map(function(cName) {
              if (cName !== cacheName){
                 console.log('[ServiceWorker] Activate delete cName = ' + cName);
                 return caches.delete(cName);
              }
            })
         );
     })
  );
});

Fetch

A fetch event fires every time any resource controlled by a service worker is fetched, which includes the documents inside the specified scope, and any resources referenced in those documents. You can attach a fetch event listener to the service worker, then call the respondWith() method on the event to hijack our HTTP responses and update them with your own magic. In below material will be looked first from the cache and returned if exists, if not material will be tryed to load from the net. It also fails, then a offline.html file will be displayed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
self.addEventListener('fetch', function (event) {
  event.respondWith(
    // try cache
    caches.match(event.request)
      .then(function (response) {
        if (response) {
          console.log('[ServiceWorker] Fetch Cache: ' + response.url);
          return response;
        }
        console.log('[ServiceWorker] Fetch Request: ' + event.request.url);
        return fetch(event.request);
      }).catch(function() { 
        console.log("both fail, show a generic fallback");
        return caches.match('./offline.html');
      })
  );
});

Note

Service Worker runs only on HTTPS connections.

Read more: Introduction to Service Worker
Read more: The Service Worker Lifecycle

Check PWA support

You can easily check which Web platform features are supported with different devices and browsers. Just go to What Web Can Do Today web site with your mobile phone browser and check what features are available. This will give you a good picture what PWA can do with browser you are testing.

In a below, there are example screenshots of iOS and Android phone browser features.

iPhone X 12.4.1 Safari 12 PWA 1

Android Galaxy S8 Android 9 Chrome 73.0.3683.90 PWA 2

Like you will notice Android and Chrome supports many more features than iOS and Safari. Apple has said to add more support for PWA in the future, but we will see...

Small example

Video: PWA example video

This is just a demo to show how you can create a PWA app. In this demo you will learn how to

  • Use manifest.json to describe your PWA app
  • Register service working in your index.html
  • Test that your app is really PWA
  • Publish your files to server side
  • Test from mobile phone and install as a PWA

Project folder

Create a folder and name it FirstPWA.

manifest.json

Create a manifest.json file with a below code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
    "name": "My First PWA",
    "short_name": "First PWA",
    "description": "This is just a test application.",
    "icons": [
      {
        "src": "icons/icon-128x128.png",
        "sizes": "128x128",
        "type": "image/png"
      },
      {
        "src": "icons/icon-144x144.png",
        "sizes": "144x144",
        "type": "image/png"
      },
      {
        "src": "icons/icon-152x152.png",
        "sizes": "152x152",
        "type": "image/png"
      },
      {
        "src": "icons/icon-192x192.png",
        "sizes": "192x192",
        "type": "image/png"
      },
      {
        "src": "icons/icon-256x256.png",
        "sizes": "256x256",
        "type": "image/png"
      },
      {
        "src": "icons/icon-512x512.png",
        "sizes": "512x512",
        "type": "image/png"
      }
    ],
    "start_url": "./",
    "display": "standalone",
    "background_color": "yellow",
    "theme_color": "#685f85"
}
  • Here the name (maximum of 45 characters) is the primary identifier of the app and is a required field. The short_name (maximum of 12 characters recommended) is a short version of the app's name. It is an optional field and if not specified, the name will be used, though it will likely be truncated.
  • The description member is a string in which developers can explain what the application does.
  • You need add at least a 192px and a 512px sized icons. You can just download a icons here: icons.zip or create your own. Remember use icons folder as it is described in manifest.
  • After your web app has been added to a user's homescreen, the start_url property in the Web App Manifest determines what page of your app loads first when the user launches your app from the homescreen.
  • The display member is a string that determines the developers’ preferred "display mode" for the website/app. Now we use as a "standalone", the application will look and feel like a standalone application.
  • The background_color defines a placeholeder background color for the application page to display before its stylesheet is loaded and theme_color is a string that defines the default theme color for the application.

index.html

Create an index.html file to your project. This is just HTML file which are used to test all the PWA settings.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html>
  <head>
    <meta name="theme-color" content="#685f85"/>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>My First PWA test</title>
    <link rel="stylesheet" type="text/css" href="styles.css" media="all">
    <link rel="manifest" href="manifest.json">
  </head>
  <body>
    <h1 class="center">My First PWA</h1>
    <p>
      This is just a testing app...
    </p>
    <script>
        if('serviceWorker' in navigator) {
          navigator.serviceWorker.register('./serviceworker.js')
            .then(function() {
                  console.log('Service Worker Registered');
            });
        }
    </script>
  </body>
</html>
  • Look how the manifest.json file is linked inside the index.html file
  • Service Worker need to be registered here in the index.html file

styles.css

Create a small styles for your demo.

1
2
3
4
body {
  background-color: yellow;
  color: black;
}

serviceworker.js

Create a file for the Service Worker as a below code. Remember read Service Worker Introduction.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
var cacheName = 'FirstPWA';
var filesToCache = [
  './',
  './index.html',
  './styles.css',
  './offline.html'
];

self.addEventListener('install', function(e) {
  console.log('[ServiceWorker] install');
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      console.log('[ServiceWorker] caching app');
      return cache.addAll(filesToCache);
    })
  );
});

self.addEventListener('activate', function(event) {
  console.log('[ServiceWorker] Activate');
   event.waitUntil(
     caches.keys()
       .then(function(cacheNames) {
         console.log('[ServiceWorker] Activate cacheNames = ' + cacheNames);
          return Promise.all(
            cacheNames.map(function(cName) {
              if (cName !== cacheName){
                 console.log('[ServiceWorker] Activate delete cName = ' + cName);
                 return caches.delete(cName);
              }
            })
         );
     })
  );
});

self.addEventListener('fetch', function (event) {
  event.respondWith(
    // try cache
    caches.match(event.request)
      .then(function (response) {
        if (response) {
          console.log('[ServiceWorker] Fetch Cache: ' + response.url);
          return response;
        }
        console.log('[ServiceWorker] Fetch Request: ' + event.request.url);
        return fetch(event.request);
      }).catch(function() { 
        console.log("both fail, show a generic fallback");
        return caches.match('./offline.html');
      })
  );
});
  • Cache name will be defined and index and styles will be cached when app run first time.
  • Activate will be called after Service Worker is installed. Once activated, the service worker controls all pages that load within its scope, and starts listening for events from those pages. A specific task can be done in activation event - like clearing the old cache.
  • After a service worker is installed and the user navigates to a different page or refreshes, the service worker will begin to receive fetch events. Now event.respondWith() is used with promise from caches.match(). This method looks at the request and finds any cached results from any of the caches your service worker created. If we have a matching response, we return the cached value, otherwise we return the result of a call to fetch, which will make a network request and return the data if anything can be retrieved from the network. This is a simple example and uses any cached assets we cached during the install step.

Note

Remember this is just a small demo, you definitely need to look 'fetch' more deeply when working with bigger PWA's.

Testing from the web server with Desktop web browser

Copy your files to web server which supports https-connection, for example to LabraNet services fot JAMK students. Open a Chrome browser and type your URL for testing your PWA.

It should display as a normal web page:

PWA 2

Open Browser's developer tools and select Lighthouse. Select Mobile as a target device and run Lighthouse.

Tip

Lighthouse is an open-source, automated tool for improving the quality of web pages. You can run it against any web page, public or requiring authentication. It has audits for performance, accessibility, progressive web apps, SEO and more.

PWA 2

It will take a few time to test your page against Performance, Accessibility, Best Practices, Seo and Progressive Web App. Hopefully you will see success with PWA and notice how Service Worker is registered, Installed and Caching the app.

PWA 2

Testing from the Mobile phone browser

User your mobile phone chrome browser (or safari in iOS) and browse this same web page. You should see the same web page visible and browser will automatically ask it to add to the Home Screen (in Android). In iOS you need to select share button and select Add to HomeScreen option.

PWA 2 PWA 2

After a few seconds, application icon will be visible at the home screen and this PWA can be launched by tapping the icon. Notice how your PWA look likes when launched from the home screen. There are not location URL visible at all. It looks like an "normal" app! Look differences with first and last picture!

PWA 2 PWA 2

Note

So, what we have learned? PWA can be done with any web based technology!

This was just a fast introduction to PWA basics. You can find more information here: Progressive Web Apps and of course you will learn more by doing a course exercises.