HTML 本身无法构建离线缓存的 Web 组件,manifest 属性和 .appcache 文件在 Chrome 95+、Firefox 85+、Safari 16.4+ 中已彻底移除;唯一可行方案是 Service Worker 拦截请求并配合 CacheStorage 管理资源,且必须部署在 HTTPS 或 localhost 环境下。

直接说结论:HTML 本身无法构建离线缓存的 Web 组件,manifest 属性和 .appcache 文件在 Chrome 95+、Firefox 85+、Safari 16.4+ 中已彻底移除,写了也完全不生效。真正能落地的方案只有一条路——用 Service Worker 拦截请求并配合 CacheStorage 管理资源,且必须部署在 HTTPS(或 localhost)环境下。
Service Worker 注册为什么总失败
注册失败通常没报错,但 DevTools → Application → Service Workers 面板为空,说明根本没跑起来。常见卡点:
-
navigator.serviceWorker是undefined:当前页面不是 HTTPS(localhost除外),浏览器直接禁用 API -
sw.js返回 404 或 302:注册路径写成navigator.serviceWorker.register('/js/sw.js'),但实际文件在/sw.js;或 Nginx/Apache 把sw.js当作静态文件重定向了 -
sw.js响应头含Cache-Control: no-store:浏览器拒绝注册(常见于开发服务器默认配置) - 已有激活中的 SW:新版本注册后卡在
waiting状态,需在install事件中调用self.skipWaiting(),或手动点击 DevTools 里的 “Skip Waiting”
install 阶段 cache.addAll() 为什么一个资源都不缓存
cache.addAll() 是原子操作,列表中任意一项 fetch 失败,整个缓存就中止,SW 卡在 installing 状态,后续 fetch 事件也不会触发。典型原因:
- 路径写相对:比如
sw.js在/js/sw.js,却写./style.css→ 解析为/js/./style.css→ 404 - 路径带查询参数:
/app.js?v=2.1和/app.js?v=2.2被视为两个键,浪费空间且易失效;应统一用无参路径/app.js - HTML 依赖未全列:只写了
['/index.html'],但页面里有<script src="app.js">→ 断网时 HTML 加载成功,app.js404,JS 功能全挂 - 服务端返回
Cache-Control: no-store或 MIME 不匹配(如字体返回text/plain)→ 缓存直接拒绝
fetch 事件里怎么避免白屏又不乱缓存 API
单行写 caches.match(event.request).then(r => r || fetch(event.request)) 是危险操作:它把 POST 请求、带 credentials: 'include' 的登录接口也去缓存查,导致表单静默失败或登录态丢失。必须按 event.request.destination 分流:
立即学习“前端免费学习笔记(深入)”;
-
event.request.destination === 'document':主页面请求,必须命中缓存,否则断网白屏;建议 fallback 到预存的offline.html字符串 -
=== 'script'或=== 'style':可直接caches.match()返回,静态资源缓存优先没问题 -
=== 'json'或空字符串(如fetch('/api/user')):适合stale-while-revalidate,先返回缓存再 fetch 更新;注意响应头要设Cache-Control: max-age=0, stale-while-revalidate=60
Web 组件(Custom Element)如何适配离线缓存
Web 组件本身不改变缓存逻辑,但容易踩坑的地方是:组件内联 JS/CSS、动态 import、或 fetch 请求未被 SW 拦截。关键点:
- 组件模板中引用的
<link rel="stylesheet">或<script>必须出现在urlsToCache清单里,不能只缓存 HTML - 用
import('./my-component.js')动态加载的模块,路径必须是绝对根路径,且在install阶段显式加入缓存列表 - 组件内部发起的
fetch('/api/data'),若想离线可用,需在fetch事件中识别event.request.url.includes('/api/')并 fallback 到 IndexedDB,而不是依赖 CacheStorage - 不要在组件
connectedCallback里直接调用indexedDB.open():数据库打开是异步的,可能来不及初始化就渲染了;应在页面 load 后提前建好 DB 实例
最常被忽略的是:缓存策略必须和资源语义对齐——HTML 页面要“缓存优先 + 网络兜底”,静态资源要“缓存唯一”,API 响应则要“网络优先 + 缓存 fallback”,混用一种策略会导致离线时功能断裂或数据陈旧。这不是配置问题,而是设计决策。



















