Service Worker 的初衷是极致优化用户体验,带来丝滑般流畅的离线应用。但同时也可以用作站点缓存使用。它本身类似于一个介于浏览器和服务端之间的网络代理,可以拦截请求并操作响应内容。功能强大,但由于兼容性问题,更适合用作渐进增强来使用。
一、前言
- Service Worker 是独立于当前页面的一段运行在浏览器后台进程里的脚本,它有自己独立的注册文件;它是 Web Worker 的一种,不能够直接操作 DOM;
- 出于安全问题考虑,它只能在 HTTPS 域名下或者 localhost 本地运行;
- 可以通过 postMessage 接口传递数据给其他 JS 文件;
- Service Worker 中运行的代码不会被阻塞,也不会阻塞其他页面的 JS 文件中的代码;
- 每个 Service Worker(注册文件)都有自己的作用域,它只会处理自己作用域下的请求,而 Service Worker 的存放位置就是它的最大作用域;
- 缓存的资源存储在 Cache Storage 中,缓存不会过期,但是浏览器对每个网站的 Cache Storage 的大小有硬性限制,所以需要清理不必要的缓存;
二、Service Worker 的生命周期
- 注册 Service worker,在网页上生效;
- 安装成功,激活 或者 安装失败(下次加载会尝试重新安装);
- 激活后,在 sw 的作用域下作用所有的页面,首次注册 sw 不会生效,下次加载页面才会生效;已经注册的 sw 不会重复注册;不会因为页面的关闭而被销毁;
- sw 作用页面后,处理 fetch(网络请求)和 postMessage(页面消息)事件 或者 被终止(节省内存)。
三、Service Worker 安装注册
注册文件
1 | if ('serviceWorker' in window.navigator) { |
注销文件
1 | if ('serviceWorker' in window.navigator) { |
register 方法接受两个参数,第一个是 service worker 文件的路径,第二个参数是 Serivce Worker 的配置项,可选填,其中比较重要的是 scope 属性。
拓展 Service Worker 作用域
scope的默认值为 ./
(注意,这里所有的相对路径不是相对于页面,而是相对于sw.js脚本的),因此,navigator.serviceWorker.register('/static/home/js/sw.js')
代码中的 scope 实际上是/static/home/js
,Service Worker也就注册在了/static/home/js
路径下,显然无法在/home
下生效。
可以通过添加 Service-Worker-Allowed
响应头的方式来扩展 service worker 的作用域:
1 | // express 扩展 service worker scope |
打包工具生成静态资源注册文件
自己本地调试,可以一个个写进 Service Worker 的注册文件里调试;实际开发中可以借助 gulp / webpack 等打包工具等生成站点静态文件的 sw 注册文件;
以 gulp 为例,使用 sw-precache
插件生成注册文件:
1 | gulp.task('generate-service-worker', function(callback) { |
四、Service Worker.js 注意事项
- 不要给 service-worker.js 设置不同的名字
实际开发过程中,为了避免静态资源缓存,通常的做法是在打包压缩静态资源的时候,在文件名后边加上 MD5 后缀,让浏览器认为这是一个新文件从而重新发起请求,但是这种做法在 service-worker.js 上是不可取的;
第一种情况:如果缓存了 html 文件,service-worker.js 的文件因为是在 html 中引入的,所以更改 service-worker.js 的名字并不会更新。
第二种情况:只缓存了css,js 文件,未缓存 html 文件;页面引入了新的 service-worker.js ,但是旧版本的 service-worker.js 还在使用中,会导致页面状态有问题。 - 不要给 service-worker.js 设置缓存
理由和第一点类似,也是为了防止在浏览器需要请求新版本的 sw 时,因为缓存的干扰而无法实现。毕竟我们不能要求用户去清除缓存。因此给 sw 及相关的 JS (例如 sw-register.js,如果独立出来的话)设置 Cache-control: no-store 是比较安全的。
五、遇到的问题
- 接收不到浏览器的fetch事件:
原因:静态资源缓存:页面路径不能大于 Service worker 的 scope (详情) public/*
无法匹配public路径下的所有文件, addCaches 时只能写fileName?
原因:service worker 没有通配符 * 这个概念,/sw-test/
这个 path 只是让 sw 寻找缓存时的一个入口,用以区分各个路径的缓存(详情);
解决方案:service-worker.js 使用官方的sw-precache
插件生成(详情);- 如果 service worker 缓存的了全部的 js 和 img 会不会导致 cacheStorage 很占用用户的系统空间?
不会,cacheStorage 的值不是无限大的。虽然各个浏览器分配给各站点的 cacheStorrage 的值不一样,同时也受用户设备空间影响。
落地情况
还是觉得 Service Worker 最适合在 SPA、文档类应用的等场景使用,才能把离线缓存的优势发挥出来。比如 Vue 的官网。
2019.4.23
未落地。主要原因有两点:
- 工作中想要使用 Service worker 提供离线缓存服务的是一个负责 APP 内嵌页面的 H5 站点,HTML都是动态渲染的,活动数据是实时性较强,缓存数据意义不大;
- 这个站点的页面入口都是几乎都是单独的活动页,没有一个统一 sw 注册的入口;
2020.3.16
重新看这篇文章的时候,如果在几个主要的活动入口页引入 sw 的注册文件,那么这几个长期的活动就可以应用 sw 缓存了,但这并没有覆盖全站,所以依然不是好的解决方案。
2024.5.9
查了下现公司的使用方式,在主页注册,pv/uv 高的页面会使用 Service Worker 拦截请求将响应缓存到 indexDB 中。
应用场景
这部分总结摘录自这篇文章:Service Worker 从入门到出门
- 网站功能趋于稳定:频繁迭代的网站似乎不方便加 Service Worker。
- 网站需要拥有大量用户:管理后台、OA系统等场景似乎不是很有必要加 Service Worker。
- 网站真的在追求用户体验:优先保证网站其他功能正常运行,在此基础上引入 SW 来优化加载体验。
- 网站用户体验关乎用户留存:体验优先于功能。
简单总结:Service Worker 在实际应用前,应考虑成本和收益,不要为了用技术而用技术。