绝大多数情况下,index.html 不该放进离线包,因其作为页面入口和版本锚点一旦锁死,会导致紧急回滚滞后;应让html走网络请求以实现即时更新,而js/css等静态资源才放入离线包并采用content-hash命名确保匹配。

离线包要不要包含 index.html?
绝大多数情况下,index.html 不该放进离线包——它得走网络优先策略。原因很实在:HTML 是页面逻辑和资源引用的“总控开关”,一旦打包进离线包,就锁死了版本更新节奏。比如你发了个含严重 bug 的 index.html,用户下次打开 App 仍会加载旧版,直到下一次离线包全量更新(可能间隔几小时甚至几天),紧急回滚完全失效。
更麻烦的是资源错配:HTML 中引用的 JS/CSS 文件名带 hash(如 main.a1b2c3.js),而离线包里缓存的是旧 hash 版本,结果 HTML 拉新 hash,离线包只提供旧文件,404 直接白屏。
正确做法是:index.html 单独请求、不缓存或短缓存(Cache-Control: max-age=60),其他静态资源(js/、css/、assets/)才放进离线包。这样既能保证 HTML 实时更新,又能复用本地缓存资源加速加载。
怎么打包静态资源成离线包?
别手写 zip,也别直接拖整个 dist 目录发出去。关键在两点:路径收敛 + 清单可控。
- 所有资源路径必须是相对路径,且以
./或../开头;绝对路径(如/js/main.js)在file://下必然 404 - 用构建工具生成资源清单,比如 Webpack 的
webpack-manifest-plugin输出manifest.json,里面明确列出每个文件的路径和 hash 值 - 打包脚本应过滤掉无用目录:
node_modules/、.git/、src/、package-lock.json全部剔除,只保留运行必需的index.html(如果真要放)、js/、css/、assets/ - 最终产物建议用 tar.gz 而非 zip(macOS/Linux 更友好),并附带一个校验文件
checksum.txt(内容为sha256sum *结果)
离线包如何被 App 正确加载?
WebView 加载离线包不是简单把文件解压到某个路径就完事。核心在于拦截请求并映射路径。
典型错误是:App 把离线包解压到 /data/data/com.xxx/cache/offline_v1/,但 WebView 仍按默认规则去 file:///android_asset/ 找资源,结果全 404。
必须做两件事:
- 注册自定义
WebViewClient,重写shouldInterceptRequest,对匹配https?://yourdomain.com/的请求,改为读取本地文件路径(例如将https://a.com/js/main.js映射为file:///data/.../offline_v1/js/main.js) -
index.html里所有资源引用必须跟线上一致(即用域名前缀),否则拦截规则无法命中;不能写成./js/main.js,得写https://a.com/js/main.js,靠拦截器兜底转成本地路径 - 首次加载时,先检查本地是否存在对应版本的离线包;不存在则 fallback 到网络加载,并触发后台下载新包
Service Worker 能替代离线包吗?
不能。Service Worker 是浏览器端缓存机制,依赖 HTTPS 或 localhost,file:// 协议下直接不可用。而离线包是 App 主动管理的本地文件集合,不依赖协议,也不受浏览器限制。
两者定位不同:SW 适合纯 Web 场景(PWA),做 runtime 缓存和静默更新;离线包是 Hybrid 场景下的基础设施,负责首屏秒开、弱网兜底、资源预置。
容易踩的坑:
- 在 HTTP 环境或
file://下硬上 SW,注册直接失败,控制台报Failed to register a ServiceWorker - 用 Workbox 生成 SW 脚本时没配
skipWaiting()和clientsClaim(),导致新版本缓存不生效,用户始终看到旧资源 - 把
index.html加进 SW precache 列表,又没处理版本切换逻辑,结果新 HTML 加载了,但引用的旧 JS 还在 cache 里,功能错乱
真正需要关注的,是离线包的版本号、校验机制、增量更新能力——这些都得由 App 层实现,浏览器帮不上忙。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











