Files
TrueGrowth/docs/VERSION_UPDATE_STRATEGY.md

5.6 KiB
Raw Permalink Blame History

版本更新策略

问题描述

在应用升级时,旧版本的 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,不受版本影响
// 延迟 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 自动检测新版本

registration.addEventListener('updatefound', () => {
  const newWorker = registration.installing;
  // 检测到新版本时的处理逻辑
});

2.2 智能激活策略

开发模式:立即激活新 Service Worker

if (isDevelopment) {
  newWorker.postMessage({ type: 'SKIP_WAITING' });
}

生产模式:延迟 5 秒激活,避免打断用户当前操作

setTimeout(() => {
  newWorker.postMessage({ type: 'SKIP_WAITING' });
}, 5000);

2.3 监听 Controller 变化

当新的 Service Worker 接管时,自动刷新页面:

navigator.serviceWorker.addEventListener('controllerchange', () => {
  setTimeout(() => {
    window.location.reload();
  }, 1000);
});

2.4 定期检查更新

每 5 分钟自动检查一次新版本:

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
    navigator.serviceWorker.getRegistrations().then(registrations => {
      registrations.forEach(r => r.unregister());
    });
    
  3. 清除缓存
    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 - 版本信息文件