A first look at progressive web apps

By Matthew Tyson

Progressive web apps are an innovation of modern web development, pairing the ubiquity of web browsers with the richness of native applications. Specialized features such as service workers increase the complexity of development as compared to a typical web UI, but they provide an enormous benefit in exchange: cross-device, native-like features delivered inside a web browser.

Features of progressive web apps

If you consider the difference between a typical web browser application and an app installed on a laptop or mobile phone, you get a sense of the gap that progressive web apps bridge. A defining feature of installed apps is that they run on the device when there is no network connection. Progressive web apps support similar offline functionality within the browser.

Unlike browser-based web applications, progressive web apps are highly dependent on application type: the application's features play a big role in determining how the PWA is implemented.

Common characteristics of progressive web apps are:

A good use case for a progressive web app is transitioning a web-deployed application to implement PWA features such as offline functionality. Google Docs is an example of a browser-based app that supports "offline mode" when the network is not available. Essentially, the app can save everything locally on the browser and then sync that up with the back end when the browser comes back online.

Of course, synchronizing distributed parts of an app is inherently complicated. Google Docs is a major architectural undertaking, but a more basic application could be a lot simpler. The application's requirements will dictate how involved the PWA implementation must be.

Now that you have a sense of what progressive web apps can do for you, let’s look at how they work.

Installing progressive web apps

A distinctive feature of progressive web apps is they can be "installed" even though they run in the browser. On the front end, you put a link on the device homepage which launches the website in the browser.

Installation is done via a manifest.json file, which describes to the browser the app’s features and its homepage icon. Here's an example of a simple manifest:

{ "name": "My PWA App", "short_name": "My PWA", "icons": [ { "src": "icons/icon-192x192.png", "sizes": "192x192", "type": "image/png" } ], "start_url": "/", "display": "standalone", "theme_color": "#ffffff"}

If the browser finds such a file in the root directory, and the file is valid, it will offer to add a link to the homepage.

Service workers

Service workers are the main avenue for delivering PWA features. The navigator.serviceWorker object gives you access to the Service Worker API. It is only available in a secure (HTTPS) context. A service worker is similar to a worker thread, but it has a more long-lived lifecycle and less access to the DOM and browser APIs.

Think of a service worker as an isolated context that can interact with your main thread (and other workers) via messages and events. It can respond to these events, run network requests, respond to push calls, and store things with the Cache API or with IndexedDB. A service worker can only act on the UI via messages back to the main thread. In a sense, a service worker is proxy middleware that runs in the browser.

Service workers have a unique lifecycle. Specific conditions determine when they will be terminated. They can run and present notifications to the user based on push updates even after the page that spawned them is closed. The service worker lifecycle offers a good overview. You can also find browser-specific information about the termination of service workers; for example, in Chrome.

Service worker events

Essentially, service workers are asynchronous event handlers. They respond to events from the UI or from the back end. When building them, you have to plan for their context to be wiped away between events—you cannot save state in local variables to share between events; instead, you use the cache or a database. Here are all the events a service worker can respond to:

Along with these events, service workers have access to several APIs:

Service worker example

A service worker always begins life by being loaded in a JavaScript file with the navigator.serviceWorker object, like so:

const subscription = await navigator.serviceWorker.register('service-worker.js');

Event subscriptions then happen in service-worker.js. For example, to watch for a fetch event and use the cache API, you could do something like this:

self.addEventListener('fetch', (event) => { const request = event.request; const url = new URL(request.url); // Try serving assets from cache first event.respondWith( caches.match(request) .then((cachedResponse) => { // If found in cache, return the cached response if (cachedResponse) { return cachedResponse; } // If not in cache, fetch from network return fetch(request) .then((response) => { // Clone the response for potential caching const responseClone = response.clone(); // Cache the new response for future requests caches.open('my-cache') .then((cache) => { cache.put(request, responseClone); }); return response; }); }) );});

When the service worker is loaded, self will refer to it. The addEventListener method lets you watch for various events. Inside the fetch event, you can use the Cache API to check if you have the given request URL already cached; if so, you will send that back. If the URL is new, then you make your request to the server and then cache the response. Notice the Cache API eliminates much of the complexity in determining what is or is not the same request. Using the service worker makes all this transparent to the main thread.

Conclusion

Progressive web apps let you deliver your app in a browser and offer capabilities you won't find in a typical browser-based application. On the other hand, you have to deal with more complexity when developing progressive web apps. Most of the native-like things that you can do with a progressive web app involve service workers, which require more work than doing the same thing in a native app using an operating system such as Android or macOS.

Using PWA techniques to achieve the same functionality across multiple target platforms inside the browser is less work than reimplementing the functionality on multiple platforms. With a progressive web app, you need only build and maintain a single codebase, and you get to work with familiar browser standards.

© Info World