大家好嗷我是 HOHO。
在上一篇文章 PWA 开发指南(一)中我们介绍了如何最快最简单的接入 PWA 应用。这一篇我们就来聊一聊 PWA 中的核心技术:Service Worker。
作为先导文章,本文不会涉及太多的实践,而是从科普介绍的角度出发,带大家看一下在现代化的前端项目里开发 servie worker 都会遇到哪些概念和名词,防止下一篇实践的时候晕头转向。
简单介绍 service worker
service worker 其实也是一个 js 文件(我们下面简称 sw.js),拥有和其他 web worker 类似的隔离空间。不过他拥有一些特殊的回调事件,比如 fetch 事件。
任何从你这个网站发出的任何类型的请求,都会先从你这个 fetch 回调里过一遍。
同时,浏览器还提供了一个 Cache storage,可以让开发者在前端通过代码的形式来处理请求的缓存。
这就给开发者很大的操作空间了。你可以选择直接拦截,返回缓存。也可以先看缓存里有没有,有就返回缓存,没有就放行(进入网络线程发送真实请求)。你甚至可以用这个东西实现一个后端服务,用户在离线时做的增删改查会被缓存下来,等有网了在同步到服务器。
想更深入了解 service worker 的话,可以去读一下 这篇文章。
不过一般情况下我们不会直接去用 service worker 的 api 写代码,谷歌提供了一个开源 sdk,叫做 workbox,可以帮我们快速的开发 sw.js。
简单介绍 workbox
有的小伙伴会想,刚才听你说的 service worker 不是很简单么,那这个 workbox 还有什么用?
原因就是我们要解决的问题是比较复杂且重复的,比如一个服务,里边有三个请求只能用缓存,还有十个请求要先缓存再请求。还有五组路由需要根据正则匹配决定怎么请求...
非常烦人的兄弟,所以谷歌根据这些常见的场景和方案进行了一些封装,做了一个上层的 "封装",这些封装最终沉淀成一个 sdk,就是这里的 workbox。我们可以 npm 安装这些 sdk,然后就可以愉快的开发了。
这里就简单讲一下这个 sdk 里都有啥东西,方便大家后续理解:
- workbox-routing:怎么匹配请求,比如什么样的请求要走策略A,什么样的请求要走方案B。
- workbox-precaching:用于预先缓存一些内容。
- workbox-strategies:缓存策略,有很多种,用于指定请求拦截下来要干什么。
- workbox-window:一个桥接层,在 dom 页面里和 service worker 进行通信。
这里的 workbox-strategies 就是常见的处理方案,比如接口是只能用缓存(Cache only)还是完全不能用缓存(Network only)、是先看缓存,没缓存再请求(Cache first),还是先请求,没网了再走缓存(Network first)。
这里我就不照着文档念了,感兴趣的可以读一下官方的 什么是 workbox、缓存策略原理 和 运行时缓存。
注意哦,读一下了解概念就行了,我们不会用里边的 api 写代码的。因为在 workbox 之上,还有一个工具可以帮助我们直接生成出来想要的代码,这个工具的名字叫做 workbox build。
简单介绍 workbox build
顾名思义,workbox build 实际上就是来 bulid 一个 sw.js 调用脚本的。那么为啥会有这么一个玩意?我直接用 workbox 写 sw.js 脚本不行么?
原因在于,PWA 应用有个预缓存机制,这个机制会在 PWA 应用启动的时候把网站的静态资源都后台静默下载下来,这样就能实现桌面端应用的"秒开"效果。(上面 workbox 的 workbox-precaching 功能就是用来干这个)。
现在问题来了,要预缓存的话,那你得有个清单来 workbox 哪些要缓存对吧。但是现在现代化的前端项目里,静态资源都是构建出来的,还有各种分包、懒加载策略... 每次打完包面对几十上百个名字里带随机 hash 的 js、css,你总不能让我每次构建完都手动抄一份清单复制到 sw.js 里吧。
这不仅麻烦,而且危险,一旦有疏忽写错了,那网站就可能因为缓存问题而裂开了,谷歌也明确提示最好不要直接手写预缓存相关的逻辑:

于是,workbox build 应运而生,这是一个 nodejs 命令行工具,可以用 npm 安装,也是谷歌开源出来的。它接受一段配置,然后根据这段配置生成出具体的 workbox 调用脚本。
workbox build 支持两种生成模式:generateSW 和 injectManifest。
一般来说 generateSW 模式用的比较多,这个模式会完全生成出 sw 脚本,比较简单方便。而当你已经有一个 sw 脚本,需要把动态的内容注入到这个脚本里时,才会用到 injectManifest。
如果你对他的具体配置感兴趣的话,可以参考 workbox-build docs。
实际上,谷歌还提供了一个直接命令行生成 sw 脚本的工具,叫做 workbox-cli,不过我们不会用,这里就不介绍了。
你觉得到这里结束了么?nonono,在 workbox build 之上还有一层编译器插件的封装。这些插件才是我们实际会用到的东西。
简单介绍 pwa 编译器插件
这里的编译器指的是 webpack vite 这些工具。相关的插件包括 webpack-pwa-manifest、workbox-webpack-plugin、vite-plugin-pwa。
这些工具一般都会暴露 workbox build 的配置项让你来自定义,同时自己完成一些把 sw 脚本注入到 index.html 中、自动识别打包出的静态资源并生成预加载清单的功能。你还记得第一篇文章里提到的 manifest.webmanifest 文件么,这些编译器插件也会自动生成 manifest.webmanifest 并注入到 index.html 中。
可以说,workbox build 是用来生成 sw 脚本的工具,而这些 webpack 和 vite 插件则是用来生成 pwa 应用的工具。他们是一个包含的关系。
在实际的项目中,我们正是通过配置这些插件的形式来给项目添加 PWA 的能力,而这也就是我们下一篇文章里要做的事情。
小结
本篇大致讲了下 service worker 相关的生态构成,从原始的 service worker api,到封装好的 workbox,再到自动生成的 workbox build,最后诞生了我们会接触到的 pwa 插件。
其实站在过来人的角度回头看,这些一层层的设计和封装都是有对应的意义的。但是作为刚接触 PWA 的新人来说,很容易迷失在这一大堆的概念里寸步难行。这也是我在实践前先写了这篇文章的初衷。
如果大家遇到了一些名词不太清楚是干什么的话,可以在这篇文章的评论区一起交流一下。