A Beginner's Guide To Progressive Web Apps - Smashing Magazine

August 11, 2016 0 Comments progressive

A Beginner's Guide To Progressive Web Apps - Smashing Magazine

Progressive web apps could be the next big thing for the mobile web. Originally proposed by Google in 2015, they have already attracted a lot of attention because of the relative ease of development and the almost instant wins for the application's user experience.

A progressive web application takes advantage of the latest technologies to combine the best of web and mobile apps. Think of it as a website built using web technologies but that acts and feels like an app. Recent advancements in the browser and in the availability of service workers and in the Cache and Push APIs have enabled web developers to allow users to install web apps to their home screen, receive push notifications and even work offline.

Progressive web apps take advantage of the much larger web ecosystem, plugins and community and the relative ease of deploying and maintaining a website when compared to a native application in the respective app stores. For those of you who develop on both mobile and web, you'll appreciate that a website can be built in less time, that an API does not need to be maintained with backwards-compatibility (all users will run the same version of your website's code, unlike the version fragmentation of native apps) and that the app will generally be easier to deploy and maintain.

Why Progressive Web Apps? Link

A progressive web application takes advantage of a mobile app's characteristics, resulting in improved user retention and performance, without the complications involved in maintaining a mobile application.

Use Cases Link

When assessing whether your next application should be a progressive web app, a website or a native mobile application, first identify your users and the most important user actions. Being "progressive," a progressive web app works in all browsers, and the experience is enhanced whenever the user's browser is updated with new and improved features and APIs.

Thus, there is no compromise in the user experience with a progressive web app compared to a traditional website; however, you may have to decide what functionality to support offline, and you will have to facilitate navigation (remember that in standalone mode, the user does not have access to the back button). If your website already has an application-like interface, applying the concepts of progressive web apps will only make it better.

Characteristics Of A Progressive Web App Link

  • Progressive
    By definition, a progressive web app must work on any device and enhance progressively, taking advantage of any features available on the user's device and browser.
  • Discoverable
    Because a progressive web app is a website, it should be discoverable in search engines. This is a major advantage over native applications, which still lag behind websites in searchability.
  • Linkable
    As another characteristic inherited from websites, a well-designed website should use the URI to indicate the current state of the application. This will enable the web app to retain or reload its state when the user bookmarks or shares the app's URL.
  • Responsive
    A progressive web app's UI must fit the device's form factor and screen size.
  • App-like
    A progressive web app should look like a native app and be built on the application shell model, with minimal page refreshes.
  • Connectivity-independent
    It should work in areas of low connectivity or offline (our favorite characteristic).
  • Re-engageable
    Mobile app users are more likely to reuse their apps, and progressive web apps are intended to achieve the same goals through features such as push notifications.
  • Installable
    A progressive web app can be installed on the device's home screen, making it readily available.
  • Fresh
    When new content is published and the user is connected to the Internet, that content should be made available in the app.
  • Safe
    Because a progressive web app has a more intimate user experience and because all network requests can be intercepted through service workers, it is imperative that the app be hosted over HTTPS to prevent man-in-the-middle attacks.

Let's Code! Link

Our first progressive web app, Sky High, will simulate an airport's arrivals schedule. The first time the user accesses our web app, we want to show them a list of upcoming flights, retrieved from an API. If the user does not have an Internet connection and they reload the web app, we want to show them the flight schedule as it was when they last downloaded it with a connection.

The Basics Link

For this demo, we will retrieve a static JSON file, instead of a real API. This is merely to keep things simple. In the real world, you would query an API or use WebSockets.

index.html Link

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Sky-High Airport Arrivals</title> <link async rel="stylesheet" href="./css/style.css"> <link href="https://fonts.googleapis.com/css?family=Roboto:300,600,300italic,600italic" rel="stylesheet" type="text/css"> </head> <body> <header> <div class="content"> <h3>Arrivals</h3> </div> </header> <div class="container"> <div id="main" class="content"> <ul class="arrivals-list" data-bind="foreach: arrivals"> <li class="item"> <span class="title" data-bind="html: title"></span> <span class="status" data-bind="html: status"></span> <span class="time" data-bind="html: time"></span> </li> </ul> </div> </div> <script src="./js/build/vendor.min.js"></script> <script src="./js/build/script.min.js"></script> </body> </html>

The index.html file is relatively standard. We've created an HTML list and bound our View Model property arrivals to it using Knockout, through the attribute data-bind="foreach: arrivals". The View Model arrivals is declared in the page.js file below and exposed in the Page module. On our HTML page, for each item in the arrivals array, we've bound the title, status and time properties to the HTML view.

(var Page = (function() { // declare the view model used within the page function ViewModel() { var self = this; self.arrivals = ko.observableArray([]); } // expose the view model through the Page module return { vm: new ViewModel(), hideOfflineWarning: function() { // enable the live data document.querySelector(".arrivals-list").classList.remove('loading') // remove the offline message document.getElementById("offline").remove(); // load the live data }, showOfflineWarning: function() { // disable the live data document.querySelector(".arrivals-list").classList.add('loading') // load html template informing the user they are offline var request = new XMLHttpRequest(); request.open('GET', './offline.html', true); request.onload = function() { if (request.status === 200) { // success // create offline element with HTML loaded from offline.html template var offlineMessageElement = document.createElement("div"); offlineMessageElement.setAttribute("id", "offline"); offlineMessageElement.innerHTML = request.responseText; document.getElementById("main").appendChild(offlineMessageElement); } else { // error retrieving file console.warn('Error retrieving offline.html'); } }; request.onerror = function() { // network errors console.error('Connection error'); }; request.send(); } } })();

This page.js file exposes the Page module, which contains our ViewModel vm and two functions, hideOfflineWarning and showOfflineWarning. The View Model ViewModel is a simple JavaScript literal that will be used throughout the application. The property arrivals on the ViewModel is Knockout's observableArray, which automatically binds our HTML to a JavaScript array, allowing us to push and pop items onto our array in JavaScript and automatically update the page's HTML.

The functions hideOfflineWarning and showOfflineWarning enable the rest of our application to call these functions to update the page's UI that displays whether we are connected online. The showOfflineWarning adds a class of loading to our arrivals-list HTML element to fade the list, and then it retrieves the HTML file offline.html through XHR. Assuming that the file has been retrieved successfully ( response.status === 200), we append this to our HTML. Of course, if we aren't using service workers and the user is not connected to the Internet, then it would not be possible to retrieve offline.html, and so the user would see the browser's offline page.

The business logic from where we retrieve the data from our API and bind it to our View Models and Views is found in and is standard MVVM functionality using Knockout. In the arrivals.js file, we simply initialize the services and View Models that we will be using throughout the application, and we expose a function - Arrivals.loadData() - that retrieves the data and binds it to the view model.

Web App Manifest Link

  • have a valid web app manifest file,
  • be served over HTTPS,
  • have a valid service worker registered,
  • have been visited twice, with at least five minutes between each visit.

manifest.json Link

{ "short_name": "Arrivals", "name": "Arrivals at Sky High", "description": "Progressive web application demonstration", "icons": [ { "src": "launcher-icon.png", "sizes": "48x48", "type": "image/png" }, { "src": "launcher-icon-96.png", "sizes": "96x96", "type": "image/png" }, { "src": "launcher-icon-144.png", "sizes": "144x144", "type": "image/png" }, { "src": "launcher-icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "launcher-icon-256.png", "sizes": "256x256", "type": "image/png" } ], "start_url": "./?utm_source=web_app_manifest", "display": "standalone", "orientation": "portrait", "theme_color": "#29BDBB", "background_color": "#29BDBB" }

Let's break down this manifest file:

  • short_name is a human-readable name for the application. In Chrome for Android, this is also the name accompanying the icon on the home screen.
  • name is also a human-readable name for the application and defines how the application will be listed.
  • description provides a general description of the web application.
  • icons defines an array of images of varying sizes that will serve as the application's icon set. In Chrome for Android, the icon will be used on the splash screen, on the home screen and in the task switcher.
  • start_url is the starting URL of the application.
  • display defines the default display mode for the web application: fullscreen, standalone, minimal-ui or browser.
  • orientation defines the default orientation for the web application: portrait or landscape.
  • theme_color is the default theme color for the application. On Android, this is also used to color the status bar.
  • background_color defines the background color of the web application. In Chrome, it also defines the background color of the splash screen.
  • related_applications is not implemented in our example but is used to specify native application alternatives in the various app stores.

Add the manifest.json reference to the index.html file's head tag:

Once a user has added the web app to their home screen, they will be able to re-engage with your application immediately from their device, without having to directly open the browser. You can see how this is much more than a web bookmark.

Service Workers

All of this is possible through service workers, which are event-driven scripts (written in JavaScript) that have access to domain-wide events, including network fetches. With them, we can cache all static resources, which could drastically reduce network requests and improve performance considerably, too.

Application Shell Link

The application shell is the minimum HTML, CSS and JavaScript required to power a user interface. A native mobile application includes the application shell as part of its distributable, whereas websites ordinarily request this over the network. Progressive web applications bridge this gap by placing the application shell's resources and assets in the browser's cache. In our Sky High application, we can see that our application shell consists of the top header bar, the fonts and any CSS required to render these elegantly.

To get started with service workers, we first need to create our service worker's JavaScript file, sw.js, placed in the root directory.

// Use a cacheName for cache versioning var cacheName = 'v1:static'; // During the installation phase, you'll usually want to cache static assets. self.addEventListener('install', function(e) { // Once the service worker is installed, go ahead and fetch the resources to make this work offline. e.waitUntil( caches.open(cacheName).then(function(cache) { return cache.addAll([ './', './css/style.css', './js/build/script.min.js', './js/build/vendor.min.js', './css/fonts/roboto.woff', './offline.html' ]).then(function() { self.skipWaiting(); }); }) ); }); // when the browser fetches a URL... self.addEventListener('fetch', function(event) { // ... either respond with the cached object or go ahead and fetch the actual URL event.respondWith( caches.match(event.request).then(function(response) { if (response) { // retrieve from cache return response; } // fetch as normal return fetch(event.request); }) ); });

Let's look more closely at our service worker. First, we are setting a cacheName variable. This is used to determine whether any changes have been made to our cached assets. For this example, we will be using a static name, meaning that our assets will not change or require updating.

self.addEventListener('install', function(e) { // declare which assets to cache }

The install event fires during the installation phase of the service worker and will fire only once if the service worker is already installed. Therefore, refreshing the page will not trigger the installation phase again. During the installation phase, we are able to declare which assets will be cached. In our example above, we are caching one CSS file, two JavaScript files, our fonts file, our offline HTML template and, of course, the application root. self.skipWaiting() forces the waiting service worker to become active.

So far, we have declared our service worker, but before we see it kick into effect, we need to reference it in our JavaScript. In our application, we register it in main.js

// Register the service worker if available. if ('serviceWorker' in navigator) { navigator.serviceWorker.register('./sw.js').then(function(reg) { console.log('Successfully registered service worker', reg); }).catch(function(err) { console.warn('Error whilst registering service worker', err); }); } window.addEventListener('online', function(e) { // Resync data with server. console.log("You are online"); Page.hideOfflineWarning(); Arrivals.loadData(); }, false); window.addEventListener('offline', function(e) { // Queue up events for server. console.log("You are offline"); Page.showOfflineWarning(); }, false); // Check if the user is connected. if (navigator.onLine) { Arrivals.loadData(); } else { // Show offline message Page.showOfflineWarning(); } // Set Knockout view model bindings. ko.applyBindings(Page.vm); 

We've also included two event listeners to check whether the session's state has changed from online to offline or vice versa. The event handlers then call the different functions to retrieve the data through Arrivals.loadData() and to enable or disable the offline message through Page.showOfflineWarning and Page.hideOfflineWarning, respectively. Our application also checks whether the user is currently online, using navigator.onLine, and either retrieves the data or shows the offline warning accordingly. And in the last line of main.js, we apply the Knockout bindings to our View Model Page.vm.

If we load our application for the first time (with Chrome Developer Tools), we will see nothing new. However, upon reloading, we will see that a number of network resource have been retrieved from the service worker. This is our application shell.

Offline Test Link

A user running the application without an Internet connection (assuming that they have already been on the page) will simply result in the application shell and the offline warning being displayed - an improvement over Chrome's prowling t-rex. Once the user has established a network connection, we disable the warning and retrieve the latest data.

The Guardian takes a particularly interesting approach when offline users access its website, providing a crossword puzzle:

Push Notifications Link

28
Push notifications on Emojoy29 (View large version30)

The Push API is supported in Chrome, Opera and Samsung's browser and is under development in Firefox and Microsoft Edge. Unfortunately, there is no indication that the feature will be implemented in Safari.

Performance Link

One of the easiest wins with service workers is that we can improve performance with little to no effort. Comparing our website to itself before service workers were implemented, before we were retrieving over 200 KB upon page load; that is now reduced to 13 KB. On a regular 3G network, the page would have taken 3.5 seconds to load; now it takes 500 milliseconds.

These performance improvements are drastic because the application itself is very small and has limited functionality. Nevertheless, through the correct use of caching, it is possible to significantly improve performance and perceived performance, especially for users in places with low-connectivity.

Lighthouse Link

To run a Lighthouse test, your website needs to be available online, meaning that you cannot test on localhost.

To start, download the npm package:

npm install -g GoogleChrome/lighthouse

Once that's installed, run Chrome (version 52 onwards):

npm explore -g lighthouse -- npm run chrome lighthouse https://incredibleweb.github.io/pwa-tutorial/

The output of the Lighthouse run will be visible in the command line and will grade your website according to the progressive web app features and properties you have implemented - for example, whether you are using a manifest.json file or whether your page is available offline.

Conclusion Link

Cross-Browser Support

These are still early days for progressive web apps, and cross-browser support is still limited, especially in Safari and Edge. However, Microsoft openly supports progressive web apps and should be implementing more features by the end of the year.

  • Service workers and Cache API
    Supported in Chrome, Firefox, Opera and Samsung's browser. In development in Microsoft Edge, expected to be available by the end of 2016. Under consideration for Safari.
  • Add to home screen
    Supported in Chrome, Firefox, Opera, Android Browser and Samsung's browser. Microsoft seems to indicate that progressive web apps will be available as store listings. No plans for Safari as of yet.
  • Push API
    Mostly supported in Chrome, Firefox, Opera and Samsung's browser. In development in Microsoft Edge. No plans for Safari as of yet.

If more developers take advantage of the features offered by progressive web apps - which are relatively easy to implement and provide immediate rewards - then users will prefer consuming these web apps in supported browsers, hopefully convincing the other browser vendors to adapt.

Source Code Link

  1. 1 http://blog.gaborcselle.com/2012/10/every-step-costs-you-20-of-users.html
  2. 2 http://info.localytics.com/blog/push-messaging-drives-88-more-app-launches-for-users-who-opt-in
  3. 3 http://www.flipkart.com/
  4. 4 https://flights.airberlin.com/en-DE/progressive-web-app
  5. 5 #crossBrowser
  6. 6 https://developers.google.com/web/fundamentals/getting-started/your-first-progressive-web-app/
  7. 7 https://www.smashingmagazine.com/wp-content//uploads/2016/08/sky-high-screenshot-opt.png
  8. 8 https://www.smashingmagazine.com/wp-content//uploads/2016/08/sky-high-screenshot-opt.png
  9. 9 http://knockoutjs.com/
  10. 10 https://material.google.com/
  11. 11 https://addyosmani.com/blog/making-a-site-jank-free/
  12. 12 http://knockoutjs.com/documentation/introduction.html
  13. 13 #serviceWorker
  14. 14 https://github.com/IncredibleWeb/pwa-tutorial/blob/master/demo/js/arrivals.js
  15. 15 https://w3c.github.io/manifest/
  16. 16 https://developers.google.com/web/updates/2015/03/increasing-engagement-with-app-install-banners-in-chrome-for-android
  17. 17 https://www.smashingmagazine.com/wp-content//uploads/2016/08/web-app-install-banner-opt.png
  18. 18 https://www.smashingmagazine.com/wp-content//uploads/2016/08/web-app-install-banner-opt.png
  19. 19 https://developer.mozilla.org/en/docs/Web/API/IndexedDB_API
  20. 20 https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/onLine
  21. 21 https://www.smashingmagazine.com/wp-content//uploads/2016/08/app-shell-opt.png
  22. 22 https://www.smashingmagazine.com/wp-content//uploads/2016/08/app-shell-opt.png
  23. 23 https://www.smashingmagazine.com/wp-content//uploads/2016/08/sky-high-offline-opt.png
  24. 24 https://www.smashingmagazine.com/wp-content//uploads/2016/08/sky-high-offline-opt.png
  25. 25 https://www.smashingmagazine.com/wp-content//uploads/2016/08/the-guardian-opt.jpg
  26. 26 https://www.smashingmagazine.com/wp-content//uploads/2016/08/the-guardian-opt.jpg
  27. 27 https://developers.google.com/web/updates/2015/03/push-notifications-on-the-open-web
  28. 28 https://www.smashingmagazine.com/wp-content//uploads/2016/08/pwa-push-opt.png
  29. 29 https://jakearchibald-gcm.appspot.com/
  30. 30 https://www.smashingmagazine.com/wp-content//uploads/2016/08/pwa-push-opt.png
  31. 31 https://github.com/GoogleChrome/lighthouse
  32. 32 https://chrome.google.com/webstore/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk
  33. 33 https://developer.mozilla.org/en/docs/Web/API/Push_API
  34. 34 https://developers.google.com/web/updates/2015/12/background-sync
  35. 35 https://github.com/IncredibleWeb/pwa-tutorial
  36. 36 https://incredibleweb.github.io/pwa-tutorial/

↑ Back to topTweet itShare on Facebook

A Beginner's Guide To Progressive Web Apps - Smashing Magazine


Tag cloud