serviceWorker

React-H5,通过 webpack 优雅使用 WorkBox

原文

为了提高 React 应用的启动速度、离线访问能力, 做到页面能离线启动、service worker 能在后台默默更新本地缓存的页面、数据的版本,并且做到监控版本更新能力的靠谱性。

开胃菜

  • PWA (Progressive Web Apps)
    • 借助 Service Worker 缓存网站的静态资源,甚至是网络请求,使网站在离线时也能访问。并且我们能够为网站指定一个图标添加在手机桌面,实现点击桌面图标即可访问网站。
  • serviceWorker
    • 服务器与浏览器之间的中间人,如果网站中注册了Service Worker那么它可以拦截当前网站所有的请求,进行判断(需要编写相应的判断程序),如果需要向服务器发起请求的就转给服务器,如果可以直接使用缓存的就直接返回缓存不再转给服务器,我们在Service Worker 中可以做拦截客户端的请求、向客户端发送消息、向服务器发起请求等先关操作,其中最重要且广泛的的作用就是离线资源缓存。
    • serviceWorker 仅支持本地(localhost/127.0.0.x)的 http 协议和带有安全证书的 https 协议 (本地缓存)
    • Service Worker 是浏览器在后台独立于网页运行的脚本。是它让 PWA 拥有极快的访问速度和离线运行能力。
      • 特性
        • 基于 web worker(JavaScript 主线程的独立线程,如果执行消耗大量资源的操作也不会堵塞主线程)
        • 在 web worker 的基础上增加了离线缓存的能力
        • 本质上充当 Web 应用程序(服务器)与浏览器之间的代理服务器
        • 创建有效的离线体验(将一些不常更新的内容缓存在浏览器,提高访问体验)
        • 由事件驱动的,具有生命周期
        • 可以访问 cache 和 indexDB
        • 支持消息推送
        • 并且可以让开发者自己控制管理缓存的内容以及版本
        • 可以通过 postMessage 接口把数据传递给其他 JS 文件
        • 更多无限可能
  • workBox
    • 其实可以把 Workbox 理解为 Google 官方的 PWA 框架,也可以理解为对 serviceWorker 的封装。
  • workbox-webpack-plugin
    • webpack 通过配置插件(缓存策略), 简易接入 workBox。

正菜: workbox-webpack-plugin

官方文档: developers.google.com/web/tools/w…

  • 安装: npm install workbox-webpack-plugin -D
  • 配置: webpack 插件中使用~

webpack 配置中引入插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const { GenerateSW } = require("workbox-webpack-plugin");
exports.override = (webpackConfig, options) => {
webpackConfig.plugins.push(
new GenerateSW({
swDest: "workboxServiceWorker.js", // 注意点1: 必须写这个名字,

// 因为插件默认会生成 service-worker.js 这个文件,然后不知道WHO又生成了一次service-worker.js这个文件(内容不是workbox预期), 所以webpack生成的workbox的脚本就这样被替换了! 导致插件配置好的文件其实没被写出!!!

// 当我们每次访问网站时都会去下载这个文件,当发现文件不一致时,就会安装这个新 Service Worker ,安装成功后,它将进入等待阶段。
importWorkboxFrom: "disabled", // 可填`cdn`,`local`,`disabled`, 区别下面整理
importScripts: "https://fds.api.x.net/workbox-cdn/workbox-sw.js", // 我从自己的cdn引入了workbox,这样就不用每个项目都上传
// 这三个都写true
skipWaiting: true, // 新 Service Worker 安装成功后需要进入等待阶段,skipWaiting: true 将使其跳过等待,安装成功后立即接管网站。
clientsClaim: true, // 立即接管
offlineGoogleAnalytics: true, // 离线也记录ga数据, 有网了再上报的意思。
cleanupOutdatedCaches: true, // 尝试删除老版本缓存

// 缓存规则, 具体下面记录, 更详细的请查阅文档。 目前只缓存api
runtimeCaching: [
{
urlPattern: /^https:\/\/easy-mock\.com\//,
handler: "NetworkFirst",
options: {
cacheName: "cached-api",
networkTimeoutSeconds: 2,
expiration: {
maxEntries: 50,
maxAgeSeconds: 1 * 24 * 60 * 60, // 1 day
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
],
})
);

return webpackConfig;
};

在 react 入口 js 的代码里注册代码, 你也可以选择在public下的 index.html 模板里的 script 标签里写(别说我教你们的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
// 敲黑板, 这里的/workbox/workboxServiceWorker.js需要根据实际情况变化

// 注意: 这里有个坑 workboxServiceWorker 会被缓存, 解决方案在下面的坑点介绍
navigator.serviceWorker
.register("/workbox/workboxServiceWorker.js")
.then((registration) => {
console.log("SW registered: ", registration);
})
.catch((registrationError) => {
console.log("SW registration failed: ", registrationError);
});
});
}

坑点:

  1. swDest: 'workboxServiceWorker.js'

官方文档中, 这个选项是可选填, 默认值为: service-worker.js。我遇到的问题是, 如果不写这个重新写出一个文件, 不知道是哪个”B”, 也写出了一个叫service-worker.js的文件, workBox 的先写出来了, 然后又被一个同名文件写出覆盖了! 然后你自认为接入了workbox, 实际上你不知道你接入的是啥

  1. 浏览器兼容坑点:

测试了 PC 端: 谷歌, 火狐, QQ 浏览器, UC 浏览器 || 移动端: QQ 浏览器, miui 浏览器

  • 谷歌, 火狐正常, 能更新最新版本缓存以及更新页面 (当发现代码有变化的时候)
  • QQ 浏览器 || UC 浏览器: 会把最主要的workboxServiceWorke.js这个文件居然自动硬盘缓存了!!! 导致不会去服务器对比网站是否更新。

解决方案

方法一:

1
`/workbox/workboxServiceWorker.js?${Date.now()}`; // 在workboxServiceWorker.js 后加上时间戳, 禁止被缓存!!!

方法二: FDS 上配置 workboxServiceWorker.js 的响应头, 禁止缓存

3.. `importWorkboxFrom` 和 `importScripts`

importWorkboxFrom 可以选填三个值: cdn,local,disabled

  • cdn: 引入 google 的官方 cdn, 后果是国内用户打开网站, 一脸懵逼的被墙 (所以肯定不能用这个默认值!!!)
  • local: workbox 人性化的在本地写出了 workbox 的代码, 然后和项目代码一起上传部署就 ok, 但每个项目都要这样, 就很麻烦。
  • disabled: 傲娇的不从谷歌引入, 也不导出的本地。但如果你不配置: importScripts 的引入地址, 那将一脸懵逼。

所以我最终的方案:

1
2
importWorkboxFrom: 'disabled',
importScripts: 'https://fds.api.x.net/workbox-cdn/workbox-sw.js', // 把local模式导出的文件, 先部署获取到cdn链接, 在写死就ok
  1. runtimeCaching: 具体的运行时缓存策略通过这个选项配置, 具体的需要实战或者根据自己的业务调整, 注意下面第四点, runtimeCaching 中无需放置代码页面的缓存
  2. 缓存分为precacherunningCache, 打包之后的代码, 会自己加入到 precache 中, 所以无需再运行时配置缓存资源, 比如:

具体预缓存的文件可以看 precache-manifest.xxxxxx.js

在文档中搜索precache, 有更多可以配置的, 比如: include/exclude || chunks/excludeChunks

1
2
3
4
5
6
7
8
9
10
11
12
13
// 没必要!!!
runtimeCaching: [
{
// cdn资源,这个原本想缓存的是代码,实则已经被预缓存了
urlPattern: new RegExp("^https://cdn.net"),
handler: "staleWhileRevalidate",
options: {
cacheableResponse: {
statuses: [200],
},
},
},
];

解析配置

主要解析 runtimeCaching 中的缓存策略 (只在 demo 中测试, 没接正式项目, 不知道有没有更多的坑点)

  • Stale While Revalidate (主要)
    这种策略的意思是当请求的路由有对应的 Cache 缓存结果就直接返回,在返回 Cache 缓存结果的同时会在后台发起网络请求拿到请求结果并更新 Cache 缓存,如果本来就没有 Cache 缓存的话,直接就发起网络请求并返回结果,这对用户来说是一种非常安全的策略,能保证用户最快速的拿到请求的结果,但是也有一定的缺点,就是还是会有网络请求占用了用户的网络带宽。
    用来做 CSS,JS,PNG 等资源的策略, 觉得蛮好。
  • Network First (次主要)
    这种策略就是当请求路由是被匹配的,就采用网络优先的策略,也就是优先尝试拿到网络请求的返回结果,如果拿到网络请求的结果,就将结果返回给客户端并且写入 Cache 缓存,如果网络请求失败,那就读取 Cache 中的数据,这种策略一般适用于返回结果不太固定或对实时性有要求的请求,为网络请求失败进行兜底。
    用来做 API 接口的,也许就是这样。
  • Cache First
    这个策略的意思就是当匹配到请求之后直接从 Cache 缓存中取得结果,如果 Cache 缓存中没有结果,那就会发起网络请求,拿到网络请求结果并将结果更新至 Cache 缓存,并将结果返回给客户端。这种策略比较适合结果不怎么变动且对实时性要求不高的请求。
  • Network Only
    比较直接的策略,直接强制使用正常的网络请求,并将结果返回给客户端,这种策略比较适合对实时性要求非常高的请求。
  • Cache Only
    这个策略也比较直接,直接使用 Cache 缓存的结果,并将结果返回给客户端,这种策略比较适合一上线就不会变的静态资源请求。( - - 你敢确定不会变吗…)

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!