This is the 7th day of my participation in the August More Text Challenge


preface

As technology continues to evolve, application performance has become a top priority to optimize user experience. So the second the web is an important part of improving user experience.

The business line I was in charge of was advertising and promotion, which often encountered high concurrency. When I was just launched, it often caused the server to crash, the page could not be opened, and users complained, so I could only optimize a little bit with my friends at the back end. This time I’ll document the web offline caching technique. Can enable users to have a better user experience in the absence of network, weak network, server crash, prolong user retention time.

A Service Worker is like a proxy server between a Web application and a browser. JavaScript is single-threaded and a Service Worker is independent of the main thread and does not clog JS, so it cannot access DOM elements. You can communicate with pages through postMessage. Service Worker can realize functions such as message transfer to the background, network proxy, request forwarding, forged response, offline cache, message push and so on. For security reasons, Service workers can only be used in HTTPS or local environments.

Service workers are based on event listening mechanisms. The most commonly used events include the following:

  • installThe installationService WorkerResources are usually cached at this event so that the user can speed up the next access.
  • activateThe first load will be ininstallAnd then it’s triggered.
  • fetchRequest interception of a web page after activation.
  • messageExecute when a message is received.

Use the Service Worker

The registration service

Since Service Worker compatibility is low, you need to check whether the browser supports it first. The purpose of loading the Service Worker into the load() event is to ensure that page resources are loaded first and page rendering first, so as not to consume extra CPU. The picture shows compatibility provided by CaniUser.com:

<! DOCTYPEhtml>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <script src="js/jqurey.js"></script>
</head>

<body>
    <img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/154cd131b76e431897c2c04af97880df~tplv-k3u1fbpfcp-watermark.image" width="100px">
    <div>This is a string of words</div>
    <div class="list"></div>
    <script>
        let baseUrl = ""
        function getList() {
            $.ajax({
                type: "get".url: "http://localhost:3000/api/getList".data: {},
                dataType: "json".success: res= > {
                    let html = ""
                    for (let i in res.data) {
                        html += "<div>" + res.data[i].name + "</div>"
                    }
                    $(".list").html(html)
                }
            })
        }
        getList()
    </script>
    <script>
        window.addEventListener("load".event= > {
            if ("serviceWorker" in navigator) {
                navigator.serviceWorker
                    .register("sw.js")
                    .then(registration= > {
                        console.log("Registration successful");
                    })
                    .catch(error= > {
                        console.log("Registration failed", error); }); }});</script>
</body>

</html>
Copy the code

This simulates the scenario of most web pages, which contain text, images, and Ajax resources (the Service Worker cannot cache POST requests). Register () registers a Service. This method takes two arguments, the first being the file the Service Worker is loading and the second, optional, specifying the file directory to control, which by default is the same directory as sw.js. This method returns a Promise. If registration fails, success or error messages can be caught using then and catch.

Install the installing

Sw.js is the core module of Service Worker, which defines how to cache resources for each lifecycle. When we register the Service Worker, the install event is triggered.

var verson = "v1"
self.addEventListener('install'.event= > {
   event.waitUntil(caches.open(verson).then(cache= > {
     return cache.addAll(['demo.html'.'js/jqurey.js'.'https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/154cd131b76e431897c2c04af97880df~tplv-k3u1fbpfcp-watermark.image'])}})))Copy the code
  • waitUntilThe event receives onepromiseCan prolong the effect of events.
  • caches.open(String)You can create a cache space called String to add files to the cache.
  • cache.addAll(Array)Select which files are cached;

If you open the console > Application > Cache Storage, you will find an additional Cache library named v1. The cached resources are exactly the resources we set up.

Activation of activated

The activated state is activated only when the Service Worker is installed for the first time. If a Service Worker is in the running state, this event is not executed. Wait for the new Service Worker to be installed or call self.skipwaiting (). The Activated phase updates the cache.

self.addEventListener('activate'.event= > {
  console.log("activate")
  var fn = caches.keys().then(function (cacheList) {
    return Promise.all(
      cacheList.map(function (cacheName) {
        if(cacheName ! == verson) {returncaches.delete(cacheName); }})); }) event.waitUntil(Promise.all([fn])
    .then(() = > {
      return self.clients.claim()
    })
  )
});
Copy the code
  • claim()Make a newService WorkerTo take over the page

fetch

At this step, we disconnected the network and refreshed the page, but found that there was still no network page. This is because the most important step is missing: listen to the FETCH event, which will intercept the request, determine whether the cache exists, preferentially take the data in the cache, and continue the request if there is no cache.

self.addEventListener('fetch'.function (event) {
  console.log("fetch",event.request.url)
  event.respondWith(
      caches.match(event.request).then(function (response) {
          if (response) {
              return response
          }
          return fetch(event.request)
      })
  )
})
Copy the code

At this point, we are done caching static resources. At this time, we test, modify the text content of the page and the data returned by the interface to refresh the page. We can see that only the data returned by the interface has changed, and the text modification of the page is not updated to the page.

Modify before:

Revised:

If we want to cache the interface data, we can copy the request and put it in the cache. If the request fails or the request is returned in POST mode, the code is as follows:

self.addEventListener('fetch'.function (event) {
  console.log("fetch", event.request.url)
  event.respondWith(
    caches.match(event.request).then(function (response) {
      if (response) {
        return response;
      }
      var request = event.request.clone(); 
      return fetch(request).then(function (httpRes) {
        if(! httpRes || (httpRes.status ! = =200&& httpRes.status ! = =304&& httpRes.type ! = ='opaque') || request.method === 'POST') {
          return httpRes;
        }
        var responseClone = httpRes.clone();
        caches.open(verson).then(function (cache) {
          cache.put(event.request, responseClone);
        });
        returnhttpRes; }); })); });Copy the code

Update mechanism

What if you have a new version of your code and you want your pages to be updated? Sw.js in the Service Worker is executed every time. With this feature we can update the version number of the sw.js file and then call self.skipWaiting() in the install event to make the Service Worker go directly to the Activate event. Then delete the old version cache by version number determination in the Activate event, and update the cache with elf.clients.claim().

var verson = "v1"
self.addEventListener('install'.event= > {
  console.log("install")
  this.skipWaiting()
})
self.addEventListener('activate'.event= > {
  console.log("activate")
  caches.open(verson).then(cache= > {
      return cache.addAll(['demo.html'.'js/jqurey.js'.'https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/154cd131b76e431897c2c04af97880df~tplv-k3u1fbpfcp-watermark.image'])})var delFn = caches.keys().then(function (cacheList) {
    return Promise.all(
      cacheList.map(function (cacheName) {
        if(cacheName ! == verson) {returncaches.delete(cacheName); }})); }) event.waitUntil(Promise.all([delFn])
    .then(() = > {
      return self.clients.claim()
    })
  )
});
self.addEventListener('fetch'.function (event) {
  console.log("fetch",event.request.url)
  event.respondWith(
      caches.match(event.request).then(function (response) {
          if (response) {
              return response
          }
          return fetch(event.request)
      })
  )
})
Copy the code