Service Worker 基本使用

569 阅读4分钟

背景

Web 应用程序传统上假设网络是可访问的,service worker 提供了离线能力,让我们可以在网络 offline 的时候,也能提供资源。

Service worker 独立于主线程,提供请求拦截、资源缓存、消息推送等功能。

接下来从 service worker 的原理与 API 使用、service worker 部署两个方面展开说明。

名词解释:sw: service worker

SW API 使用

前端页面-注册 API

SW 的注册逻辑位置处于需要添加 SW 能力页面的 html 中

window.addEventListener('load', () => {
    // Is service worker available?
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/sw-ad.js', { scope: '/pages' })
      navigator.serviceWorker.oncontrollerchange = () => {
        console.log(navigator.serviceWorker.controller, '====controller')
      }
    }
  });

初次注册时,为了防止跟页面初始化的请求抢夺资源,将 service worker 的注册放到页面 load 后

前端页面-其他 API

相关概念

  • ServiceWorkerContainer: 即 navigator.serviceWorker
  • ServiceWorkerRegistration:register 成功后,返回一个 registration
  • ServiceWorker:注册的 SW 实例

client-context:w3c.github.io/ServiceWork…

SW 上下文 - API

execution-context:w3c.github.io/ServiceWork…

_self.addEventListener('install', event => {
  console.log(`${Version} installing…`);
  _self.skipWaiting()
});

_self.addEventListener('activate', event => {
  console.log(`${Version} now ready to handle fetches!`);
  event.waitUntil(_self.clients.claim())
});

_self.addEventListener('fetch', event => {
    // ... event
});

SW 原理

SW 作为一个独立的线程,可对主线程的任何网络请求选择性的进行代理

SW 生命周期

enum ServiceWorkerState {
  "parsed" ,
  "installing" ,
  "installed" ,
  "activating" ,
  "activated"
};

首次安装

image.png

image.png

默认情况下,首次安装会执行到 activated ,但是不会立马接管页面中的请求,需要进行一次刷新或者重新打开。只在首次安装时执行 install、activate 事件,后续页面刷新不会二次执行。

在安装的过程中出错,即执行一遍 service worker 文件,会抛弃该 service worker,即安装失败,不影响页面。

clients.claim() 在 activating 阶段执行,可以直接开始接管页面中的请求,包括其他打开的在 scope 下的页面对应的 tab,不需要等待页面重新 reload

更新

service worker 的更新逻辑如下:

  • 默认情况下,会忽略 service worker 文件的 Http cache,每次都会下载 service worker,比对字节数是否发生改变,改变则触发更新流程。

更新流程如下:

image.png 新的 worker 的安装过程是独立的,不影响页面中正在工作的 worker,新 worker 到达 installed 后会进入 waiting 阶段

image.png

需要等待旧的 worker 控制的所有的页面全部关闭后,再次打开页面时,新的 worker 才会接管页面,旧的 worker 被废弃。

可以直接通过 clients.skipWaiting(),跳过等待阶段,直接接管页面中的请求

销毁

  1. 关闭当前 sw 控制的所有的页面,sw 会被销毁,当再次打开页面时,会再次激活
  2. 在更新流程中,新的 worker 接管页面的请求时,旧的 worker 会被销毁

SW 页面访问控制

名词:

  • 注册路径 registerPath:navigator.serviceWorker.register('/sw-ad.js', { scope: '/pages' }) 中的 /sw-ad.js

    • 注册时如果不指定 scope,如 registerPath 为 /a/b/c.js 那么 scope 为 /a/b/
  • 页面路径 pagePath: window.location.pathname

image.png

例如:

  1. Service worker 的 scope 设置为 / ,这样 service worker 能接管所有挂载在 www.baidu.com 这个域名下的页面,然后在编码层面针对请求的 referrer 进行区分,如果未命中我们想要处理的请求,直接转发到网络中即可
  2. Service worker 的 scope 设置为 /pages ,它会接管 /pages 路径下的页面的请求,但是由于 /pages 下不止推广管理这个页面,仍然要在编码层面进行区分
// sw-ad.js
_self.addEventListener('fetch', event => {
  if (event.request.referrer.indexOf('/pages/promotion.html') == -1) {
// 直接返回没有任何处理,该请求会直接被转发到网络中, sw 不做任何处理,仅仅做请求和响应的转发
    return
  }
});

SW 部署

Service worker 需要部署到单独的域下,单独起一个服务承载开发和线上 service-worker 静态资源访问

部署后直接通过 https://域名/sw.js 就能访问到

-dist/
 -sw-ad.js // sw 文件
-node_modules/
-server
 -index.js // 静态资源服务器 koa + koa-static
-bootstrap.sh

SW 缓存方案

Runtime Memory

self.addEventListener('install', event => {
  console.log(`${Version} installing…`);
  self.skipWaiting()
});

self.addEventListener('activate', event => {
  console.log(`${Version} now ready to handle fetches!`);
  event.waitUntil(self.clients.claim())
});
// 将缓存结果存在 map 中
const map = new Map()
self.addEventListener('fetch', event => {
  event.respondWith(map.get('xxx'))
});

Cache Storage

var CURRENT_CACHES = "font-cache-v1";

self.addEventListener("fetch", function (event) {
  console.log("Handling fetch event for", event.request.url);

  event.respondWith(
    // 打开以'font'开头的 Cache 对象。
    caches.open(CURRENT_CACHES).then(function (cache) {
      return cache
        .match(event.request)
        .then(function (response) {
          if (response) {
            console.log(" Found response in cache:", response);
            return response;
          }
        })
        .catch(function (error) {
          // 处理 match() 或 fetch() 引起的异常。
          console.error("  Error in fetch handler:", error);
          throw error;
        });
    })
  );
});

Service Worker 提供了 Cache api 对 fetch 的 request 和 response 对象进行一对一缓存,比较适合于页面关闭后缓存仍然被保存的情况,例如 PWA 会大量使用

[Cache api](https://developer.mozilla.org/zh-CN/docs/Web/API/Cache) 使用例子:developer.chrome.com/docs/workbo…