The future of web apps
The gap between native apps and web apps is narrowing. Discover the world of progressive web apps, and how they can help our apps level up.
The capabilities available to websites and apps have changed a lot since the early days of static pages. Even in recent times we've seen many significant advancements: native animations, simple flexible layouts (finally), real 3D graphics and streaming video, to name but a few. But there is still more to do, especially if the web is to compete effectively with native platforms.
Spec writers and browser engineers have been looking at the features that make native platforms and their apps so popular, seeing how the web matches up, and working hard to fill any holes in the web platform's capabilities. Essentially it boils down to a handful of killer features, some of which are pretty simple to implement, others not so much. The following isn't an exhaustive list, but it covers the main areas. Native apps are:
- Installable: Having the app available on the device and integrated with the underlying OS feels good – even better if you have a nice little tappable app icons on your homescreen, and the apps open full-screen or in their own chrome. It's the simple things, right?
- Discoverable: Us web nerds don't really like closed app stores, but we can't deny they improve the experience of finding apps – although some argue that even the best closed app stores are failing because of overcrowding.
- Offline: Web connectivity feels ubiquitous in many parts of the world, but it can't be relied on 100 per cent of the time. It is annoying to not be able to use an app at all just because you are offline, and it needn't be that way any more.
- Engaging: Once you've got a user to use your app, half the battle is over. But how do you keep them engaged and on top of the latest updates? Native app developers have long enjoyed the availability of push notifications for updating users.
- Speedy: Native code performs better than web code, or so some say. But as you'll read later on, the gap is closing rapidly.
However, the web also has some particular benefits. It is:
- Linkable: It is possible to link to a web app at a given URL.
- Responsive: With tools like media queries, you can create complex app layouts that work across a variety of form factors (orientation, resolution, screen size, and so on). In fact, the web's default website layout model is inherently responsive, which makes it especially good for handling text.
- Data-centric: With HTML to provide semantics and structure, the web is great at handling data.
- Progressive: You can write web apps that give modern browsers the latest shiny goodness, while still providing an acceptable experience on legacy browsers.
- Secure: The web provides capabilities for apps to be secure, with its single-origin security model, and HTTPS. Creating secure web apps has become even easier thanks to initiatives such as Let's Encrypt.
We want to create a web that allows for the kind of killer features found in native apps, but not at the expense of the basic things that it already does really well. This movement is termed progressive web apps, and is being championed by organisations including Mozilla and Google.
Later on we'll look at the new technologies that are enabling such advancements, but first let's talk a bit about other approaches to helping the web catch up to native.
Note: What's interesting is that native app platforms are working on closing the gap with the web too, from the other direction. For example, with Rube Goldberg-esque contraptions that enable deep linking.
What came before
Apps with native capabilities aren't a completely new concept. For a start, there have been many packaged 'web' app formats appearing in various places – for example Chrome apps – which allow verified packages to be installed on a device so they are usable offline, and have permission to access device hardware and the like.
Get the Creative Bloq Newsletter
Daily design news, reviews, how-tos and more, as picked by the editors.
We have had so-called 'hybrid app' tools for a while, for example Cordova and Electron. These allow developers to write apps using web technologies, and then translate them into native apps that will run on iOS, Android and so on. Tools like this are very popular, but they require several versions of an app to be created and distributed, and there are some limitations on the functionality that such apps can support. Not quite 'write once, deploy anywhere' as we know it.
Also important is the offline-first movement. Advocates have experimented with and evangelised the idea of creating web apps that will run offline by default, and then connect to the network to receive updates only if it is available.
And before moving on, it is worth mentioning the single-page apps movement. In this, one single HTML page acts as the app's container and loads the different views dynamically using XHR (or similar), with the aim of making web apps snappier and more responsive. This is the kind of app model supported by popular frameworks like Ember.js and AngularJS and, along with offline-first, forms the basis of a large part of progressive web apps.
Installation and discoverability
The first area of progressive web apps we'll look at is installation and discoverability. This is made possible using the web app manifest, which is a JSON file containing information about the web app, such as icons, name, and display mode to use when opening the app:
{
"name": "My sample app",
"short_name": "My app",
"start_url": "./?utm_source=web_app_manifest",
"display": "standalone",
"icons": [{
"src": "images/touch/homescreen48.png",
"sizes": "48x48",
"type": "image/png"
}]
...
This can be used to integrate the app more with the underlying OS (icon on homescreen, opens fullscreen instead of in the browser app, and so on), and in addition provides better discoverability on app listings – for example, it will appear in search engine results.
This information is already used by Opera and Chrome on mobile for platform integration, with Firefox support following soon.
Offline
The web's offline problem has persisted for an incredibly long time, considering how simple it appears to be at first glance. The trouble is that implementing an effective offline technology set is far from simple. Storing an app's data offline is not too much of a problem – modern browsers have had APIs like IndexedDB for a while now, with web storage being supported as far back as IE8.
Asset storage, on the other hand, is a different ball game. For a while it looked like AppCache was going to solve this problem, but it proved not to be. Far too much was assumed about the way an app would be set up, and the developer didn't have enough control over what was happening.
Fast-forward to now, and we have Service Workers; a new technology available in Firefox, Chrome and Opera that allows developers to write offline apps in the same vein as AppCache, plus a lot more besides. It is a much lower-level API, and consequently the syntax is a lot more complicated, but it does give developers a lot more power over how they want their Service Worker interactions to work.
A Service Worker takes the form of a chunk of JavaScript written in a separate file and running in a special worker thread. It acts as a proxy server, sitting in between the network and your app (and the browser). When a request is made for an asset, the Service Worker can intercept it and customise the response before it is sent back to the browser to be used.
To use a Service Worker, you first need to register it by running navigator.serviceWorker.register() in the main thread.
navigator.serviceWorker.register('sw.js')
.then(function(reg) {
// Do something to initialise app
});
Then install the app using an install event listener over in the Service Worker thread. This often involves storing all the app's essential assets using the Cache API (which incidentally can be used independently of Service Workers):
this.addEventListener('install', function(event) {
event.waitUntil(
caches.open('v1').then(function(cache) {
return cache.addAll([
'/sw-test/',
'/sw-test/index.html',
'/sw-test/style.css',
'/sw-test/app.js'
]);
})
);
});
Next, use a fetch event listener in the Service Worker to fire code whenever a request happens. This allows developers to customise the resulting response. So a developer could cache each request for files that don't change much (such as the UI shell) in the install step, and then serve the versions stored offline in the cache when they are requested, instead of the network version. Bingo, offline apps!
this.addEventListener('fetch', function(event) {
var response;
event.respondWith(caches.match(event.request).catch(
// write code to handle case where the requested file
// isn't already stored offline in the cache
);
});
It's worth noting that Service Workers will only work over an HTTPS connection.
User re-engagement
Native platforms have long relied on push messaging – the ability to push a message from the server to the client to tell a user that something has changed, or that something new is available – even when the app is not open. Maybe a news app wants to share breaking news with the user? Or perhaps your Pokémon is bored and wants some attention? Well, thanks to the web Push API, we now have this capability for web apps too.
To use web push, a Service Worker must be installed and active on your app's page(s), as explained previously. Then, we subscribe to the push service. This can be done by using ServiceWorkerRegistration.pushManager.subscribe(), as shown below:
navigator.serviceWorker.ready.then(function(reg) {
reg.pushManager.subscribe({userVisibleOnly: true})
.then(function(subscription) {
// Update UI, etc. in light of subscription
// Update status to subscribe current user on server
var endpoint = subscription.endpoint;
var key = subscription.getKey('p256dh');
updateStatus(endpoint,key,'subscribe');
})
});
This method returns a promise that resolves with a subscription object. This object has an endpoint property (which contains a unique URL pointing to a push server), and a getKey() method, which can be used to generate a public key for encryption purposes. You'll need to access both of these, then send the results to your app's server.
To send a push message on the server, make a request to the endpoint URL. This causes the push server to send a message to the app's Service Worker. If you want to send data along with the push message, it must be encrypted using the key. Push message data currently only works on Firefox, but Blink-based browsers shouldn't be far behind.
The Service Worker listens for a push message using a push event handler. Once a push event is fired, you can access the message data using the event object's data property. Then you can respond to the message however you like – for example by firing a notification, or sending a channel message back to the main thread to update your app in some way.
self.addEventListener('push', function(e) {
var obj = e.data.json();
// Respond to the push message in some way
// e.g. a notification or channel message
});
Note: Most of the Service Workers family of technologies (Push and so on) enjoy reasonable support across Chrome and Firefox, with other browsers considering them carefully. Push data only works in Firefox at the time of writing.
Performance
Performance has long been a sore point between web apps and native apps, with native developers dismissing the web's performance capabilities. But the divide is significantly narrower in the modern day, with much faster JavaScript engines in browsers, and fast in-browser code for running things like animations and 3D graphics, which can increasingly take advantage of available GPU power.
In addition, we are now capable of transpiling native code (such as C++) across to JavaScript, using technologies like Emscripten. When we say JavaScript here, we are talking about asm.js, a highly-optimisable subset of JavaScript that can take advantage of AOT compilation techniques to provide near-native performance. This has proved so popular that it is being supported in not only Firefox, but Chrome and Edge too, and asm.js/web has been added as a publishing target for popular 3D engines Unity and Unreal.
And the next generation is on its way – a number of browser vendors are collaborating on speccing out and implementing WebAssembly, which will do a similar thing to Emscripten, but in a faster, more standard, more performant way.
It will use a format called wasm, rather than asm.js. Given that it will have a concrete spec, we should be able to rely on compilers outputting valid wasm, and browsers then running that output consistently, regardless of which compiler created it. Watch this space.
Conclusion
As you can see, the web is continuing to evolve, with some exciting new technologies designed to address some of the remaining shortcomings our beloved platform has traditionally suffered, as well as boasting even better performance. These will allow web apps to stand proud beside native apps, while not losing sight of what made the web great to begin with.
This article originally appeared in net magazine issue 282; buy it here!
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1