不会让你惊讶的 Service workers:离线优先 PWAs 的确定性缓存
Source: Dev.to
Series:
从这里开始 · Part 1 · Part 2 · Part 3 · Part 4 · Part 5 · Part 6 · Part 7 · Part 8 · Part 9 · Part 10
这篇文章是 Dev.to 系列的第 3 部分,基于开源 Pain Tracker 仓库。
非医学建议。
非合规声明。
它涉及确定性的行为和真实的边界。
如果你希望以隐私为先、离线的健康技术能够在没有监视资助的情况下存在,请 赞助构建 → https://paintracker.ca/sponsor。
如果你还没有阅读第 2 部分:
- 第 2 部分: 三个存储层(状态缓存 vs 离线数据库 vs 加密金库)
什么“令人惊讶”的 Service Workers 会做
如果你曾被 Service Workers 烦到,通常是以下几种情况(如果你还没遇到:恭喜——你的日子快到了)。
-
陈旧的 HTML 在部署后导致应用崩溃
- 浏览器持续提供旧的
index.html。 - 模块图发生变化 → 你会收到块 404 错误或出现空白屏幕。
- 浏览器持续提供旧的
-
基础路径与实际不符
- 在根路径
/下工作正常,但在 GitHub Pages 的/pain-tracker/下会出错。 - 你注册了错误的作用域,导致一切行为都不符合预期。
- 在根路径
-
缓存变成了意外的数据保留
- 你并未打算缓存响应。
- 你因为方便而缓存了“所有内容”。
- 用户特定的负载会被缓存(对敏感应用来说是个问题)。
-
更新令人困惑
- 新的 worker 安装成功但未激活。
- 用户没有刷新页面。
- 你无法判断当前运行的是哪个版本。
主题: 浏览器正好按照你的指示行事……只是你的指示不够严谨。
Source: …
Pain Tracker 的 Service Worker 哲学
- 导航采用网络优先(避免陈旧的 HTML)
- 仅缓存静态资源(脚本、样式、图片、字体)
- 使用带版本号的缓存并在激活时清理
- 小型预缓存 用于
offline.html和 manifest
没有“离线魔法”,也不对任意 API 响应进行运行时缓存,更不会把 Service Worker 当作数据层。
两个 Service Worker 脚本
| 范围 | URL |
|---|---|
| 根目录(普通部署) | |
GitHub Pages 基础路径(/pain-tracker/) |
两个脚本中最关键的一行概念上是:导航采用网络优先。
在 public/sw.js 中,通过 request.mode === 'navigate' 或 Accept: text/html 来检测导航请求。随后执行:
try {
return fetch(request);
} catch (_) {
return caches.match('/offline.html');
}
仅此而已。
为什么这很重要
如果对导航使用缓存优先策略,最终会发布一个新构建,其中的 HTML 指向新的块文件名。缓存的旧 HTML 仍然指向旧文件名 → 产生块 404,应用会出现“随机崩溃”。
对于健康相关的 PWA,这种故障比显示一条干净的离线提示更糟糕。
什么会被缓存
Pain Tracker 只缓存 同源 GET 请求,且这些请求看起来像是静态资源:
- 允许的路径前缀:
/assets/、/icons/、/logos/、/screenshots/(以及 GitHub Pages 构建对应的/pain-tracker/前缀)。 - 保守的扩展名白名单:
.js、.css、.png、.svg、.woff2,……
缓存流程
- 如果请求已在缓存中 → 返回缓存。
- 否则 →
fetch请求,缓存成功的200响应,然后返回。
产生的离线策略
- 首次访问后,外壳加载迅速。
- 部署不会被缓存的 HTML 卡住。
- 敏感的运行时响应不会被意外缓存。
版本管理与清理
两个 Worker 都有版本字符串,并以此构建缓存名称:
const CACHE_NAME = `pain-tracker-static-v${VERSION}`;
在激活时,SW 会删除所有以 pain-tracker- 为前缀的旧缓存,保持缓存干净一致:
- 提升 Service Worker 版本 → 旧缓存被移除。
- 无需猜测、无“可能会更新”。要么更新,要么不更新。
GitHub‑Pages‑风格的 Worker(public/pain-tracker/sw.js)
变化如下:
- 预缓存的 manifest URL 变为
/pain-tracker/manifest.json。 - 静态前缀包括
/pain-tracker/assets/。 - 离线回退路径包括
/pain-tracker/。
这种重复避免了“为什么在一个环境下离线而另一个环境不离线”的数小时排查。
在应用中注册 Service Worker
注册代码位于 src/utils/pwa-utils.ts:
// https://github.com/CrisisCore-Systems/pain-tracker/blob/main/src/utils/pwa-utils.ts
关键行为
- 根据
VITE_BASE(如果设置)或 Vite 的BASE_URL计算baseUrl,当 Vite 提供相对基路径时回退到location.pathname。 - 使用
scope: baseUrl注册${baseUrl}sw.js。 - 将
updateViaCache设置为'none'以强制检查更新。 - 添加
updatefound监听器,以便在有新内容时通知用户。
Service Worker 在激活时会发送 SW_READY 消息,并在收到 PING 时回复 PONG。PWA 管理器监听这些消息并将 window.__pwa_sw_ready = true 设置为 true。
实用技巧
- 避免了“Service Worker 是否已就绪?”的 flaky 测试。
- 为你在 DevTools 中提供一个简单的调试信号。
它 不 做的事(以及原因)
- 不 缓存任意的 fetch 请求。
- 不 缓存 API 响应。
- 不 缓存页面导航。
- 不 实现“将健康数据同步到云端”的系统。
这些并非缺失的功能,而是有意设定的边界。
如果以后添加更多 Service Worker 功能(后台同步、离线处理等),请将它们视为 新的信任边界:
- 决定哪些数据可以存入缓存。
- 避免在 Cache Storage 中存储 Class A 负载。
- 明确说明在 “锁定” 与 “解锁” 状态下可能发生的行为。
快速测试检查清单
- 检查 Service Worker 脚本
- 在 DevTools 中
- Application → Service Workers – 确认作用域 + 脚本 URL。
- Application → Cache Storage – 查找
pain-tracker-static-v…。
- 测试离线行为
- Network → Offline。
- 刷新一个路由。
- 你应该看到离线回退,而不是破损的外壳。
接下来是什么?
- 第 4 部分 将涵盖防御性解析(Zod + 模式优先输入)以及如何防止“离线‑优先”变成“悄悄接受垃圾”。
前一篇: 第 2 部分 — 三层存储
下一篇: 第 4 部分 — Zod + 防御性解析
赞助项目(主要)
https://paintracker.ca/sponsor
/paintracker.ca/sponsor
给仓库加星(次要):
https://github.com/CrisisCore-Systems/pain-tracker
从头阅读完整系列:
link