Progressive Web Apps (PWAs) are becoming increasingly popular and allow developers to implement their applications once and have them accessible across all devices and offline. With this increased popularity comes better tooling and Workbox from Google provides some excellent tools for getting started quickly with PWAs.
Webpack is a module bundler for JavaScript applications and combined with plugins such as Workbox some powerful workflows are possible. This blog post will not explain setting up Webpack (their own documentation does a good job of that) but will rather attempt to collate the different strategies needed to build a fully functional PWA.
The Workbox plugin is available through NPM:
npm install -D workbox-webpack-plugin
Once installed you can import the plugin and add it to your Webpack config:
const workboxPlugin = require("workbox-webpack-plugin");
module.exports = {
...
plugins: [
...
new workboxPlugin.GenerateSW({
swDest: "service-worker.js"
})
]
};
Finally, you need to register the service worker within your main application or web page (see getting started):
<script>
// Check that service workers are registered
if ("serviceWorker" in navigator) {
// Use the window load event to keep the page load performant
window.addEventListener("load", () => {
navigator.serviceWorker.register("/service-worker.js");
});
}
</script>
And that's it! You now have an offline PWA...
Well not quite. This is all the config you need to have the static assets that pass through your Webpack build cached and served offline, e.g.:
All of the files that are output by your Webpack build get tracked by the Workbox plugin and it then outputs two additional files, a service worker script and a precache manifest file. The service worker script runs in the background of your web browser and is what is responsible for caching and serving up cached resources. The precache manifest lists the static assets of your site along with a hash of their contents which the server worker uses to preload and cache them for use offline.
If you change any of these files they get a new hash and a new precache manifest is generated resulting in the new version being loaded on subsequent visits to your site.
Below I'm going to discuss some of the additional strategies you may want to use to have a more capable PWA.
Full documentation of the GenerateSW config options can be found here.
As well as your static assets, there are other things you might want to cache including API requests and Third party assets (e.g. fonts and polyfills).
Workbox provides a runtime caching option and a number of different strategies for different situations.
This strategy attempts to retrieve a response from the cache first, falling back to the network if there is nothing in the cache.
Useful for assets that are unlikely to change (e.g. fonts) or have been versioned.
This strategy only retrieves from the cache, if it can't be found it will throw an error.
This strategy makes a network request first and loads data from the cache if you're offline or the request times out. Useful for frequently updated data - your API requests for example. This way online users get up to date information and offline users can still use the site with cached data.
This strategy makes a request to the network and the cache in parallel, if the cache is available this will be returned otherwise it will fall back to the network. The difference between this and the cache first strategy is that even if the cache responds it will update the cache with the network response.
This is useful for things that change frequently but where the latest version isn't always required, for example, avatars or non-versioned scripts.
Below is a sample of what your runtime caching configuration might look like, with some examples of different resources and how you might want to cache them.
runtimeCaching: [
{
urlPattern: new RegExp("/api/"),
handler: "networkFirst"
},
{
urlPattern: new RegExp("^https://cdn.polyfill.io/v2/polyfill.min.js"),
handler: "staleWhileRevalidate"
},
{
urlPattern: new RegExp("^https://fonts.(?:googleapis|gstatic).com/(.*)"),
handler: "cacheFirst",
options: {
cacheName: "googleapis",
expiration: {
maxEntries: 30
}
}
}
],
So if you've got this far all of your sites assets and data are available offline. However, if you try and access one of the deeper pages of your site whilst offline you will probably get an error. You're most likely relying on a server-side redirect to serve up your index, but since the server isn't available this won't work.
Fortunately, workbox provides a solution with the navigateFallback option - this configures your service worker to serve the specified route in response to navigation requests.
navigateFallback: "/index.html",
However adding this option alone will break your application as it will redirect all requests to this route, including requests to your API. You can blacklist and routes you don't want to be redirected to your index with the navigateFallbackBlacklist option:
navigateFallbackDenyList: [/\/api\//, /\/healthcheck\/?/]
As well as the service worker there are a few other things you need to consider to have a fully functional PWA.
The Web App Manifest is a JSON file that describes your application and allows your users to install it onto their devices - defining how this should look: what icons to show, what colours to use, etc.
It is relatively simple and you can write it yourself though there are a number of free tools online which will generate one for you and will even resize your icons as needed for the various different devices you might want to install.
One such tool is PWA Builder which has an online tool as well as Node.js CLI. Or there is a more simple online Web App Manifest Generator available here.
For certain critical areas of your app, you might want to implement some custom code to store and retrieve data from local storage - so you can guarantee the integrity of your users data. I would suggest writing a class or function that you can call that reads/writes from both the server and local storage.
I've hopefully covered most of the standard options you will need for a basic offline application. Workbox Webpack plugin makes this all simple to set up with very few lines of code.
Workbox provides additional utilities for more advanced concepts such as background sync and offline Google Analytics but these are beyond the capabilities of the GenerateSW plugin. It does however also provide the injectManifest plugin which allows you to write your own service worker and have Workbox inject the precache manifest.
Have you used Workbox? How have you set it up and what caching strategies have you used?