背景
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.serviceWorkerServiceWorkerRegistration:register 成功后,返回一个 registrationServiceWorker:注册的 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"
};
首次安装
默认情况下,首次安装会执行到 activated ,但是不会立马接管页面中的请求,需要进行一次刷新或者重新打开。只在首次安装时执行 install、activate 事件,后续页面刷新不会二次执行。
在安装的过程中出错,即执行一遍 service worker 文件,会抛弃该 service worker,即安装失败,不影响页面。
clients.claim()在 activating 阶段执行,可以直接开始接管页面中的请求,包括其他打开的在 scope 下的页面对应的 tab,不需要等待页面重新 reload
更新
service worker 的更新逻辑如下:
- 默认情况下,会忽略 service worker 文件的 Http cache,每次都会下载 service worker,比对字节数是否发生改变,改变则触发更新流程。
更新流程如下:
新的 worker 的安装过程是独立的,不影响页面中正在工作的 worker,新 worker 到达
installed 后会进入 waiting 阶段
需要等待旧的 worker 控制的所有的页面全部关闭后,再次打开页面时,新的 worker 才会接管页面,旧的 worker 被废弃。
可以直接通过 clients.skipWaiting(),跳过等待阶段,直接接管页面中的请求
销毁
- 关闭当前 sw 控制的所有的页面,sw 会被销毁,当再次打开页面时,会再次激活
- 在更新流程中,新的 worker 接管页面的请求时,旧的 worker 会被销毁
SW 页面访问控制
名词:
-
注册路径 registerPath:
navigator.serviceWorker.register('/sw-ad.js', { scope: '/pages' })中的/sw-ad.js- 注册时如果不指定 scope,如 registerPath 为
/a/b/c.js那么 scope 为/a/b/
- 注册时如果不指定 scope,如 registerPath 为
-
页面路径 pagePath: window.location.pathname
例如:
- Service worker 的 scope 设置为
/,这样 service worker 能接管所有挂载在www.baidu.com这个域名下的页面,然后在编码层面针对请求的referrer进行区分,如果未命中我们想要处理的请求,直接转发到网络中即可 - 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…