Initial TrueGrowth source import

This commit is contained in:
2026-07-07 09:36:36 +08:00
commit 3b6781d695
2283 changed files with 691996 additions and 0 deletions

View File

@@ -0,0 +1,158 @@
# 智能 CDN 优先加载经验总结
## 背景
之前静态资源采用 CDN 优先策略,发版期间会出现服务器已经切到新版本,但 npm CDN 对应版本资源尚未完全可用的窗口,导致部分 JS/CSS 返回 404 或错误页,页面加载失败。后续改成源站优先后,正确性恢复,但用户感知到首屏变慢。
这次优化的目标不是“改回 CDN 绝对优先”,而是在保证发布窗口可用性的前提下,让绝大多数正常流量重新走 CDN。
## 这次采取的方案
### 1. 区分入口链路和版本化静态资源
- `index.html`、导航请求、`version.json``manifest.json``sw.js``precache-manifest.json` 继续同源优先。
- `js/css/font/image/ico/版本化 json` 改为 `Cache First -> 智能 CDN 优先 -> 源站快速兜底`
这样可以避免入口文件与静态资源来自不同版本,降低发版窗口错配风险。
### 2. 让主线程 CDN 探测真正参与 SW 决策
- 保留 `cdn-config.js` 的非阻塞测速逻辑。
- 主线程在注册/接管 Service Worker 后,把当前测速结果通过 `SW_CDN_SET_PREFERENCE` 同步给 SW。
- SW 将偏好持久化为一份很小的记录,刷新页面时即使主线程尚未完成测速,也能优先复用上次已知的 CDN 偏好。
经验:测速结果如果不进入真正的请求决策链路,只会增加复杂度,不会带来收益。
### 3. CDN 失败要“短时熔断”,避免单页重复踩坑
- CDN 对当前版本资源返回超时、404/5xx、HTML 错页、异常内容类型时,立即记为失败。
- 连续失败达到阈值后,短时间内不再优先尝试该 CDN。
- 熔断窗口过去后允许恢复探测。
经验:如果没有熔断,同一页面的几十个静态资源会重复打向已失效 CDN既慢又放大错误。
### 4. 已校验的 CDN 缓存应该允许复用
之前逻辑把“非 server 来源的静态缓存”统一视为 suspicious导致 CDN 成功拉下来的资源下次仍然可能被删掉并重新回源。
这次改为给静态缓存统一打元数据:
- `x-sw-source`
- `x-sw-revision`
- `x-sw-app-version`
只清理以下情况:
- 缺少必要元数据
- 缓存版本与当前 `APP_VERSION` 不一致
- 静态资源缓存实际返回 HTML 错页
经验CDN 缓存是否可复用,判断依据应该是“是否已被校验 + 是否与当前版本匹配”,而不是“是否来自源站”。
## 这次实现后得到的结论
### 推荐策略
- 入口链路同源优先
- 版本化静态资源智能 CDN 优先
- CDN 失败快速回源
- CDN 偏好持久化 + 健康状态熔断
- 静态缓存按版本和元数据复用
- 发布时先等主 CDN 关键资源 ready再切服务器 HTML
- 首屏必需内容不做高风险按需加载
- 构建切分优先保守,不为追求更细粒度 chunk 牺牲稳定性
### 不推荐策略
- CDN 绝对优先:发版窗口风险高
- 源站绝对优先:正常流量浪费 CDN
- 所有资源做 CDN/源站双发竞速:虽然更快,但会放大重复请求,增加服务器和 CDN 压力
- 为了压首屏包体,强行加 `manualChunks`,但没先理清循环依赖边界
## 这轮新增经验2026-04-22
### 1. 多 CDN 不是越多越稳,关键在“顺序 + 熔断”
这轮最终保留了双 CDN
- 主 CDN`jsdelivr`
- 备用 CDN`unpkg`
- 最终兜底:源站
经验:
- 多 CDN 只有在“主次明确”时才会更稳
- 如果所有 CDN 都被等权重频繁重试,失败面只会被放大
- 备用 CDN 应该是兜底,不应该在已知异常期间持续抢占请求
### 2. 连续失败要自动熔断更久,不能固定 1 分钟
之前的短时熔断只能解决“瞬时失败”,但对持续异常的备用 CDN 不够。
这轮改成按 CDN 和连续失败次数动态拉长冷却时间:
- `jsdelivr`:基础 60 秒,最长 5 分钟
- `unpkg`:基础 10 分钟,最长 1 小时
- 连续失败越多,熔断窗口越长
经验:
- 主 CDN 可以恢复得更积极
- 备用 CDN 一旦连续失败,应该更保守,避免每次首屏都被它拖慢
- 熔断策略要区分“主链路”和“备用链路”,不能一个常量打天下
### 3. 熔断必须可观测,否则线上只能猜
这轮补了两类观测信息:
- SW 日志直接打印 `failCount``reason``cooldown`
- 状态报告额外返回 `cooldownMs``cooldownUntil``remainingCooldownMs`
经验:
- 只知道“某 CDN 挂了”不够
- 还要知道“失败了几次”“为什么失败”“还要熔断多久”
- 没有剩余熔断时间,排查时很难区分是“还在熔断”还是“恢复探测又失败了”
### 4. 首屏稳定性优先于激进切分
这轮一开始尝试过继续用 `rollupOptions.manualChunks` 优化加载,但项目里存在一些动态导入和静态导入交织的边界,进一步切分后容易触发循环 chunk 风险,表现为运行时未定义或首屏卡死。
最终采取的保守结论:
- 回退激进 `manualChunks`
- 保留已有安全切分
- 首屏所需内容保证直接可加载,不依赖高风险按需拆分
经验:
- “首屏更小”不等于“首屏更稳”
- 当代码边界本身不干净时,继续切 chunk 是放大风险,不是优化
- 这类问题应该先治理依赖边界,再谈更激进的包体优化
### 5. 发布正确性的门要设在“CDN ready”之前
这轮保留了发布 gate
- npm 发版完成后
- 先等待主 CDN 关键资源可访问
- 再部署服务器侧 HTML
经验:
- 只要 HTML 比 CDN 资源先切到新版本,用户就可能卡在半加载状态
- 发版系统不能只关心“npm publish 成功”,还要关心“主 CDN 已真正可用”
- 这一步比事后依赖 SW 回退更重要,因为它直接缩小了错误窗口
## 后续可继续观察的指标
- 发版窗口内静态资源 404/HTML 错页是否明显下降
- 首屏静态资源命中 CDN 的比例是否提升
- 首次加载与二次刷新时的资源来源分布
- 某个 CDN 短时异常时,是否能稳定切到次选 CDN 或源站
## 一句话总结
CDN 优先不是问题,问题在于“没有版本边界、没有熔断、没有缓存复用规则”。把这三件事补齐,才能既快又稳。
补充一句:如果代码边界还不稳定,就不要拿 `manualChunks` 去赌首屏,先保正确性,再做更细粒度优化。