TL;DR Here is a repository with a working example. :)
Many of your have probably noticed all the buzz about offline first Web-apps. The Web is full of articles about the AppCache about Service Workers and such and now you ask yourself, how do I can get my apps work offline, like the cool kids. Or you simply asked, how come that data I already have on my device isn't shown to me anymore?!
Well, there are a bunch of technologies to help you with this, but this seems a rather fast paced topic these days. So first things first.
If you write a single page application, you often have different types of request your app sends. Some get images, some get styles, some code and some actual user data.
Caching User Data
For the last point, the user data, you will basically store the whole stuff somewhere on the client. Since this depends mostly on your business logic, it is advisable to write at least part of that logic yourself.
You can use localStorage for this if a key-value-store is enough or, indexed DB if you need a more sophisticated datastore. There are even libraries that help with the usage of these low-level Web-APIs. Like PouchDB and LocalForage.
Caching Assets
But this post is about your assets. Your HTML, CSS, JS and Images.
If you bundle your assets with Webpack, there is a really easy way: The offline-plugin.
After building with this plugin, you can call a function, that will make all Webpack generated assets available offline.
A basic webpack.config.js
could look like this:
const HtmlWebpackPlugin = require('html-webpack-plugin')
const OfflinePlugin = require('offline-plugin')
const html = new HtmlWebpackPlugin({template: './src/index.html'})
const offline = new OfflinePlugin
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].[hash].js',
path: './assets/',
},
plugins: [html, offline],
}
Pitfall: Non-Webpack Assets
Now you probably ask, why does the basic version need another plugin? Well, the offline-plugin has a catch.
It will only cache the assets that are produced by Webpack if it isn't handed any configuration object.
So if you have an index.html
, which you don't generate with Webpack, it won't be cached and your whole app won't show when there is no connection.
Using the html-plugin is a way to get around this problem. Then the index.html
will be included.
Another way is using externals
:
const offline = new OfflinePlugin({
externals: ['index.html'],
})
This is also used for assets from another server, for example if you require Bootstrap or jQuery from a content delivery network (CDN.
I found it a bit confusing at first, because I thought of externals
as API requests and CDN assets. But it basically means: files not built by Webpack
The basic config won't give you any warning, only if you try to manually configure the caches you will maybe see this:
WARNING in OfflinePlugin: Cache asset [index.html] is not found in the output assets,if it's an external asset, put it to the |externals| option to remove this warning
Pitfall: Cached Forever
The basic approach is to require the offline-plugin runtime at the beginning at your code and install it. Which basically means enable caching.
const offlinePluginRuntime = require('offline-plugin/runtime')
offlinePluginRuntime.install()
But you don't have to activate the caching automatically on application start. This can lead to unexpected behaviour if you're not used to offline modes.
For example, you could upload another version of a file and give it the same name, so it won't change on the client.
Or you have an API call in externals
and now only the cached results are shown.
This can greatly help with debugging.
Conclusion
The offline-plugin is a really easy way to show already downloaded data to your users, even if they aren't online. These days, where more and more people are online via mobile devices, this offline scenario becomes less and less of an exeption.