avatar

Coderek's blog

Loneliness is the gift of life

Adding Service Worker to my website

Service Worker has been around for a while. As of now, Edge, Firefox, Safari and Chrome all have 100% support of service worker features1. So it's good time to add it to my website.

The biggest use case of Service Worker is to act as proxy and cache requests in browser. It can intercept all kinds of http requests. But usually, we only cache GET requests. With cache, webpages don't have to fire network requests to the web server every time to get the same file. This essentially makes your webpages load faster.

Since files are loaded from local disk, it means your website is still accessible without internet. Sure, it's view-only mode, but infinitely better than the default browser error page. For the sites that mostly provide informations to the users, this is super useful. For example, devdocs is a documentation website. It contains tons of read-only documentation. By using service worker, devdocs can function even the original documentation site is down. Thus making devdocs a more reliable source of documentation. My blog consists of mostly articles that I would like people to read, so it's good to add service worker too.

There are many articles online about how to add service worker, here I will only talk about the important things I learnt along the way. Also the followings will focus only on the caching aspect on service worker.

Service worker is loaded through service worker file. Service worker file is a JavaScript file specifying how you would handle related events. Service worker file has its own scope marked by the path to the file. It cannot effect across scopes. Thus, for large website, you might have multiple service worker file per team.

Service worker file is running in a separate worker thread from the main thread. It exposes a couple of useful events, namely installation, activation and fetch.

Installation event is the first event fired after the service worker script is run. The script is run when a service-worker.js is new or updated. The same file won't be run twice. The installation phase is before the webpage is handed over to the new version of service-worker.js. So this phase is considered a preparation phase. It's best suited for pre-populating cache with asset files that the website potentially will use. As service worker is run in separate thread, the loading and writing of files won't impact the use of webpage.

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        return cache.addAll(precache);
      })
  );
});

Note that cache.addAll method returns a promise. Thus the installation will wait until all files are loaded. However, if any file is not loaded successfully in this phase, the installation will fail and the script will not proceed to next phase. So we must be careful about files we preload. The full list of cache API can be found here.

After it's done, the activation event is fired. At this point, the new script kicks in and old script is gone. It's the best time to clean up the mess produced by old script. For instance, clearing the old cache. Caches are identified by names, so we can easily clean up the outdated cache. But this also means that we have to reload the files that are shared between both caches.

After activation, then the new script is fully operational now. However, we need to implement fetch handler in order to use the cache. fetch is fired every time the controlled page makes a http request. We can hijack these requests and use event.respondWith to short circuit the request. This is basically how we use the cache. There are several strategies of using caches. One of the good documentation is from workbox, even though you don't have to use workbox to implement these strategies.

So in my case, I defined a couple of regular expressions of the assets that loaded from my website. And I only use cache-first strategy to load process the request.

const statics = [
  /\/static\/images\/myAvatar\.png$/,
  /\/static\/CACHE\/css\/pygment\.[a-zA-Z0-9]+\.css$/,
  /\/static\/CACHE\/css\/base\.[a-zA-Z0-9]+\.css$/,
]

self.addEventListener('fetch', function(event) {

  if (!statics.some(s => s.test(event.request.url)) && precache.indexOf(event.request.url) == -1) {
    return event.respondWith(fetch(event.request))
  }

  console.log('cached ' + event.request.url)
  // cache then update
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {

        if (response) {
          return response;
        }

        return fetch(event.request).then(
          function(response) {
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }

            const responseToCache = response.clone();

            caches.open(CACHE_NAME)
              .then(function(cache) {
                cache.put(event.request, responseToCache);
              });

            return response;
          }
        );
      })
  );
});

There are a couple of important things to note here.

  1. I use regular expression to match the request URL. This allows me to still able to use the same service-worker.js even my asset content is changed and url path updated with a new hash.

  2. I don't cache HTML files. This one is especially important if you render dynamic content from server, which I believe most people do. For instance, the same URL can point to different versions of the page, depending on authentication status. Omitting HTML page also makes sure the assets URLs needed to render the webpage at client side is up to date. So this is very important.

The performance of my website before and after I added service worker is huge. There is no more flickering when loading the avatar image. The page feels instantly loaded. It's almost comparable to a single page app. Actually it is interesting to consider: with service worker, what is exactly the advantage of having Single Page App? I will have another post to talk about this.

The power of service worker is beyond caching. It can be used to create an offline page when you are not connected to internet. Since it's acting like a background thread, it can be used to do background tasks like syncing or listening to server messages. This is very much like Android's service component. (As of now, background sync feature is only available in Chrome. ) Service worker can talk to main thread using PostMessage API.

Apparently, service worker is tasked to help web developers to make native grade apps. We should really embrace it and use it to help design better apps.

Hence, my next task is to add offline page to the blog.


  1. All required features for caching 

(End of article)