service worker预缓存本身不实现毫秒级异步加载,而是通过安装阶段预存微模块资源,配合dynamic import与cache-first策略,在后续请求中精准命中cache storage,从而实现毫秒响应。

Service Worker 的预缓存(Pre-caching)本身不直接实现“毫秒级异步加载”,它解决的是资源可离线访问、首次请求免网络、二次加载零延迟的问题。真正让海量微模块“毫秒级异步加载”的,是预缓存 + 模块按需加载 + Cache API 精准命中三者协同的结果——预缓存提前把模块资源稳稳存进 Cache Storage,后续 import() 或 fetch 时直接从本地缓存读取,绕过网络往返,自然接近毫秒响应。
明确预缓存的定位与边界
Pre-caching 发生在 Service Worker 安装阶段(install event),它把构建时已知的静态资源列表(如 JS 模块、CSS、图标)一次性写入 Cache Storage。它的核心价值不是“加速首次加载”,而是确保这些资源在用户第一次访问时就已就位,且后续所有请求都能被拦截并秒返回。它不适用于运行时动态生成的模块路径,也不处理未预缓存的资源——那些得靠 runtime caching 补充。
- 预缓存清单必须在构建时确定(如通过 webpack 插件或 vite-plugin-pwa 生成 precache-manifest.json)
- 每个微模块(如
./modules/chart.js、./modules/table.js)应作为独立 chunk 输出,并被纳入预缓存列表 - 预缓存失败会导致 Service Worker 安装失败,所以需用
event.waitUntil()包裹缓存逻辑并做好错误兜底
微模块异步加载:用 dynamic import + Cache-first 策略
用户触发模块加载(比如点击按钮打开图表)时,应使用 import() 动态导入语法。此时 Service Worker 的 fetch 事件会捕获该请求,并优先查 Cache Storage;命中则立即 resolve,未命中再走网络——这就是毫秒响应的关键链路。
- 确保微模块 URL 路径与预缓存时完全一致(含 hash 版本号,避免缓存混淆)
- fetch 事件中对 JS/CSS/JSON 等模块类资源采用
cacheFirst策略:await caches.match(req) || fetch(req).then(r => caches.open(...).then(c => c.put(req, r.clone()))) - 避免在主线程中阻塞等待模块加载,始终用
async/await或.then()处理 Promise,保持 UI 流畅
支撑海量模块的工程化要点
当微模块数量达数百个时,单靠手动维护预缓存列表不可行。必须依赖构建工具自动化注入与版本管理:
- 使用 Vite 的
vite-plugin-pwa或 Webpack 的workbox-webpack-plugin,自动扫描import('./modules/*.js')或 chunk 文件,生成带 content-hash 的 precache 清单 - 为每个微模块输出独立文件名(如
chart.abc123.js),确保内容变更即 URL 变更,旧缓存自动失效,新缓存精准覆盖 - 在 Service Worker 中按资源类型分 cache name(如
'modules-v1'、'assets-v1'),便于单独清理或灰度更新 - 对非 JS 模块资源(如 JSON 配置、SVG 图标),同样纳入预缓存,并在 fetch 中统一匹配
规避常见陷阱
毫秒级体验极易被几个细节破坏:
- 不要在预缓存中包含 HTML 页面(除非是 shell),否则会干扰导航请求的正常处理
- 避免在 install 阶段缓存过大的资源(如整站图片),导致安装超时;微模块 JS 通常几 KB~几十 KB,适合预缓存
- 确保所有微模块入口路径都经过 base 路径标准化(如
new URL('./chart.js', import.meta.url)),防止 fetch 事件中 URL 匹配失败 - 开发阶段禁用浏览器强缓存(DevTools → Network → Disable cache),否则可能读到旧版 SW 或旧缓存,掩盖问题











