Understand Service Workers of a PWA (Part 1)

11 minute read

PWA is the abbreviation for Progressive Web App. A progressive web app is a web application that has the characteristics of a native app. PWAs are supported by all major web browsers (e.g. Chrome, Firefox or Safari). This results in the great advantage of platform independence. PWAs work across all platforms because they are web apps. So you can say “The web is the platform”.

Understand Service Workers of a PWA (Part 1)
Generated with Bing DALL-E

The article shows how you can implement a PWA with JavaScript. We use no frameworks like Vue, React or Angular. First, we introduce the basic features of a PWA. Then we show how individual features of a PWA can be implemented from scratch. In this article, we focus on the implementation of the Service Worker (SW). In another article, we will look at the offline capability of PWAs. Be curious!

The steps are the following:

  1. What is a PWA?
  2. Web App Manifest
  3. Service Worker
  4. Application Shell
  5. Conclusion

What is a PWA?

A PWA has the following features:

  • Progressive (logical): Independence of the web browser
  • Responsive: Scalable to any screen (desktop, smartphone or tablet)
  • Offline support: Offline use is possible
  • Up-to-date: No manual updates
  • Secure: Use of HTTPS
  • Findable: SEO optimization
  • Customer loyalty: Support for push notifications
  • Installable: Add to the home screen
  • Linkable: Distribution via URLs is possible

Our Online Courses and recommendations


Web App Manifest

The manifest defines the app’s appearance on the home screen and in the app switcher. Furthermore, a JSON file defines the manifest. You can include the manifest in the head of the HTML document with the following HTML code:

<link rel="manifest" crossorigin="use-credentials" href="./manifest.json">

The link tag href ensures that the browser can load the JSON file. The following code shows an example web app manifest:

{
    "dir": "ltr",
    "lang": "de-DE",
    "name": "App Name",
    "short_name": "Short App Name",
    "description": "An example web app manifest for a PWA.",
    "scope": "/",
    "start_url": ".",
    "display": "standalone",
    "orientation": "portrait",
    "background_color": "white",
    "theme_color": "#FFFFFF",
    "icons": [
      {
        "src": "192x192.png",
        "type": "image/png",
        "sizes": "192x192"
      },
      {
        "src": "512x512.png",
        "sizes": "512x512",
        "type": "image/png"
      }
    ],
    "author": {
      "name": "Your Name"
    }
  }

The listing above shows:

  • (optional) "dir": Indication of the writing direction of the content ("ltr" -> left to right)
  • (optional) "lang": Abbreviation for the language
  • "name": Name of the application
  • "short_name": Short name of the application
  • "description": Description of the application
  • "scope": This property defines the navigation scope. This is a URL that specifies the permitted URLs. If you set https://example/foo/ as the scope, the PWA can only use the URLs below this URL. The URL https://example/ is not usable.
  • "start_url": This property specifies the start URL that the browser should load.
  • "display": This entry defines the display mode. The "standalone" display mode makes the PWA appear like a native app on the desktop or smartphone. The web application finally runs without browser elements on the desktop and the smartphone.
  • "orientation": This entry determines the screen orientation. The value "portrait" specifies that we use portrait mode.
  • "background_color": Definition of the background color
  • "theme_color": The browser can use this property to color parts of the user interface according to a color value.
  • "icons": In this entry, you can define icons. An image resource consists of the specifications "src", "sizes", and "type". The entry "src" specifies the URL of the image file. The entry "sizes" specifies the dimensions of the image file. Furthermore, you can specify the MIME type of the image resource via "type". The RealFaviconGenerator is a great generator for creating icons in different sizes.
  • "author": Indication of the authors

You can learn more about the Web App Manifest on the page Add a web app manifest.

Splashscreen

Some web browsers allow you to bridge the loading time when starting a PWA by displaying a splash screen, which makes the application appear more native. However, this display is not a feature of the Web App Manifest. Some browsers implement this with the properties of the manifest. Not every browser that supports the manifest displays a splash screen.

Chrome Android: Google recommends the following to support a splash screen:

  • You have to set the property "name" in the manifest.

  • You have to set the property "background-color" to a valid color value in the manifest.

  • The icons array must contain at least one icon with the dimensions 512x512. Furthermore, this icon must be in PNG format.

Safari iOS:
On iOS, the display of splash screens is not supported (only) via the Web App Manifest but via proprietary means. In this context, you have to insert some elements into the HTML header of the HTML document. The following listing shows an excerpt of the implementation:

<meta name="apple-mobile-web-app-capable" content="yes">     
<link href="splashscreens/iphone5_splash.png" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image" />   
<link href="splashscreens/iphone6_splash.png" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image" />   
<link href="splashscreens/iphoneplus_splash.png" media="(device-width: 621px) and (device-height: 1104px) and (-webkit-device-pixel-ratio: 3)" rel="apple-touch-startup-image" />    
<link href="splashscreens/iphonex_splash.png" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)" rel="apple-touch-startup-image" />   
<link href="splashscreens/iphonexr_splash.png" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2)" rel="apple-touch-startup-image" />

You can generate splash screens for iOS with the iOS Splash Screen Generator.

Installation of a PWA

Progressive Web Apps can display a banner to the user for installation. However, this is not a classic installation but merely an entry in the program list. You can add the PWA to the program list via the button “Add to start screen”. To do this, you have to activate a flag in the Google Chrome browser for Mac and Linux under chrome://flags/#enable-desktop-pwas. You can also remove a PWA from the program list. In Google Chrome, you have to enter chrome://apps in the address bar and you get an overview of all installed apps. You can remove the app with a right-click on the app.

The following code listing shows the installation routine:

// javascript button for add to homescreen
const installButton = document.getElementById('install');
let deferredPrompt;

// prompt install banner
window.addEventListener('beforeinstallprompt', evt => {
    event.preventDefault();
    deferredPrompt = evt;
    installButton.style.display = 'block';
});

// wait for click event
installButton.addEventListener('click', async () => {
    await deferredPrompt.prompt();
    const choiceResult = await deferredPrompt.userChoice;
    console.log(choiceResult.outcome);
});

Web applications can use the beforeinstallprompt event to inform themselves whether the requirements for installing the web page are there. The variable deferredPrompt stores the prompt for installation and the method window.addEventListener('beforeinstallprompt') registers the beforeinstallprompt event. The call event.preventDefault() prevents an automated banner call by the browser. The line installButton.style.display='block' makes the button visible.

The method installButton.addEventListener('click'...) registers the click event for the button. The line await deferredPrompt.prompt() activates the installation notice (banner) when you click the button. The argument await deferredPrompt.userChoice evaluates the user choice.

You can learn more about the Installation of a PWA on the page How to provide your own in-app install experience.

Service Worker

General

The Service Worker (SW) is used to implement the offline functionality and for processing push notifications. In addition, the SW runs in its own thread and cannot access the Document Object Model (DOM). It has its own cache and can connect to any outgoing network traffic. When a network request is there, the SW decides whether to fetch the data from its cache or forward the request to the network. The SW is independent of a tab or window. The web browser can call the SW when a network request is there. The SW thus serves as a background service and is powerful. That is the reason why you can install it only via HTTPS. The only exception is the localhost for development purposes.

SW Registration

First, we register the SW:

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./sw.js')
            .then(reg => console.log(reg))
            .catch(err => console.log(err));
}

The JavaScript code shown above queries whether the browser supports SWs or not. If the browser supports SWs, the SW is registered. The JavaScript file sw.js implments the SW.

We can divide the lifecycle of an SW into the following:

  • Install
  • Activate
  • Fetch

SW Installation

The following code installs the SW on the first visit to the PWA.

self.addEventListener('install', function(event) {
    console.log('[Service Worker] Installing...');
    event.waitUntil(
        // open the cache
        caches.open(cacheName).then(function(cache) {
            // add files to the cache
            return cache.addAll(filesToCache);
        })
    );
});

The installation of the SW takes place during registration. The install event is triggered in the script sw.js. The example shows how the developer can register for this event using the method addEventListener(). The method addEventListener() is called on the global SW scope self. Furthermore, the method waitUntil() ensures that the SW remains in the installation phase until the required steps within the function are completed. In the installation step, the SW can prepare its cache by loading some resources into its cache.

The variable filesToCache contains an array with all files for caching.
The variable cacheName stands for the name of the cache. In the Chrome browser, you can display the installed SWs under the link chrome://serviceworker-internals.

SW Activation

For the activation of the SW, see the following code:

self.addEventListener('activate', function(event) {
    /* e.g. open a database, delete the cache or do something else */  
    console.log('[Service Worker] Activating...');
    event.waitUntil(
        caches.keys().then(function(cacheNames) {
            return Promise.all(cacheNames.map(function(key) {
                if( key !== cacheName) {
                    console.log('[Service Worker] Removing old cache', key);
                    return caches.delete(key);
                }
            }));
        })
    );
    return self.clients.claim();
});

First, the developer must register for the activate event using the addEventListener() method. In this event, you can open a database, e.g. IndexedDB (see next article about Offline Storage) or delete the SW cache. While this script is running, the SW updates its cache. The method self.clients.claim() ensures that the SW immediately gains control of the web application.

SW Fetch

The following code runs when there is an outgoing network request:

self.addEventListener('fetch', function(event) {
    console.log('[Service Worker] Fetch', event.request.url);
    event.respondWith(
        caches.match(event.request).then(function(response) {
            return response || fetch(event.request);
        })
    );
});

The event fetch is a functional event, i.e. this event is only executed when the SW is already installed and activated.

First, the SW registers for the event fetch to be able to intercept a network request. First, the SW searches for an answer in its cache. If the SW finds an answer, then the SW answers it accordingly. If the SW does not find an answer in the cache, then the SW issues a network request. That is transparent to the application code.

The property request is available on the argument event. This property allows access to the network request. The SW can return an HTTP response using the respondWith() method.

You can learn more about Service Workers on the page Progressive Web Apps: Going Offline.

Application Shell

An application shell (App Shell) represents the user interface. That includes the menus, the title and the status bar of a PWA. The source files of the app shell should always be kept offline so that the PWA acts like a native app. The basic framework of the PWA can therefore be loaded immediately at the start of the app, even if there is no internet connection. Furthermore, the user of the PWA saves data volume because the basic framework is always available offline. In addition, the App Shell implements the navigation elements that connect different layers of the PWA. The PWA is more reliable and native with an offline-capable App Shell.

You can learn more about the App Shell on the page Architecture.

Conclusion

In this article, we learned how to define a Web App Manifest. We also discussed the source code on how to install a PWA. Then we looked at the lifecycle of the SW. That consists of Install, Activate and Fetch. Finally, we introduced the Application Shell as a concept. We have provided links to each section, which you can check out for more details.

Thanks so much for reading. Have a great day!

Additional references and links


💡 Do you enjoy our content and want to read super-detailed articles about data science topics? If so, be sure to check out our premium offer!


Leave a comment