PWA 渐进式网络应用 - 2 - 使用 Service Workers
1. 简介
释放 PWA 力量的关键在于
Service Workers
,Service Workers
可以在浏览器后台挂起新线程,并且可以拦截网络请求进行本地缓存或请求转发,相当于充当 Web 应用程序与浏览器间的代理服务器,也可以在网络可用时,作为浏览器和网络间的代理
Service Workers
旨在使得能够创建有效的离线体验,拦截网络请求并给予网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。还允许访问推送通知和后台同步 API
Service Workers
是一个注册在指定源和路径下的事件驱动worker
它采用 javascript 控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度的缓存资源,可以通过它完全控制应用在特定情形下的表现
// 判断浏览器是否支持 service workers 支持就添加事件监听器
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker
// 注册 service worker
.register('/sw.js', { scope: '/' })
.then(function(registration) {
// 注册成功
console.log(
'ServiceWorker registration successful with scope: ',
registration.scope
);
})
.catch(function(err) {
// 注册失败:(
console.log('ServiceWorker registration failed: ', err);
});
});
}
注册时的
scope
参数是可选的,用于指定service workers
控制哪个路径下的内容,例如 指定{scope: '/admin'}
那么 这个service workers
只能捕获 path 为 ‘/admin’ 开头的 fetch 事件
2. 注意事项
Service Workers
运行在worker
上下文,因此不能访问 DOM- 一旦被
install
就永远存在,除非被 uninstall - 可以向客户端推送消息
- 相对于驱动应用的主 javascript 线程,它运行在其他线程中,不会造成堵塞
- 设计上完全异步,同步 API (XHR、localStorage) 不能在其中使用
- 安全上考虑,由于具有修改网络请求的能力,所以只能由 HTTPS 承载
3. Service Workers
缓存
由于
Service Workers
可以拦截网络请求,由此可以替代服务器告诉浏览器资源需要缓存多久,作为开发者可以全权掌握这里使用了
CacheStorage
可以更方便的将请求和请求结果缓存其他,但是它
- 只能缓存 get 请求
*
const cacheName = 'text';
// 进入 service worker 安装事件
self.addEventListener('install', function(event) {
console.log('herr');
event.waitUntil(
caches
.open(cacheName) // 打开缓存
.then(cache =>
cache.addAll([
'/text.json', // 添加文件到缓存中
'/about.html'
])
)
.then(() => console.log('suce'))
);
});
// 监听 fetch 事件
self.addEventListener('fetch', function(event) {
console.log(caches);
event.respondWith(
caches
.match(event.request) // 检查传入请求的 url 是否在缓存中有与之匹配的项
.then(res => res || fetch(event.request)) // 有的话就将其返回,否则通过正常网络请求获取
);
});
api 解析
event.waitUntil()
方法,用来确保service worker
不会再waitUntil()
里边的代码执行完毕之前安装完成可以使用
self.skipWaiting()
方法,手动触发Service Worker
activate
事件,并告知 Service Worker 立即开始工作,而无需等待用户跳转或刷新页面。caches.open(cacheName)
方法用来创建一个名为cacheName
的缓存,成功后返回 promisecache.addAll([])
方法用来指定缓存的资源列表
注意:以上方法,并不会在用户第一次访问网站的时候激活
Service Worker
来控制页面,只有当Service Worker
安装完成后用户刷新页面或者用户跳转至网站其他页面,才会被激活并且拦截请求
self.addEventListener('install', function(event) {
// 触发 activate 事件,告知 Service Worker 立即开始工作
event.waitUntil(self.skipWaiting());
});
skipWaiting()
函数强制等待中的Service Worker
被激活,如果配合self.clients.claim()
一起使用,可以确保底层Service Worker
的更新立即生效
const SW_VERSION = '1.0.1';
self.addEventListener('install', function(event) {
// 触发 activate 事件,告知 Service Worker 立即开始工作
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', function(event) {
// 确保更新立即生效
event.waitUntil(self.clients.claim());
});
4. Service Workers
版本控制
如果你更改了 WEB 文件,并想要确保用户接受到最新版本的文件,这个时候需要给
Service Workers
一个更新时间点每当对
Service Workers
文件本身做出任何更改时,都会自动触发Service Workers
的更新流程,每当用户导航至你的网站时,浏览器会尝试在后台重新下载Service Workers
即使下载的Service Workers
文件与当前的相比只有一个字节的差别,浏览器也会认为他是新的注意:’sw.js’ 更新后,会触发更新算法并触发
install
事件,但是此时已经处于激活状态的旧的Service Worker
还在运行, 新的Service Worker
安装完成后,会进入 waiting 状态,直到所有以打开页面都关闭,旧的Service Worker
自动停止,新的Service Worker
会在重开的页面生效
1. 自动更新
如果需要在每次更改
Service Worker
文件后,马上更新所有资源,则可以在install
事件中执行self.skipWaiting()
方法跳过waiting
状态,然后会直接进入 activate 阶段。接着在 activate 事件发生时,通过执行self.clients.claim()
方法,更新所有客户端上的Service Worker
,参考下边例子
// 每次版本更新是在这里做记录
const SW_VERSION = '1.0.6';
self.addEventListener('install', function(event) {
// 触发 activate 事件,告知 Service Worker 立即开始工作
event.waitUntil(self.skipWaiting());
});
// 监听 service workers 更新时间
self.addEventListener('activate', function(event) {
console.log('sw.js 更新');
event.waitUntil(
Promise.all([
// 更新客户端
self.clients.claim(),
// 清理旧版本
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
console.log(cacheName, SW_VERSION);
// 凡是缓存名不是当前版本号的缓存全部删除,这样最终只会保留当前版本号对应的文件
if (cacheName !== SW_VERSION) {
return caches.delete(cacheName);
}
})
);
})
])
);
});
// 缓存请求
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(res => {
// 检查缓存中有就直接返回缓存
if (res) {
return res;
}
// 请求是一个流,只能使用一次,为了再次使用这里需要克隆
const requestToCache = event.request.clone();
// 针对缓存中没有存在资源这里重新请求一下
return fetch(requestToCache).then(res => {
// 请求返回的结果错误 则不缓存
if (!res || res.status !== 200) {
return res;
}
// 克隆响应
const responseToCache = res.clone();
// 打开缓存,将响应添加进去
caches
.open(SW_VERSION)
.then(cache => cache.put(requestToCache, responseToCache));
return res;
});
})
);
});
2. 手动更新 registration.update()
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(registration => {
console.log('successful', registration);
document.getElementById('btn').addEventListener('click', function() {
registration.update().then(() => console.log('update'));
});
});
}
3. 24 小时强制更新
Service Worker 处除了由浏览器触发更新之外,还应用了特殊的缓存策略: 如果该文件已 24 小时没有更新,当 Update 触发时会强制更新。这意味着最坏情况下 Service Worker 会每天更新一次。
5. 生命周期
installing
安装,会触发install
事件,指定一些静态资源进行离线缓存installed
安装后,完成安装,等待其他Service Worker
线程被关闭。activating
激活,在这个状态下没有被其他的 Service Worker 控制的客户端,允许当前的 worker 完成安装,并且清除了其他的 worker 以及关联缓存的旧缓存资源,等待新的Service Worker
线程被激活。activated
激活后,可以处理功能性的事件 fetch、push、syncredundant
废弃状态
对应生命周期支持的事件
install
Service Worker
安装成功后被触发的事件,事件回调中处理缓存,有两个方法可以使用event.waitUntil()
需要传入一个 promise 为参数,表示等到该 Promise 为 resolve 状态,再变更状态为完成安装self.skipWaiting()
执行该方法表示,强制当前处在waiting
状态的Service Worker
进入activate
状态
activate
安装完成后,进入激活状态时触发,这里可以做缓存清理或者版本更新,回调中有两个方法可以使用event.waitUntil()
与之前的接口一样都是等到 参数 promise 为 resolve 状态,再变更状态为完成激活self.clients.claim()
取得页面控制权,这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面,之后会被停止。
message
Service Worker
运行于独立context
中,无法直接访问当前页面主线程的 DOM 等信息,但是通过postMessage
API,可以实现他们之间的消息传递,这样主线程就可以接受Service Worker
的指令操作 DOMfetch
请求,当浏览器在指定的 scope 下发起请求时,会触发,在回调中可以处理代理缓存push
推送,通过 PUSH API ,当订阅了推送服务后,可以使用推送的方式唤醒Service Worker
以响应来自系统消息,即使用户已经关闭了页面