Offline Mode — Make your App Great Again!

Ave, folks!

Today I am going to tell you a fascinating story about the great feature which you probably heard off — The Mighty Offline Mode powered by Service Workers and magic helper from Google — The Workbox.

This is a S̶P̶A̶R̶T̶A̶ PRACTICAL GUIDE(!) which will help you to understand:

  1. Use cases for Service Workers, Workbox and Offline Mode.
  2. Particular case which will be considered in this blogpost.
  3. Technical details and implementation.
  4. Browser and System support of service-workers.

1. Use cases for Service Workers

Back then in ancient times when my pal Julius Caesar rules over Romans, people need to go “online” to get any information, like go outside their gorgeous houses and talk to everyone around. But what if I am sociopathy and what to get some information and always be able to read it at home and stop going out to this loud and dangerous world? There is an answer — Service Workers!

But answer my question: “How often do you see this awesome dinosaur?”

Probably often, if you are using Google Chrome. This is typical sign for you of being Offline while browsing. It works more or less same for any browser on any Operational System! Service Worker is a way around this blank page, but before we start digging in I want to tell you about one more powerfull tool called — Cache API. Cache is used by Service Workers to store data and cache can be handled by some strategies:

  1. Network or Cache
  2. Cache only
  3. Cache and Update
  4. Cache, update and refresh
  5. Embedded fallback

So, dig in differences of these strategies is not a case for this blogpost, but here, my dear friend, you will find all information.

We going to use first option and make the most part of our application useful offline. But before, it is one more important guest to present: The Workbox!

In two words, The Workbox is a useful set of tools which will help dummy us to manage complexity of Service Workers in a really simple manner and you will be amazed HOW simple it is!

You are maybe asking already: “What the heck, Scriptus? Where is my sample?”. Ye, ye, it is coming finally!

Two more words about particular case and we are ready to go!

2. Particular case

I want to demonstrate how to build basic App with offline cached assests + one API call, for instance, Authentication or GET any data call, which also will be handled by Service worker.

Side note:

Now service worker can handle only GET requests by default. Support for POST is coming!

Usually, the problem for beginner is that it is a bit hard to understand that Service Worker works differently from usuall FE App and if you made some fixes you need to be sure that every client with old version of Service Worker need to get updated version. There are mechanisms to make it work as a charm, but you should carefully read the specs, because every mechanism has it own pros and cons and you should rely more on your specific case that common rule.

My particular case can be extended to more generic example when you have an App with many requests, like Auth calls, get user details calls, etc.

Also, worth to mention, I will use lovely create-react-app as a base to show my sample and heroku as a easy to deploy solution.

3. Implementazione!

So now your feelings about Service Workers are a bit dimmed:

But hopefully at the end of the blogpost you will understand more about it :)

Yes, you need some time to get it, but don’t forget:

Rome wasn’t built in a day @someone_famous
  1. Setup:

Make sure you have Node and npm installed:

node -v
v8.9.3
npm -v
5.5.1

Make sure create-react-app installed:

npm i create-react-app -g

create-react-app — help

Then create new create-react-app project:

create-react-app offline-test

And then install all dependencies which we need:

yarn add react-app-rewired react-app-rewire-workbox workbox-webpack-plugin

About dependencies:

  • react-app-rewired — tweak the create-react-app webpack config(s) without using ‘eject’ and without creating a fork of the react-scripts.
  • react-app-rewire-workbox — rewire service worker from default config to workbox one.
  • workbox-webpack-plugin— extend webpack config with workbox configuration

Then small updates connected only to create-react-app in particular:

  1. Create separate service worker file, in my case called custom-sw.jsand later we will add code there.
  2. In package.json change react-scripts into react-app-rewired
  3. Create config-overrides.js file (I will create more or less default one):
const { compose } = require('react-app-rewired');
const { rewireWorkboxInject, defaultInjectConfig } = require('react-app-rewire-workbox');
const path = require('path');
const sourceDir = process.env.SOURCE || 'src';
const rewireWebpackAliases = config => ({
    ...config,
    resolve: {
       ...config.resolve,
       modules: ['node_modules', sourceDir],
    },
});
// The most important part
const rewireWorkbox = (config, env) => {
if (env === 'production') {
const workboxConfig = {
...defaultInjectConfig,
swSrc: path.join(__dirname, 'src', 'custom-sw.js')
};
config = rewireWorkboxInject(workboxConfig)(config, env);
}
return config;
};
module.exports = {
    webpack: compose(
        rewireWebpackAliases,
        rewireWorkbox
    )
};

The most important part here is rewireWorkbox where we are changing default service worker from create-react-app to our custom-sw.js .

Finally, through all this boring explanations you reach main goal of the blogpost my friend!

Service Worker:

So all changes will take place in custom-sw.js :

// ==== Setup cache details ====
workbox.core.setCacheNameDetails({
    prefix: 'offline',
    suffix: 'v1',
    runtime: 'run-time'
});
// ==== skipWaiting and clientsClaim will force the new service worker to immediately take control of open pages and push new version of itself to them ====
workbox.skipWaiting();
workbox.clientsClaim();
// ==== Matcher for our only one and very lovely request :) ====
const matchApi = ({ url }) => url.pathname === '/users';
// ==== Register call to the API and save it to the cache. I am using networkFirst strategy because I want to be sure that every time users reach my page and ping /users request, new version of the response will be cached. The same applies for assets.
workbox.routing.registerRoute(
matchApi,
workbox.strategies.networkFirst());
// ==== Register call to get assests and save it to the cache
workbox.routing.registerRoute(
/\.(?:png|gif|jpg|jpeg|svg|js|css|eot|ttf|woff|woff2|html)/,
workbox.strategies.networkFirst()
);
// ==== Helps Service Worker to understand client history changes like going from / to /login, etc. So basically on every location change request service worker will answer with index.html and react can handle it properly.
workbox.routing.registerNavigationRoute('/index.html');

If you carefully read everything ( Note: Psss, explanation is between lines of code) you can understand what actually happens here! Bingo!

If everything is done correctly that is what you should see locally:

More details can be found on workbox page!

Github Repo — here.

My app is here — https://offline-mode.herokuapp.com/

And now you should be able to go offline, reload the page and still see app working perfectly fine or even close and reopen browser and offline page — still should work like a charm(Note: There are couple of issues with Service Worker and window “online” and “offline” events support which will be shown below):

If you still see Network Error that means your service worker was not installed properly or registration failed, etc.

Be aware that in Incognito Mode or in Guest Tab Service Worker will not cache anything, because cache is disabled.

To be sure that your service worker is installed and working, just go Developer Console / Application / Service Workers:

To be sure that service worker saving everything correctly to the cache go Developer Console / Application / Cache Storage / {your_cache_id}

Last but not least:

4. Browser and System Support:

In MDN we trust!

Window “online” and “offline”:

Here you can find all information. Please read it carefully, young padawan, before using in production(!)

Service Worker support:

So details about support can be found here — https://caniuse.com/#feat=serviceworkers

Achtung!

Just one thing I want to mention from personal experience.

On iOS it is still tricky to have service workers and offline-mode, because iOS Chrome is not yet supporting service workers at all and Safari supports it, but only one case, when user go offline and reload page, but if user closes the browser and then try to reach the website being offline, Safari will fail with “No Network” response.

Thank you for reading!

I know it was tough but I hope I made it as enjoyable as possible and you can be proud of yourself, young pada… Jedi!