不会让你惊讶的 Service workers:离线优先 PWAs 的确定性缓存

发布: (2026年2月4日 GMT+8 01:18)
8 分钟阅读
原文: Dev.to

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 烦到,通常是以下几种情况(如果你还没遇到:恭喜——你的日子快到了)。

  1. 陈旧的 HTML 在部署后导致应用崩溃

    • 浏览器持续提供旧的 index.html
    • 模块图发生变化 → 你会收到块 404 错误或出现空白屏幕。
  2. 基础路径与实际不符

    • 在根路径 / 下工作正常,但在 GitHub Pages 的 /pain-tracker/ 下会出错。
    • 你注册了错误的作用域,导致一切行为都不符合预期。
  3. 缓存变成了意外的数据保留

    • 你并未打算缓存响应。
    • 你因为方便而缓存了“所有内容”。
    • 用户特定的负载会被缓存(对敏感应用来说是个问题)。
  4. 更新令人困惑

    • 新的 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,……

缓存流程

  1. 如果请求已在缓存中 → 返回缓存。
  2. 否则 → 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

关键行为

  1. 根据 VITE_BASE(如果设置)或 Vite 的 BASE_URL 计算 baseUrl,当 Vite 提供相对基路径时回退到 location.pathname
  2. 使用 scope: baseUrl 注册 ${baseUrl}sw.js
  3. updateViaCache 设置为 'none' 以强制检查更新。
  4. 添加 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 负载。
  • 明确说明在 “锁定” 与 “解锁” 状态下可能发生的行为。

快速测试检查清单

  1. 检查 Service Worker 脚本
  2. 在 DevTools 中
    • Application → Service Workers – 确认作用域 + 脚本 URL。
    • Application → Cache Storage – 查找 pain-tracker-static-v…
  3. 测试离线行为
    • Network → Offline
    • 刷新一个路由。
    • 你应该看到离线回退,而不是破损的外壳。

接下来是什么?

  • 第 4 部分 将涵盖防御性解析(Zod + 模式优先输入)以及如何防止“离线‑优先”变成“悄悄接受垃圾”。

前一篇: 第 2 部分 — 三层存储
下一篇: 第 4 部分 — Zod + 防御性解析

赞助项目(主要)

https://paintracker.ca/sponsor

/paintracker.ca/sponsor

给仓库加星(次要):
https://github.com/CrisisCore-Systems/pain-tracker

从头阅读完整系列:
link

Back to Blog

相关文章

阅读更多 »

你不需要 CSS 预处理器

CSS 预处理器:它们仍然值得使用吗?曾经有段时间,CSS 预处理器看起来像是解决所有 CSS 问题的神奇灵药。只需要…