Service Workers 不仅可以应用于PWA
,PC 端也可以利用其强大的功能实现一些有趣的优化,网络中有很多关于 Service Workers 介绍的比较好的文章,基于小册宗旨,本文并不是一篇Service Workers
的详细教程。
一些注意的点
Service worker 是一个注册在指定源和路径下的事件驱动 worker。
它运行在 worker 上下文中,不能访问 DOM,相对于驱动应用的主 JavaScript 线程,它运行在其他线程中,所以不会造成阻塞。
它采用 JavaScript 控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。你可以完全控制应用在特定情形(最常见的情形是网络不可用)下的表现。
它设计为完全异步,同步 API(如 XHR 和 localStorage)不能在 Service worker 中使用。
在已经支持 serivce worker
的浏览器的版本中,很多特性没有默认开启,
了解浏览器对 serivce worker 的支持性。
如果你发现示例代码在当前版本的浏览器中怎么样都无法正常运行,你可能需要开启一下浏览器的相关配置。
另外,需要注意的是,出于安全原因 Service Workers 要求必须在 HTTPS 下才能运行,为了便于本地开发,localhost
也被浏览器认为是安全源。
注册 Service Worker
使用 ServiceWorkerContainer.register()
函数来注册站点的 service worker
(service sorker
只是一个 JavaScript 脚本)。
注意,这个文件的url
是相对于origin
, 而不是相对于引用它的那个 JS 文件。
配置缓存清单
在 Service Worker
注册之后,浏览器会尝试为你的页面或站点安装并激活它。
install
事件会在注册完成之后触发,install
事件一般是被用来填充你的浏览器的缓存能力。
为了达成这个目的,我们使用了 Service Worker
的新的标志性的存储 API — Cache
,一个 service worker 上的全局对象,它使我们可以存储网络响应发来的资源,并且根据它们的请求来生成 key
。
Cache 接口像 workers 一样,是暴露在 window 作用域下的,尽管它被定义在 service worker 的标准中, 但是它不必一定要配合 service worker 使用。
// serviceworker.js
const cacheName = 'my-cache';
// self 代表 worker 线程自身,即子线程的全局对象
self.addEventListener('install', event => {
// 用来存放缓存的静态资源和路由
const filesToCache = [
'/',
'/static/css/reset.css',
'/static/css/css-loader.css',
'/static/css/create-version.css',
'/static/css/bootstrap-grid.css',
'/static/css/bootstrap.css',
'/static/js/css-animations.js',
'/static/js/vue.js',
];
event.waitUntil(
// caches 是 CacheStorage 的实例子:caches instanceof CacheStorage -> true
// 使用 CacheStorage.open(cacheName) 打开一个 Cache 对象
caches.open(cacheName)
// 将字符串 URL 数组添加到缓存中
.then(cache => cache.addAll(filesToCache))
.catch(e => console.error(e))
);
});
这里我们新增了一个 install
事件监听器,接着在事件上接了一个 ExtendableEvent.waitUntil() 方法。
waitUntil()
用来确保 service worker 不会在 waitUntil()
里边的代码执行完毕之前安装完成。
如果caches.open(cacheName)
被rejected
,安装就会失败,这个worker
不会做任何事情(例如:URL 拼写错误)。
注意:首次注册/激活 Service Worker
线程的页面需要再次加载才会受其控制(二次生效)。在成功安装完成并处于激活状态之前,Service Worker
线程不会收到 fetch
(下文会提到)和 push
事件。
使用 fetch 拦截请求
现在已经将站点资源缓存了,还需要告诉 Service Worker
让它用这些缓存内容来做点什么,我们可以借助 fetch API 进行一层拦截。
//fetch 事件处理程序,拦截请求并应用于所有缓存中的静态资产
self.addEventListener('fetch', e => {
e.respondWith(
caches.match(e.request)
.then(response => response ? response : fetch(e.request))
)
});
caches.match(event.request)
允许我们对网络请求的资源和 cache
里可获取的资源进行匹配,查看是否缓存中有相应的资源。这个匹配通过 url
和 vary header
进行,就像正常的 http
请求一样。
查看 Fetch API documentation 了解更多有关 Request 和 Response 对象的更多信息。
当匹配到 catch
资源时,caches.match(event.request)
就会 resolve
,在 then
回调中就可以直接返回 response
。
caches.match(e.request)
.then(response => response)
如果没有匹配到资源,caches.match(event.request)
就会 reject
,可以告诉浏览器直接使用 fetch
进行默认的网络请求。(意味着在网络可用的时候可以直接像服务器请求资源)
caches.match(e.request)
.then(response => response ? response : fetch(e.request))
更新缓存
更新缓存清单,可以借助 activate
事件进行处理,本文案例代码并没有对其进行实现。
只是使用 clients.claim() 对页面进行控制权获取,这样之后打开页面都会使用版本更新的缓存。
self.addEventListener('activate', e => self.clients.claim());
- 关于如何删除旧缓存并对缓存进行更新,推荐 MDN 教程。
- 关于
SPA
项目中由于引入hash
会引起 URL 的变化,在这种情况下如何更新Service Worker
的缓存,推荐这篇 service worker 在移动端 H5 项目的应用。
优化后的效果
Chrome 有一个chrome://inspect/#service-workers
可以展示当前设备上激活和存储的 service worker。还有个chrome://serviceworker-internals
可以展示更多细节来允许你开始/暂停/调试 worker 的进程。
通过 Chrome devTools
的 Application Tab
我们可以查看当前服务工作线程的运行情况。
附 serviceworker.js
完整代码:
const cacheName = 'my-cache';
// self.clients.claim() 取得页面控制权,这样之后打开页面都会使用版本更新的缓存
self.addEventListener('activate', e => self.clients.claim());
self.addEventListener('install', event => {
// 用来存放缓存的静态资源和路由
const filesToCache = [
'/',
'/static/css/reset.css',
'/static/css/css-loader.css',
'/static/css/create-version.css',
'/static/css/bootstrap-grid.css',
'/static/css/bootstrap.css',
'/static/js/css-animations.js',
'/static/js/vue.js',
];
event.waitUntil(
// caches 是 CacheStorage 的实例子:caches instanceof CacheStorage -> true
// 使用 CacheStorage.open(cacheName) 打开一个 Cache 对象
caches.open(cacheName)
// 将 filesToCache (字符串 URL 数组)添加到缓存中
.then(cache => cache.addAll(filesToCache))
.catch(e => console.error(e))
);
});
// fetch 进行拦截
self.addEventListener('fetch', e => {
e.respondWith(
caches.match(e.request)
.then(response => {
return response ? response : fetch(e.request)
})
)
});
延伸阅读推荐:这篇文章。
同系列文章:
相关文献
- Service Worker API - https://developer.mozilla.org...
- 使用 Service Workers - https://developer.mozilla.org...
- Cache - https://developer.mozilla.org...
- Cache.addAll() - https://developer.mozilla.org...
- CacheStorage.open() - https://developer.mozilla.org...
- CacheStorage.match() - https://developer.mozilla.org...
- Fetch API https://developer.mozilla.org...
- WorkerOrGlobalScope.fetch() - https://developer.mozilla.org...
- FetchEvent - https://developer.mozilla.org...
- Request - https://developer.mozilla.org...
- Response - https://developer.mozilla.org...