Service Worker : cache your resources and other good stuff

Pretty recently I took interest in how I could better optimize my webapps. I’m not talking about the code per se, but about what’s around your webapps lifecycle : building, rendering, resources, that kind of stuff.

I started to use Webpack a few months back and fell in love with it. Thanks to it I can serve css and javascript bundles and even css modules or javascript modules ! That increased drastically the loading time of our product at Zenchef (where the codebase is starting to get pretty heavy).
So, yeah that’s good and all but how could we go further ?

I started to hear about Service Workers and their multiple uses for some time now so one day i was like ‘What the hell’ and tried an implementation on a little project.

Service Workers ? What the… ?

First let’s start talking about Service Workers specifically. They allow you to :

  • Cache resources (like your webpack bundles or your frameworks from cdn)…
  • …Which can lead to faster loading time AND offline navigation !
  • Push notifications
  • Have background sync

It’s starting to look a lot like mobile app doesn’t it ?

For my implementation i used :

  • A React app bundled with webpack
  • css bundled with webpack
  • index.html to mount react and includes scripts
  • A javascript file named initServiceWorker.js
  • A service worker named sw.js

Boldly go to the code !

First let’s start with initServiceWorker.js

if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then((reg) => {
console.log('Yay, service worker is live!', reg);
}).catch((err) => {
console.log('No oats for you.', err);
});
}

Straightforward enough, if your navigator supports Service Workers, you try to register to one. Note that it uses Promises.

Then we have the Service Worker itself. There is multiple steps to make it work but basically it listens to events and act accordingly.

First we have the ‘install’ event. That event is used once and serves as an init. We will tell it to cache our resources

const version = "v1::" //Change if you want to regenerate cache
const staticCacheName = `${version}static-resources`;

const offlineStuff = [
'/dist/app.bundle.css',
'/dist/app.bundle.js',
'/assets/workers/initServiceWorker.js'
]

self.addEventListener('install', (event) => {
console.log('in install')
event.waitUntil(
caches
.open(staticCacheName)
.then((cache) => {
cache.add('//cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react.min.js')
return cache.addAll(offlineStuff);
})
.then(() => {
console.log('WORKER:: install completed');
})
)
});

Okay so let’s break it down.
First you create variables for the version, the cache name and the path of the items you want to cache.
Then you use self.addEventListener(event) to catch an install event. This event uses funny methods like .waitUntil, that do exactly as his name says, and caches.

caches is the keyword to all cache methods. Since it’s the installation you will want to open a new cache with your version name and add some items to it. Note that you can add url like i did with the React CDN.

Mind that you need to refresh your page after the registration for the install and activation to take effect.

Worker : ACTIVATED

Now that we have our ressources installed in cache let’s talk about the activate event. This event is fired each time the Service Worker is registered.

self.addEventListener('activate', function (event) {
event.waitUntil(
caches
.keys()
.then((keys) => {
return Promise.all(
keys
.filter((key) => {
//If your cache name don't start with the current version...
return !key.startsWith(version);
})
.map((key) => {
//...YOU WILL BE DELETED
return caches.delete(key);
})
);
})
.then(() => {
console.log('WORKER:: activation completed. This is not even my final form');
})
)
});

When the worker is activated, it checks the current cache and flush outdated resources thanks to our version variable.
And… That’s it. Now to the better part : fetching

Go fetch !

The fetching event intercepts everything you’re trying to include in your app. What we are trying to do here is to : serve the content from the server / cdn / whatever OR serve it from cache if it is available in it.

self.addEventListener('fetch', function(event) {
event.respondWith(
// Try the cache
caches.match(event.request).then(function(response) {
return response || fetch(event.request);
}).catch(function() {
//Error stuff
})
);
});

And with just that you have cached all the things and made your website load faster.
But the real magic is when you switch your wi-fi off and notice that your React app still works since React.js is served from cache instead of cdn.

Conclusion

Beyond the fact that you can reduce some loading times, the most interesting point is the offline capabilities granted by the Service Workers.
Of course it will add another layer of complexity to your front if you want to have a real Offline-Capable app but it’s worth it !
Some time in the future I might try a better implementation with added background-sync and push-notifs but for now that will be all.

You can find the code and other good stuff in my starter-kit-js on Github

--

--

Ashley Romain Bonhomme
Zenchef’s Tech and Product Blog

Romain is an enthusiast javascript developer. He’s also into PC gaming and cats (especially Scottish folds)