Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.
Service Worker 对于前端开发工程师们已经不是一个新的概念了,依稀记得前两年还比较火主要的应用场景是 PWA,现在好像没有那么火热了,主要还是业务场景不太适合,比如我们的移动端大部分都是跑在 app 内。Service Worker 目前在我们移动端应用主要场景是用来缓存 js、图片、第三方 js 等一些静态资源。最近做了一个 feature,就是利用 Service Worker 缓存 html,提升应用性能 & 站点性能。接下来就跟大家分享一下我们的技术方案以及过程中遇到的一些问题和解决思路还有最后优化的效果。
2、Service Worker 简介
为了防止大家有些许遗忘,这里还是回顾一下 Service Worker 的相关知识点,熟悉的同学可以跳过
Service Worker 是浏览器在后台独立于网页运行的脚本,它本质上是一种 web worker,我们知道如果有一些耗时的任务如果需要优化我们可能首先想到的方案就是放到 web worker 中执行,因为它完全独立于 js 主线程,不会产生长任务从而导致一些用户体验问题。它相对于普通的 web worker 有了离线缓存的功能,也就是拦截处理网络请求,包括通过程序来管理缓存中的响应。
这个 API 之前之所以火热,是因为它可以支持离线体验,让开发者能够全面控制请求和响应,所以这个能力的可操作空间很大,有丰富的想象力。
使用 Service Worker 有一些需要注意的点:
生产环境必须在 https 下才能生效,因为 SW 的能力过于强大,所以必须要保证安全;
它是一种 web worker 无法访问 DOM。 Service Worker 通过响应 postMessage 接口发送的消息来与其控制的页面通信,页面可在必要时对 DOM 执行操作。
INSTALLING: 这个钩子表示 Service Worker 正在注册,**我们一般会在这个阶段预缓存资源** INSTALLED: 这个阶段表示 Service Worker 注册成功,**预缓存的资源也都成功缓存到本地** ACTIVATING: 当前页面以及没有其他 Service Worker 控制,**我们一般在这个阶段清理过期的缓存** ACTIVATED: 激活态,在这个阶段 Service Worker 正在接管了页面,可以拦截页面请求等一些操作。 REDUNDANT: 废弃,当前 Service Worker 被新的替换
如何借助 Service Worker 优化前端性能
Service Worker 对前端性能优化有什么帮助,到底能有多大的效果,我相信也是大家比较关注的。目前我们的方案是缓存静态资源在客户端来优化站点的性能,当然这也是业内比较常见的利用 Service Worker 优化前端性能的常见方案。我们都知道一个页面最终呈现在用户侧主要会经历 资源下载 以及 浏览器渲染,用下图简单呈现。
从上图我们可以肯定的是为了让页面快速呈现在用户侧,缩短资源加载时间是一个可行的方案。下面的对比也直观反应出了使用了 Service Worker 对页面加载的提升。
3、Service Worker 缓存静态资源流程
下面我们先通过一个简单的 demo 梳理一下 Service Worker 缓存静态资源的大致流程。
使用 create-react-app 脚手架初始化的 PWA 模板初始化,因为这个模板已经集成了 Service Worker
缓存 html 会有点麻烦,可能大家会说直接使用构建流程中生成的 html 就可以。但是这样有个问题就是我们缓存的 html 可能是旧版本的,因为我们除了 SSR 的路由都是上了 CDN 缓存的。举个例子,https://domain/download,这个页面是 CSR 渲染的,所以平时我们访问他的内容应该是从 CDN 节点返回的,然后我们缓存 https://domain/download 页面的 html 到本地,这个时候是没什么问题的,但是一旦这个页面在某个版本中被修改了上线后,Service Worker 重新预缓存这个路由的 html 到本地,它这个时候很有可能就是从 CDN 节点中拿到旧版本的内容,这样就导致了不能及时缓存最新版本的页面。 用下图简单说明这个问题:
1 这个流程表示首次方案页面,页面安装 Service Worker 然后预缓存 /download/ 页面的 html 到本地缓存,同时 CDN 节点也会缓存 html;
2 这条流程表示 Service Worker 安装成功、激活后用户访问页面,然后 /download/ 路由的请求被 Service Worker 拦截使用本地缓存的 html 响应,这一步没有问题。
3 这条流程表示当新版本发布后,新版的 Service Worker 重新预缓存 /download/ 页面的 html,但是这时候拿到的内容很有可能就是 CDN 节点上缓存的 html,导致 Service Worker 缓存到本地的 html,一直是旧的!
我们知道缓存 js 文件不会有这样的问题,因为 js 的路径中都包含了 **contenthash**,所以 Service Worker 缓存的永远都是最新的文件,那么最容易想到的解决方案就是给预缓存的 html 路径中也带上 **contenthash**。我们现在只需要在已经构建出来的 static html 资源基础上,再生成一份带 contenthash 的 html 提供给 Service Worker 预缓存使用。如下图所示:
所以 Service Worker 缓存 html 到本地 Caches 就应该是:
1 2 3
{ [pathname.[contenthash].html] : content, }
但是 Service Worker 从本地 Caches 查询的时候需要额外做一层映射,因为我们访问的路由是 https://domain/download, 缓存到本地的请求是 https://domain/download.[contenthash].html,所以需要有一层请求的路由到实际缓存的路由的映射关系,这层关系我们可以在生成预缓存 html 的时候处理好,然后注入到 Service Worker 文件中。
如果大家是想使用 Service Worker 来做性能方面的优化这里有几个小提醒看看自己的站点是不是真的适合:
多页应用
SSG
常规的前端优化手段都已经做过了且当前站点性能已经比较不错
之所以这样说是因为如果缓存的 html 里面没有任何内容,只有一个根节点,页面内容都要在客户端构建我觉得用户体验也不会很好;从我们做前端性能优化的经验来看,Service Worker 的优先级不是最高的,因为还有很多其他的前端优化技巧比 Service Worker 来的更有效果,所以说常规的性能优化手段都做过了可以尝试 Service Worker。
关于离线包
现在很多公司内部都有离线包的方案,常规方案的实现是 app 启动后下载、更新离线包资源到客户端本地,然后 webview 拦截对静态资源的请求从而返回本地缓存的资源。这个过程跟 Service Worker 离线访问的原理是一致的而且 Service Worker 是一个 Web 标准,所以随着标准的不断完善、各浏览器厂商的跟进,传统的离线包是不是可以退出历史舞台。