# 版本更新策略 ## 问题描述 在应用升级时,旧版本的 HTML 页面可能会尝试加载已被删除的旧版本 JS/CSS 文件,导致出现 MIME 类型错误: ``` Refused to apply style from '...' because its MIME type ('text/html') is not a supported stylesheet MIME type Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html" ``` 这是因为: 1. 服务器部署新版本后,旧的静态资源文件(如 `index-old.js`)被删除 2. 用户浏览器中的旧 HTML 仍然引用这些已删除的文件 3. 服务器返回 404 页面(HTML 内容),但浏览器期望的是 JS/CSS 文件 4. 导致 MIME 类型不匹配错误 ## 解决方案 ### 1. Service Worker 延迟清理策略 **修改位置**: `apps/web/public/sw.js` 在 Service Worker 的 `activate` 事件中: - **不立即删除旧版本缓存**:当检测到旧版本缓存时,记录但不立即删除 - **延迟 30 秒清理**:使用 `setTimeout` 延迟 30 秒后再清理旧缓存 - **保留图片缓存**:图片缓存使用固定名称 `drawnix-images`,不受版本影响 ```javascript // 延迟 30 秒后清理旧缓存,给所有客户端足够时间刷新 setTimeout(async () => { console.log('Cleaning up old version caches now...'); for (const cacheName of [...oldStaticCaches, ...oldAppCaches]) { await caches.delete(cacheName); } }, 30000); // 30秒延迟 ``` ### 2. 主应用平滑更新机制 **修改位置**: `apps/web/src/main.tsx` 添加了以下更新机制: #### 2.1 自动检测新版本 ```javascript registration.addEventListener('updatefound', () => { const newWorker = registration.installing; // 检测到新版本时的处理逻辑 }); ``` #### 2.2 智能激活策略 **开发模式**:立即激活新 Service Worker ```javascript if (isDevelopment) { newWorker.postMessage({ type: 'SKIP_WAITING' }); } ``` **生产模式**:延迟 5 秒激活,避免打断用户当前操作 ```javascript setTimeout(() => { newWorker.postMessage({ type: 'SKIP_WAITING' }); }, 5000); ``` #### 2.3 监听 Controller 变化 当新的 Service Worker 接管时,自动刷新页面: ```javascript navigator.serviceWorker.addEventListener('controllerchange', () => { setTimeout(() => { window.location.reload(); }, 1000); }); ``` #### 2.4 定期检查更新 每 5 分钟自动检查一次新版本: ```javascript setInterval(() => { registration.update(); }, 5 * 60 * 1000); ``` ## 更新流程 ### 完整更新时间线 ``` T+0s : 新版本部署到服务器 T+0s : 用户访问应用,Service Worker 检测到新版本 T+0s : 开始安装新的 Service Worker(后台进行) T+5s : 新 Service Worker 安装完成,延迟 5 秒后激活 T+10s : 新 Service Worker 激活并接管页面 T+11s : 页面自动刷新,加载新版本资源 T+41s : 旧版本缓存被清理(激活后 30 秒) ``` ### 关键时间点 1. **0-5秒**:新版本检测和安装,用户无感知 2. **5-10秒**:等待当前操作完成,准备切换 3. **10-11秒**:新版本接管,页面刷新 4. **11-41秒**:用户使用新版本,旧缓存仍然保留 5. **41秒后**:旧缓存被清理,完全切换到新版本 ## 优势 ### 1. 避免 MIME 类型错误 - 旧缓存在页面刷新前保留,确保旧 HTML 能正常加载资源 - 延迟清理给所有标签页足够时间完成更新 ### 2. 平滑的用户体验 - 不会突然打断用户操作 - 延迟激活策略让更新在后台静默完成 - 自动刷新确保用户使用最新版本 ### 3. 可靠的更新机制 - 定期检查更新,不依赖用户手动刷新 - Controller 变化监听确保更新生效 - 多层延迟策略保证更新过程稳定 ## 测试建议 ### 本地测试 1. 修改版本号:`apps/web/public/sw.js` 中的 `APP_VERSION` 2. 构建应用:`npm run build` 3. 启动服务器:`npm run serve` 或部署到测试环境 4. 打开应用,观察 Console 输出 5. 修改版本号,重新构建 6. 在已打开的应用中,观察更新流程 ### 生产测试 1. 部署新版本到生产环境 2. 在用户端打开 DevTools Console 3. 观察以下日志: ``` New Service Worker found, installing... New Service Worker installed, waiting to activate... Production mode: New version available, will reload after current operations complete Applying new version update... Service Worker controller changed Reloading page to use new Service Worker... ``` ### 验证清理时机 在 Console 中观察: ``` Found old version caches, will keep them temporarily: [...] Old caches will be cleaned up after clients are updated [30秒后] Cleaning up old version caches now... Deleted old cache: drawnix-static-v0.2.9 ``` ## 回滚策略 如果新版本出现问题,可以: 1. **快速回滚**:部署旧版本代码 2. **清除 Service Worker**: ```javascript navigator.serviceWorker.getRegistrations().then(registrations => { registrations.forEach(r => r.unregister()); }); ``` 3. **清除缓存**: ```javascript caches.keys().then(keys => { keys.forEach(key => caches.delete(key)); }); ``` ## 注意事项 1. **不要手动删除旧缓存**:让延迟清理机制自动处理 2. **监控更新日志**:在生产环境中监控 Service Worker 更新日志 3. **测试兼容性**:确保新旧版本 Service Worker 可以平滑过渡 4. **避免频繁更新**:给用户足够时间使用每个版本 ## 相关文件 - `apps/web/public/sw.js` - Service Worker 实现 - `apps/web/src/main.tsx` - Service Worker 注册和更新逻辑 - `apps/web/index.html` - 应用入口,包含版本号 - `apps/web/public/version.json` - 版本信息文件