Caching Critical Resources; Way Of The Service Workers
Tags: javascript, webperf, webdev, tutorial
Published: 2020-03-07 12:48:27

### INTRODUCTION

Another way to optimise the First Paint (FP) and First Contentful Paint (FCP) performance metrics of your website is to cache all critical resources after they have been identified. 

In this post, I show you how to cache critical resources using service workers.

But first...

### WHAT ARE SERVICE WORKERS?

Service workers are scripts that your browser runs in the background, separate from a web page. This opens the door to features that don't need a web page or user interaction. 

Today, they already include features like [push notifications](https://developers.google.com/web/updates/2015/03/push-notifications-on-the-open-web), [background sync](https://developers.google.com/web/updates/2015/12/background-sync) and so on.

Service workers allow us to support offline experiences and give us (developers) complete control over such experience.

Before service workers, there was one other API that gave users an offline experience on the web called [AppCache](https://www.html5rocks.com/en/tutorials/appcache/beginner/). There are a number of issues with the AppCache API that service workers were designed to avoid.

#### THINGS TO NOTE ABOUT SERVICE WORKERS

- They can't access the DOM directly but instead, service workers can communicate with the pages they control by responding to messages sent to them, and those pages can in turn, manipulate the DOM if needed.
- Service workers are programmable network proxies, allowing you to control how network requests from your page are handled.
- They are terminated when not in use, and restarted when it's next needed.
- Service workers make extensive use of [promises](https://developers.google.com/web/fundamentals/primers/promises).
- Any website implementing a service worker must be served over HTTPS.

#### LIFECYCLE OF SERVICE WORKERS

![Service workers lifecycle](https://i.ibb.co/BgQHMLK/sw-lifecycle.png)

A service worker has a lifecycle that is completely separate from your web page.

The first stage in the lifecycle of a service worker is to install it. At this stage, you need to register it, which you do in your page's JavaScript. Registering a service worker will cause the browser to start the service worker install step in the background.

Typically, you cache your critical resources in the install step. If all the files are cached successfully, then the service worker becomes installed. If any of the files fail to download and cache, then the install step will fail and the service worker won't be installed. If the installation fails, the service worker tries again at another time.

After installation, the activation step will follow. This stage is mostly used in  handling any management of old caches.

After the activation step, the service worker will control all pages that fall under its scope, though the page that registered the service worker for the first time won't be controlled until it's loaded again. 

Once a service worker is in control, it will be in one of two states: either the service worker will be terminated to save memory, or it will handle fetch and message events that occur when a network request or message is made from your page.

#### BROWSER SUPPORT FOR SERVICE WORKERS

As with all cool features that we may want to use, we have to make sure it is well supported by the various web browsers in an attempt of having a uniform experience for all our users.

![Browser support](https://i.ibb.co/KjHfgFM/Screenshot-2020-02-19-at-22-31-46.png)

Now, let's go into the main content of this post.

### USING SERVICE WORKERS TO CACHE CRITICAL RESOURCES

Using service workers to cache critical resources is a relatively easy task to accomplish. To do this, we go through the following steps:

- Register a service worker,
- Install a service worker,
- Returning cache requests.

#### REGISTERING A SERVICE WORKER

As in the lifecycle of service workers, the first step to caching critical resources is to register your service worker.

```js
if ('serviceWorker' in navigator) { // CHECK IF serviceWorker IS SUPPORTED
  window.addEventListener('load', function() {
    // REGISTER SERVICE WORKER AFTER PAGE IS LOADED
    navigator.serviceWorker.register('/sw.js').then(function(registration) {
      // REGISTRATION WAS SUCCESSFUL
      console.log('ServiceWorker registration successful with scope: ',registration.scope);
    }, function(err) {
      // REGISTRATION FAILED
      console.log('ServiceWorker registration failed: ', err);
    });
  });
}
```

In the code example above, we check if `serviceWorker` is supported. If it is, we register a service worker `/sw.js` after the page is done loading.

#### INSTALLING A SERVICE WORKER

After the page controlled by the service worker registers it, we hook into the `install` event of the service worker where we cache the critical resources.

```js
var CACHE_NAME = '...'; // CACHE NAME
var urlsToCache = [ // RESOURCES TO CACHE
  ...
];

self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME) // CREATE A CACHE WITH THE GIVEN NAME
      .then(function(cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache); // ADD ALL RESOURCES TO THE CACHE
      })
  );
});
```

In the code example above, we perform 4 actions:
- Give our cache a name and specify what resources to cache,
- Listen for the `install` event,
- Create a cache with the name `CACHE_NAME`,
- Add all resources to the cache.

#### RETURNING CACHED RESOURCES

Registering and Installing a service worker is not the end of the road as we'll need to return cached resources when a web page makes a network request for the cached resource.

```js
self.addEventListener('fetch', function(event) { // LISTEN FOR NETWORK REQUESTS
  event.respondWith( // RESPOND TO NETWORK REQUEST
    caches.match(event.request) // CHECK IF RESOURCE EXISTS IN THE CACHE
      .then(function(response) {
        // Cache hit - return response
        if (response) {
          return response; // RETURN THE CACHED RESOURCE
        }
        // MAKE A NETWORK REQUEST FOR THE RESOURCE IF IT CAN'T BE RETRIEVED
        // FROM THE CACHE
        return fetch(event.request);
      }
    )
  );
});
```

Here, we listen for any network request from the controlled page, check if the resource exists in the cache, return the cached resource if there is a matching resource in the cache, otherwise, we return the result of a call to `fetch`, which will make a network request and return the data if anything can be retrieved from the network.

### LEARN MORE ABOUT SERVICE WORKERS @ 
- [GOOGLE DEVELOPERS](https://developers.google.com/web/fundamentals/primers/service-workers)
- [MDN WEB DOCS](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)