[Updated on 11/27/2017]

This article is part of a serie that aims to build a basic but complete Progressive Web Application with VueJs, Webpack & Material Design, step-by-step and from scratch. If you have not read them yet, you can find previous parts below:

Code source is available on this GitHub repository.

For those who have already followed first parts: I have updated Cropchat sources to use the brand new vue init pwa command. If you already cloned this repository, I strongly recommend that you pull this repository again.

Here is where we stopped:

This third part aims to give our CropChat application a new dimension: offline mode.

In a few words, we are going to learn:

What a Service Worker is and how it can offer offline experience ;

How to cache your application App Shell (core js and css) ;

How to cache your HTTP requests (external assets, cat pictures, …) ;

How to cache Firebase stream.

Enjoy reading!

[PART 3] Offline experience with Service Workers

Being independant of network connectivity is one of the PWA key features. We want to provide our users with an app-like user experience and keep them engaged: we don’t want them to be disconnected or frozen each time they have slow or nonexistent connectivity.

Chrome usual behavior when user tries to reach a web application with a low or nonexistent connectivity

A Progressive Web App is basically a web application that runs in a webview. However, it benefits from recent browsers & OS improvements that make offline mode possible: Service Workers.

Service Workers: a short introduction

A Service Worker is a JS script that user’s browser runs in the background, separately from your web application. A Service Worker executes code even when your application is closed or inactive, and opens the way to app-like features such as caching (using Cache Storage API and Fetch API), push notifications (using Push API) or background sync.

If you want to learn more about Service Workers, please have a look at Google documentation.

Remark: Progressive applications do not necessarily implement an offline mode on every device: iOS do not yet support Service Workers. This is the reason why we call them progressive: they will run on every device and propose a better/advanced user experience if the device can handle it.

How does it work?

You can see a Service Worker as a proxy. Each HTTP request that our application makes triggers a fetch event. This event is catched by our Service Worker who choose to retrieve from cache or fetch from the network.

All you have to do is to populate your cache when your application is online. How does your Service Worker knows when to cache and when to fetch from network? It’s up to you: you have precise control on caching strategies: cache first (useful to cache App shell files), network first (useful to cache remote assets or data)…

For example, here is a “classic” caching strategy called Network first:

There are many other caching strategies but I won’t cover them here: for further information, refer to Google Offline Coobook.

Service Worker Libraries and VueJS

While writing this article, I tried several caching methods. Here are your choices:

writing your Service Worker manually following Google code proof of concept;

use sw-precache library for static caching and sw-toolbox (that comes along with sw-precache) for runtime caching ;

use the new workbox plugin developed by Google.

Here is my advice: use sw-precache plugin. Writing your own Service Worker is slow and repetitive: it is not a viable solution. Workbox plugin is promising but is not yet ready [June 2017]: it does not yet support runtime caching. [UPDATE: it seems that they released it 3 days ago. Have to look at it!]

Let’s go!

Step 1: Cache your own static assets with sw-precache

sw-precache is able to cache static assets that are served by your own application : your App Shell. The App Shell is “the minimal HTML, CSS and JavaScript required to power the user interface” following Google’s App Shell Model.

This way, no matter what network connectivity you have, your core assets will be loaded and your Front Application will be up and running. This is named pre-caching.

We are going to use sw-precache-webpack-plugin. To install it:

npm install sw-precache-webpack-plugin --save

Configure sw-precache

Actually, new VueJS cli template already includes sw-precache . If you are using it, you won’t even need to install sw-precache .

Have a look to your dist/webpack.prod.conf.js file and update your production webpack configuration:

Add following line to <body> section in your index.html so Webpack will load sw-precache plugin.

<%= htmlWebpackPlugin.options.serviceWorkerLoader %>

Build Cropchat in production mode:

npm run build

That’s all you need to cache Cropchat App Shell.

Behind the scene

During CropChat’s build, sw-precach-webpack-plugin generates a service-worker.js file for us. This Service Worker will be located in dist/service-worker.js and will be loaded and executed in your browser on first application run. Have a look at it:

Test offline mode

You probably have noticed that I am not taking care of webpack.dev.conf.js file. There is a good reason why. Service Workers is a production feature: you don’t want *.js and *.css files to be cached while developing your application.

To test it, build Cropchat in production mode:

npm run build

In order to emulate an HTTP server and serve Cropchat built files, you can use brilliant serve tool: (requires node ≥ 6.9.0)

sudo npm install -g serve

Then, let the magic happen:

serve dist/

And browse: http://localhost:3000

Press F12 to open Chrome Developer Tools and open Application Tab:

Chrome Developer Tools: our Service Worker is installed

Under Service Workers menu, you will see that CropChat now has a Service Worker installed and running. Chrome Developer Tools is a very powerful debugging software. If you want to learn more about Service Workers debugging, do not hesitate to have a look at this Progressive Web App section.

Now, let’s test offline mode:

Go back to Service Workers menu item, activate Offline Mode ;

under Network Tab, see that Offline Mode is activated here too ;

under Network Tab, disable cache (browser cache <> Service Worker cache) ;

Refresh.

Without and with Service Workers

Application is still running thanks to our Service Worker that runs in background and serves assets rom cache.

Have a look to Cache Storage: (You may need to close and re-open dev tools):

Chrome Developer Tools: Cache Storage

Static assets are cached!

However, we are far from a proper offline mode! Here is why:

we are not caching external Material Design Lite fonts and stylesheets, fetched from https://code.getmdl.io/1.2.1/material.blue-red.min.css ;

we are not caching cats pictures ;

We are going to address these points in the next section.

Step 2: Cache HTTP requests with sw-toolbox

Basically, we would like to cache all HTTP requests. Service Workers can do that for us too. It’s named runtime caching.

Cache external Material Design Lite assets

Again, we won’t write our Service Worker manually. There is great plugin for that called sw-toolbox and it is already included in sw-precache-webpack-plugin . Nothing more to install: isn’t VueJS + webpack combo great?

Update your build/webpack.prod.conf.js :

Runtime caching with sw-precache-webpack-plugin

Build and serve again. You may need to Unregister your service worker, close your Chrome Browser… (Service Workers lifecycle seems a bit buggy right now)

Service Worker with runtime caching

Cache Cropchat cat images

We are close to achieving our goal. All we need to do is to cache CropChat cat images. Remember, cat pictures urls are fetched from the Cat API. Here is what they look like:

Chrome Developer Tools: Network tab gives us CropChat images urls list

Update webpack.prod.conf.js:

Re-build, serve your application again (unregister your Service Worker or/and close Chrome if needed), activate offline mode and test it again:

CropChat is now available offline!

Take a look at your browser’s Cache Storage again:

Chrome Developer Tools: CropChat Cat Images are now cached.

That’s it!

Step 3: Cache Firebase WebSocket with Local Storage API

I lied to you. Our application is not yet functional online. Try to turn on your network connection (instead of using Chrome Offline Mode): you won’t see CropChat content anymore.

Indeed, Firebase stream is not cached. Firebase protocole is based on Web Sockets and Service Workers can’t handle WebSockets.

We need to handle Firebase case manually. Here is my network-first caching strategy:

if user has access to the Internet (using navigator.onLine check): cache Firebase Websocket content in browser’s local storage, then hook on Firebase WebSocket ;

else: serve cached content from browser’s local storage.

To get it done, update your HomeView.vue component with the following code:

And update <template> section to use getCats() in v-for loop.

Build your app again, serve it, load it (with internet connection back) and try it offline !

Conclusion

I hope you have a better understanding of how to implement offline mode with VueJS, Webpack and a few plugins. We learned:

how to use sw-precache to cache App Shell files ;

how to user sw-toolbox through runtimeCaching to cache external assets ;

how to handle Firebase websockets caching using browser’s local storage.

Let’s have a look to our Progressive Web App checklist:

Two of our requirements are not yet met. We will handle them in next parts. Part 4 will show you how to “benefit from native features” using HTML 5 powerful APIs such as MediaDevice and MediaStream APIs.

For those who are in Paris, I am co-organizing a Progressive Web App MeetUp. Do not hesitate to say ‘Hi!’.