Service Worker Learning and Practice (1)-Offline Caching

  Front end, javascript

what isService Worker

Service WorkerIn essence, it acts as a proxy server between the Web application and the browser, and also acts as a proxy between the browser and the network when the network is available. They are intended to, inter alia, enable the creation of an effective offline experience, intercept network requests and take appropriate actions based on whether the network is available and whether updated resources reside on the server. They also allow access to push notifications and background synchronizationAPI.

  • Service WorkerThe essence of is aWeb WorkerWhich is independent ofJavaScriptThe main thread, so it cannot be accessed directlyDOM, also can’t directly accesswindowObject, but,Service WorkerAccessiblenavigatorObject, you can also use messaging (postMessage) andJavaScriptThe main thread communicates.
  • Service WorkerIs a network agent, it can controlWebAll network requests for the page.
  • Service WorkerIt has its own life cycle and is well used.Service WorkerThe key is to flexibly control its life cycle.

Service WorkerThe role of

  • For browser caching
  • Implement offlineWeb APP
  • Message push

Service WorkerCompatibility

Service WorkerIs an advanced feature of modern browsers, it depends onfetch APICache StoragePromiseAnd so on, among them,CacheProvidedRequest / ResponseThe storage mechanism of object pairs,Cache StorageStore multipleCache.

Example

In understandingService WorkerBefore the principle of, let’s look at a paragraphService WorkerExamples of:

self.importScripts('./serviceworker-cache-polyfill.js');

var urlsToCache = [
  '/',
  '/index.js',
  '/style.css',
  '/favicon.ico',
];

var CACHE_NAME = 'counterxing';

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

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
    .then(function(response) {
      if (response) {
        return response;
      }
      return fetch(event.request);
    })
  );
});


self.addEventListener('activate', function(event) {
  var cacheWhitelist = ['counterxing'];

  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

Let’s start to analyze and uncover it one by one.Service WorkerThe mysterious veil of:

polyfill

First look at the first line:self.importScripts('./serviceworker-cache-polyfill.js');, introduced hereCache APIOne ofpolyfillThispolyfillSupport makes it possible to use it under a lower version of the browser.Cache Storage API. Want to achieveService WorkerThe function of, generally need to matchCache APIThe proxy network requests to the cache.

InService WorkerIn the thread, use theimportScriptsintroducepolyfillScripts, aimed at compatibility with earlier versions of browsers.

Cache Resources ListAndCache Name

After that, use aurlsToCacheList to declare static resources that need to be cached, then use a variableCACHE_NAMETo determine the current cachedCache Storage NameIt can be understood here thatCache StorageIs aDB, andCACHE_NAMEIt isDBName:

var urlsToCache = [
  '/',
  '/index.js',
  '/style.css',
  '/favicon.ico',
];

var CACHE_NAME = 'counterxing';

Lifecycle

Service WorkerIndependent of browserJavaScriptThe main thread has its own independent life cycle.

If you need to install it on the websiteService Worker, you need to use theJavaScriptThe following code is introduced into the main threadService Worker.

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js').then(function(registration) {
    console.log('成功安装', registration.scope);
  }).catch(function(err) {
    console.log(err);
  });
}

Here, must pay attention tosw.jsThe path of the file, in my example, is under the current domain root directory, which means that,Service WorkerAnd can act as a proxy for all requests of the current website ifService WorkerBe registered to/imaging/sw.jsNext, that can only be agent/imagingNetwork request under.

Can be usedChromeConsole, view the current page ofService WorkerSituation:

After the installation is completed,Service WorkerWill experience the following life cycle:

  1. Download (download)
  2. Installation (install)
  3. Activate (activate)
  • User access for the first timeService WorkerWhen controlling a website or page,Service WorkerIt will be downloaded immediately. At least every24It will be downloaded once every hour. It may be downloaded more frequently, but every24It must be downloaded once every hour to prevent bad scripts from taking effect for a long time.
  • After the download is complete, start the installationService WorkerIn the installation phase, we usually need to cache some static resources that we have declared beforehandurlsToCacheDeclare in advance.
  • After the installation is completed, activation will begin and the browser will try to download it.Service WorkerThe script file, after downloading successfully, will be linked to the previously cachedService WorkerIf the script file is compared with the previous oneService WorkerThe script file is different, proving thatService WorkerHas been updated and will triggeractivateEvents. Activation is complete.

As shown in the figure, isService WorkerThe approximate life cycle:

install

After the installation is complete, try caching some static resources:

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

First of all,self.skipWaiting()Execute, inform the browser to skip the waiting phase directly and eliminate expired ones.sw.jsTheService WorkerScript, directly start trying to activate the newService Worker.

Then usecaches.openOpen oneCache, after opening, throughcache.addAllTry caching our pre-declared static files.

Monitorfetch, proxy network request

All network requests on the page will pass throughService WorkerThefetchEvent trigger,Service Workerviacaches.matchTry fromCacheIf the cache hits, it will directly return to theresponseOtherwise, create a real network request.

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
    .then(function(response) {
      if (response) {
        return response;
      }
      return fetch(event.request);
    })
  );
});

If we need to, in the process of request, toCache StorageTo add a new cache to thecache.putMethod to add, look at the following example:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
    .then(function(response) {
      // 缓存命中
      if (response) {
        return response;
      }

      // 注意,这里必须使用clone方法克隆这个请求
      // 原因是response是一个Stream,为了让浏览器跟缓存都使用这个response
      // 必须克隆这个response,一份到浏览器,一份到缓存中缓存。
      // 只能被消费一次,想要再次消费,必须clone一次
      var fetchRequest = event.request.clone();

      return fetch(fetchRequest).then(
        function(response) {
          // 必须是有效请求,必须是同源响应,第三方的请求,因为不可控,最好不要缓存
          if (!response || response.status !== 200 || response.type !== 'basic') {
            return response;
          }

          // 消费过一次,又需要再克隆一次
          var responseToCache = response.clone();
          caches.open(CACHE_NAME)
            .then(function(cache) {
              cache.put(event.request, responseToCache);
            });
          return response;
        }
      );
    })
  );
});

In the project, we must pay attention to the control of cache, interface requests are generally not recommended cache. Therefore, in my own project, there is no dynamic caching scheme here.

activate

Service WorkerThere is always a need for a new day. With the iteration of versions, one day, we need to release the new version of the features online. At this time, we need to eliminate the old cache and the old one.Service WorkerAndCache StorageHow to eliminate it?

self.addEventListener('activate', function(event) {
  var cacheWhitelist = ['counterxing'];

  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});
  1. First of all, there is a white listCacheIs not eliminated.
  2. After that, it passedcaches.keys()Get everythingCache Storage, the not in the white listCacheElimination.
  3. Elimination of usecaches.delete()Methods. It receivescacheNameAs a parameter, delete thiscacheNameAll caches.

sw-precache-webpack-plugin

sw-precache-webpack-pluginIs awebpack plugin, can be configured in thewebpackGenerate what we want when packagingsw.jsTheService WorkerScript.

One of the simplest configurations is as follows:

var path = require('path');
var SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');

const PUBLIC_PATH = 'https://www.my-project-name.com/';  // webpack needs the trailing slash for output.publicPath

module.exports = {

  entry: {
    main: path.resolve(__dirname, 'src/index'),
  },

  output: {
    path: path.resolve(__dirname, 'src/bundles/'),
    filename: '[name]-[hash].js',
    publicPath: PUBLIC_PATH,
  },

  plugins: [
    new SWPrecacheWebpackPlugin(
      {
        cacheId: 'my-project-name',
        dontCacheBustUrlsMatching: /\.\w{8}\./,
        filename: 'service-worker.js',
        minify: true,
        navigateFallback: PUBLIC_PATH + 'index.html',
        staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
      }
    ),
  ],
}

In progresswebpackAfter packaging, a file namedservice-worker.jsFile for cachingwebpackPacked static file.

One of the simplestExample.

Service Worker CacheVSHttp Cache

Yes, compared withHttp HeaderCache,Service WorkercoordinateCache StorageAlso has its own advantages:

  1. Cache and update coexist: each update version, with the help ofService WorkerYou can use the cache to return immediately, but at the same time you can initiate a request to check if there is a new version update.
  2. Non-invasive:hashThe value is really too ugly.
  3. Not easily washed away:HttpCaches are easily flushed out and expire, whileCache StorageIt is not easy to be washed away. There is no saying about the expiration date.
  4. Offline: with the help ofService WorkerThe off-line access application can be realized.

But the disadvantage is that due toService WorkerDepend onfetch APIDepend onPromiseCache StorageWait, compatibility is not very good.

SOA Anarchy Afterword

This article is only a brief summaryService WorkerThe basic use and use ofService WorkerA simple way to do client caching, however,Service WorkerThe role of the is far more than that, for example, with the help ofService WorkerDo offline applications, push for network applications (refer topush-notifications) etc.

Even with the help ofService Worker, caching interfaces, in my project, is actually not so complicated. However, the advantage of doing interface caching is that it supports offline access and can normally access us when offline.WebApplication.

Cache StorageAndService WorkerIt’s always inseparable.Service WorkerThe best use of is actually coordinationCache StorageDo offline caching. With the aid ofService Worker, can easily control network requests, and adopt different strategies for different network requests. For example, forCacheIn fact, there are many kinds of situations. For example, the network request can be used preferentially, and the cache can be used again when the network request fails, or the cache and the network request can be used simultaneously. On the one hand, the request is checked, on the other hand, the cache is checked, and then whichever is faster is used.