Progressive web apps, or 'PWAs', are websites which use a selection of browser technologies to provide users with an experience similar to native apps. They can be installed, showing as an icon in your app drawer, and support offline usage. Browsers have the ability to request access to system devices such as cameras, and support offline data storage.
Getting started isn't too difficult, but there are a few parts to consider and also a couple of workflow gotchas which can cause confusion when you first give it a go.
Installing
Your application communicates to the browser that it is a PWA using a file known as a manifest. This declares the app's name, as well as resources such as install icons etc.
When a browser detects a manifest, it will show an icon in the browser toolbar informing the user that the app can be installed locally.
Mozilla recently removed this install button from their desktop Firefox browser. You will still see it in mobile Firefox, and all Chromium based browsers. Apps still work offline in desktop Firefox, they just can't be installed in the guise of a native desktop app.
Caching
Offline behaviour is mainly handled by a process known as a service worker. This sits between your client app and web server, tapping into browser events such as install
, activate
and fetch
. It intercepts requests and acts as a kind of caching proxy. There is a fairly standard implementation which you can tailor to your needs, for example declaring what should and shouldn't be cached.
I recently found a library from Google called Workbox which offers helpers for managing your service worker cache. I haven't tried it but it looks like it could be really useful!
This caching behaviour is one of the possible sources of confusion previously mentioned. If you don't change the cache name, then the browser will not automatically flush its cache. This can lead to old versions of resources showing up or changes not being applied when you would expect to see them, as one of the files the browser caches is of course the client side app itself.
You can inspect the service worker's state and the contents of the cache using your browser's F12 tooling under the Application tab. You will find controls to simulate offline behaviour, unregister or refresh the worker etc.
Offline storage
Offline data storage is handled by a browser-native technology called IndexedDb. Whilst this can be used directly there are libraries which build useful APIs on top of it. One popular choice is PouchDb. This combines a powerful document storage API with auto synchronisation to CouchDb instances.
Usage with SAFE stack
Usually I would post some code snippets before linking off to a full example. This time however there are a few too many parts for me to cover in one blog, so I have taken the opportunity to convert the SAFE template Todo app into a PWA.
This includes storing the Todos in the browser using PouchDb, and a Server upload routine which only sends new items.
I started with the new .NET 6 SAFE template, and hopefully you can see in the short commit history how I went about adapting it.
Give it a try! Once the app is running, you can stop the server and it should remain fully functional with persisted data, even after you close down and reopen your browser.
If you check the Server console output when you hit 'Upload', you will see the uploaded items printed - note that they only get uploaded once, after which their client-side Uploaded status is saved in the db, excluding them from the next POST
.
You will find we have wrapped a selection of the PouchDB and Service Worker types in F#, making them nicer to work with than using raw JS. This is a good starting point for a full binding if you feel ambitious!
Helpful UI libraries
We have made some Feliz wrappers for a few React components that play well with mobile applications.
These are Fable packages, so they have both nuget and npm dependencies. These can all be installed automatically using Femto.
React Burger Menu
React Pull To Refresh
React Swipeable List
React Recycled Scrolling
React QR Reader