You've probably already heard about Progressive Web Apps, but just to refresh, you are simply building an application from your website. If your application is PWA compatible, users should be able to add a shortcut (link) to the app to their home screen (desktop). The app should also work to some extent off-line, which is done with a help of the Service Worker. Note that by complying with PWA rules you will improve experience for all your users! Not just for mobile users.
In this article I will talk about some basics of transforming an existing website into a PWA. I'm saying those are the basics, but in fact, within a day or two you should be able to create a fully functional PWA. You need to avoid some traps on your way, but I'll try to guide you through this minefield.
This article is part 1 of PWA series. Part 2: PWA and HTTP Caching.
Let's stop briefly and talk about if this is something that can be used now and is going to stay. In other words – is it worth your time. My answer is yes, but don't take my word for it. I'll back it up with references for you and your boss/bosses :-).
Google is highly committed to PWA from the beginning. Not surprisingly, because they basically invented it... with some help from Jake Archibald. If the name doesn't ring a bell, Jake Archibald is the one whom you might know from shouting at Application Cache (PWA predecessor) at many conferences. He didn't only criticize it, but also (with help from Google) put out a solution for us to use.
Adding PWA to home screen was first implemented in Chrome 42 and later improved to current version in Chrome 57 (Paul Kinlan. "The New and Improved Add to Home screen". Developers Google. 2017). Note that Chrome for Android was first introduced in Android 4.0 Ice Cream Sandwich (Mat Smith. "Google Chrome Beta arrives on Android (video)". Engadget. 2012). Compatibility of Chrome versions are not clear, but I was able to install and test Chrome 64 on Android 4.1.2 (Xperia S).
So basically with Android 4.1+ you should be fine.
Microsoft seems fairly committed to PWA. They made a PWA Builder to help and build PWA. They are even running bots over the network to recognize apps and add them to Windows Store. Applications should show up in Windows Store in April 2018 (Paul Thurrott. "Microsoft’s Bold Plan to Bring PWAs to Windows 10". Thurrott. 2017).
So how about the bad boy of the Internet? Well, I might surprise you. Apple decided to implement Service Worker in its recent update to iOS (Prathik S Shetty. "Progressive web apps (PWAs) are coming to a Safari near you". Medium. 2018). Since iOS 11.3, both Web App Manifest and Service Worker should work. There are some additional effects in how Service Worker Cache behaves, it can be cleared when the app is not used, but main takeaway is that it works (Maximiliano Firtman. "PWAs are coming to iOS 11.3: Cupertino, we have a problem". Medium. 2018).
I don't mention Mozilla Firefox above because although it is a major browser, Mozilla doesn't have a major system currently. But do note that crucial standards are supported by Firefox and adding apps to home screen does work on Firefox for Android.
See more on browser compatibility on MDN: Service Worker API compatibility and Web App Manifest compatibility.
You can find some basic guidelines e.g. from Google (what is needed to install the app) and Microsoft (PWA Minimum Requirements).
I will not go into technical depth here. What you need to know is that the bare minimum is:
There are some quirks in how manifest works. I really recommend using Lighthouse for testing your manifest. It will do some tests that will hurt your feelings ;-), but I do recommend it. Do note that you don't need to score 100% in all tests. But read the notes carefully.
If, for example, you use SVG for an icon, you might be surprised that Lighthouse will complain about missing sizes.
So according to W3C Manifest Standard this should work:
{ "icons": [ { "src": "/icon/favicon.ico", "sizes": "16x16 32x32" }, { "src": "/icon/appicon.svg", "sizes": "33x33" } ] }
But it might not work (or seems not to work). You still should provide sizes to match some common sizes. Like so:
{ "icons": [ { "src": "/icon/favicon.ico", "sizes": "16x16 32x32" }, { "src": "/icon/appicon.svg", "sizes": "48x48 72x72 96x96 114x114 128x128 144x144 192x192 256x256 512x512" } ] }
Frankly, I think this should be fixed... But for now just paste the list and you're done.
If you are lost and don't know what can be put in a manifest files, I recommend using MDN's Web App Manifest documentation. They describe each field of the manifest in detail.
Service Worker is in fact a set of APIs (see more on MDN). What you need to know is that most of it is about caching and taking over requests. Yes, it can take over requests sent to the server and respond with something else. That's how your application can still work without the network.
It's also worth mentioning that Service Worker Cache makes Application Cache obsolete. You don't need to know what Application Cache is, but if you do know Application Cache Manifest then you might be worried that Service Worker is too much. I know I was. I admit I liked Application Cache mainly because of its simplicity. My first thought was that Service Worker does solve some problems, but it is just too complicated... It turned out I was wrong. There are some tools out there to help you get started really quickly.
It kind of depends on what you are working with. If you use some JavaScript framework (like Angular 2+) then you might want to use configuration recommended for that framework. You could also use Service Worker generator called sw-precache which should integrate nicely if you are using gulp or webpack for bulding your application.
But Service Worker is not really dependent on any framework. You can set it up manually and it will still work fine. For example you can use basic example from Google with minor tweaks.
Basic example from Google provides caching that will be fine for any application with pre-built (merged) scripts. You should be merging your scripts already anyway so this should be fine in most cases.
service-worker.js
.PRECACHE_URLS
array to a list of scripts and styles that are loaded on your first page load.if ('serviceWorker' in navigator) { navigator.serviceWorker.register('service-worker.js'); }
That's it. Your application should work offline already. You might need to take a moment to tweak this and add more urls to PRECACHE_URLS
as required. For example, you might want to add things users can switch to, like a CSS file from alternative theme.
Note that Service Worker will work in all subfolders. If your static code is in a different folder then your API, then you will probably be fine. If however your app is in the main directory and API is in a subdirectory then you might in trouble.
Let's assume this is true:
https://example.com/my-static-app/ – all your static application code is here (JS, CSS, HTML) https://example.com/my-api/ – this is API
Assuming you don't want to cache API you would place service-worker.js
in my-static-app
folder. My advice is to not try to cache API at first.
More complicated case:
https://example.com/ – your static application code is here (JS, CSS, HTML) https://example.com/my-api/ – this is API
In this case service-worker.js
must be placed in the main (root) folder. This means anything in my-api
folder will be cached. And so only first call to /my-api/user/login
would be sent to the server. Subsequent requests would be served from cache. But don't panic. Adding exceptions to SW cache is easy. You just need to return early from fetch
event. And as we want to add an API exception we might as well add exception for POST/PUT and similar requests. Note that attempting to cache POST requests in Chrome will result in an error (at least in Chrome 65), but most of the time you would only want to cache GET requests anyway.
self.addEventListener('fetch', event => { // skip non-GET requests if (event.request.method !== 'GET') { return; } // local request if (event.request.url.startsWith(self.location.origin + '/my-api/')) { const localUrl = event.request.url.replace(self.location.origin, ''); if (localUrl.startsWith('/my-api/')) { return; } } // ... // ... });
Theoretically you actually might want to cache some API requests (especially if API is used to download translations for GUI)... But trust me – you will want to cache too little rather then too much.
You will notice that in the basic example described above there are two distinct caches – one with a precache-v1
key and the other with runtime
key. This is mostly just a convention, but those caches are filled differently by the script.
install
event. It can download things that are not initially loaded and make them available when the device is offline.fetch
event.Note that if you just leave some basic service worker lying around, you will not be able to update your application. All pre-cached files will be served from cache. Also note that it is NOT sufficient to simply change precache-v1
to precache-v2
. Firstly, your users might not get a new service worker file with this settings. Secondly, the browsers still use their standard cache after the Service Worker Cache.
So how do you actually make sure new things are loaded from the server?
?v=1
to a script or style URL. So something like <link rel="stylesheet" href="styles.css?v=1">
. The version can be a SVN revsion number, a build time-stamp or anything similar (depends on your build process).There are some edge cases you might fall into. Before we dive into caching quirks, just check if basic updates work for you. This is an update test procedure I use:
about:debugging#workers
-> unregister.some-file.js
by adding some unique message like alert("Update v2")
.app.js
was built (if you merge your scripts).PRECACHE
version.Do keep in mind that you must also test your production (client) server. But if this works, you should be fine.
Also note that the above shows how to make updates and how will they work for your users (except the first part). So in the worst case, your application will be updated after browser restart. I think this is acceptable.
I haven't tested how it works on Safari for the moment, so I'll try to update later. If you can try it out, please let me know.
If above test doesn't work for you – stay tuned :-). I'll post another article dedicated to PWA updating and advanced caching subjects.
Part 2: PWA and HTTP Caching.