Service worker 入门手册
Service worker 基础
MDN 定义:
Service worker 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。这个 API 旨在创建有效的离线体验,它会拦截网络请求并根据网络是否可用来采取适当的动作、更新来自服务器的资源。它还提供入口以推送通知和访问后台同步 API。
Service worker 也是 PWA 技术的基石。
作用域
service worker 通过主线程执行 ServiceWorkerContainer.register('/sw.js', { scope: './' })
注册。
service worker 的最大作用域为 sw.js 脚本所在位置,如 https://yuanyxh.com/js/sw.js
所能控制的作用范围是 https://yuanyxh.com/js/**
。
可以通过 scope 参数进行精细控制,如 ServiceWorkerContainer.register('/sw.js', { scope: './assets/' })
控制作用范围为 https://yuanyxh.com/assets/**
。
sw.js 中可以注册 fetch
事件,它会拦截作用范围内的网络请求:
生命周期
service worker 有三个生命周期
download 下载阶段
install 安装阶段
activate 激活阶段
在首次前往包含 service worker 的站点时进入 download 阶段,此时会下载 sw.js。如果 sw.js 文件有任何变更,会触发新的生命周期流程,也可以通过 update
手动触发更新检查。
下载完成后进入 install 阶段,如果 sw.js 注册了 install
事件则触发。install
事件中可以进行预缓存等操作,请求一些需要预先缓存的资源。
在 install 阶段中,可以调用 ExtendableEvent.waitUntil()
方法,该方法接收一个 Promise,在此 Promise 决议前,fetch
事件会延迟触发。
传入 waitUntil
的 Promise 被拒绝时,视为本次安装失败。
在安装完成后,进行激活阶段,可以在 activate
事件中清除旧版本的缓存。
如果当前存在已激活的 service worker,且存在被 service worker 管理的客户端(可能是打开的作用范围内的标签页),新的 service worker 会进入 activating
阶段,直到所有客户端被关闭后激活。
可以通过 skipWaiting()
方法跳过 activating
阶段,使新的 service worker 立即接管当前客户端。
skipWaiting
通常配合 clients.claim()
方法使用,该方法允许一个激活的 service worker 将自己设置为其作用域内所有客户端的控制者。
Workbox 使用与配置
Web Service worker 的使用过于繁杂,其中包含大量的 API,初期较难在项目中快速集成,为此 Google 开发团队封装了一个开发工具:Workbox。
缓存策略
Workbox 存在两种不同的缓存模式:
预缓存,资源在 service worker 的 install 阶段预取并缓存
运行时缓存,资源在网络请求时动态缓存
同时预定义了几种不同的缓存策略,以此来应对需要不同新鲜度的网络资源,包括:
Cache only 仅缓存
Network only 仅网络
Cache first, falling to network 缓存优先,回退至网络
Network first, falling to cache 网络优先,回退至缓存
Stale-while-revalidate 过时重新验证
Cache only 适合几乎不会变更的、重要程度低的资源,如网站字体文件。
Network only 针对变更频繁,数据新鲜度要求较高的资源,如查询、变更接口。
Cache first, falling to network 对于 html、css、js、网站 logo 这类不经常变更的静态资源较为合适。
Network first, falling to cache 用于数据新鲜度要求较高,但希望网络不可用时也能提供服务的资源。
Stale-while-revalidate 优先提供缓存资源,在后台验证缓存时效性并更新。
配置
Workbox 在 npm 中有一个 workbox-build
包,其中导出了一个 generateSW
函数,通过传入特定的配置,他会帮我们自动生成一个 service worker 脚本。以我网站的配置为例进行讲解:
完整的配置可以在 workbox-build
中的类型定义中找到。
注意事项:配置了 navigateFallback
后,导航失败的都会回退到指定 url,当你有一个部署在此应用的子站点时,如 /illustrate
,访问此 url 时会被 service worker 重定向至主应用,可以配置 navigateFallbackDenylist
将子应用排除在外。
使用
配置 Workbox 后,在项目中使用 Service worker 变的简单,你只需要在 webpack、vite 等打包工具输出编译产物后执行 generateSW
函数即可生成 sw.js
文件,但同时也需要在你的主应用中添加注册、更新的代码:
Service worker 调试
Service worker 因为缓存、代码运行在主线程之外等原因,通常较难调试,但好在像 Chrome 之类的现代浏览器都提供了良好的调试工具,我们就来聊聊如何在 Chrome 中调试 Service worker。
在 chrome://serviceworker-internals/
中可以查看所有已注册的 Service worker:
上图是开发者工具 Application -> Service workers 面板,来看看图中的选项:
Offline 表示网站将在离线模式下运行,用来测试 service worker 缓存是否生效
Update on reload 表示在重新加载时触发更新
Bypass for network 表示始终从网络获取,一般用于需要实时查看数据更新的情况
Update 立即触发更新检查
Unregister 取消注册 service worker
Source 指向了 service worker 脚本所在
Status 指示了当前 service worker 的状态/生命周期
Upate cycle 记录了当前版本与生命周期时长
同样在 Application 中,可以查看 indexedDB 和 Cache storage 选项:
Cache 是真正存放缓存的地方,同时为了快速判断缓存是否失效,Workbox 还将缓存时间和有效期存储在 indexedDB 中。
再来看看 Sources 面板,你可以在这里看到已注册的 service worker 脚本,同时可以像调试其他脚本一样在上面打上断点:
为了更好的调试 sw.js,你还可以在 Console 面板中将 JavaScript 上下文切换至 service worker:
Service worker 注意事项
接下来聊聊使用 Service worker 过程中需要注意的一些问题,如更新延迟、数据的时效性等等。
更新延迟
在一般情况下,浏览器只会在导航至 service worker 作用范围内时去检查更新,这对于使用 SPA 的网站来说是不太友好的,假设一个 SPA 网站已经在运行且存在激活的 service worker,此时后台发布了新版本,这个 SPA 网站是无法接收到更新的,因为在 SPA 中的所有路由操作都由客户端脚本处理。
为了解决这种更新延迟的问题,我们可以在合适的时机主动触发 registration.update()
方法去触发更新检查,比如 visibilitychange
事件中,或以一定的时间间隔来调用。
版本迭代
假设我们有一个后台项目,使用了 service worker 缓存了 html、css、js 资源,这个后台项目有一个对应的后端接口。现在需要进行版本迭代,前端、后端都有变更,此时因为 service worker 更新延迟的原因,客户端使用旧版本的前端代码请求了新版本的后端接口,可能出现问题。
可以在接口请求中加入版本字段,由后端适配。
存储配额
Service worker 依赖于 self.caches
接口缓存资源,caches 有效存储空间受限于浏览器为每个源分配的额度。你可以通过 navigator.storage.estimate()
获取当前源的大概存储信息,也可以在 Application -> Storage 选项中查看:
取消注册
你可以通过 ServiceWorkerRegistration.unregister()
来取消注册 service worker,但需要注意的是,避免在重新进入页面时再次注册 service worker。
启动延时
在首次导航至 service worker 作用域内的页面时,浏览器会首先启动 service worker,在此期间浏览器无法处理任何事件,如果启动耗时过长,我们可能会看到空白的页面,可以通过导航预加载来并行请求网络上的资源以加快速度:
Service worker 能做什么
将 service worker 看做一个指定作用域下共享的代理服务器,他可以:
拦截并重写请求、响应
离线缓存与访问
后台数据同步;利用 BackgroundSync API 对失败请求进行重试,即使用户离开了站点
多个标签页的集中数据管理
以开发为目的,对 ts、less 等类型的模块进行客户端编译和依赖管理
性能增强,预取用户可能很快需要的资源
API 接口模拟
通过 service worker 提供的离线访问能力非常适合一些工具、博客类的站点,如 draw.io;Workbox 也提供了非常多的插件帮助我们构建功能强大的 Service worker。
参考
Service_Worker_API: Service_Worker_API
Workbox: Workbox
---end
版权声明: 本文为 InfoQ 作者【yuanyxh】的原创文章。
原文链接:【http://xie.infoq.cn/article/82fa668c788c30fae68d8321e】。
本文遵守【CC BY-NC】协议,转载请保留原文出处及本版权声明。
评论