9a289e1e1f
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 4s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 5s
CI/CD Pipeline / Check push changed paths (push) Successful in 16s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 12s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 13s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 15s
CI/CD Pipeline / Build Staging API Image (push) Successful in 21s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 22s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 23s
CI/CD Pipeline / CI Gate (pull_request) Successful in 9s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 2m7s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m23s
CI/CD Pipeline / Validate - Style (push) Successful in 2m50s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m37s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m6s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m36s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m31s
CI/CD Pipeline / Validate - Security (push) Successful in 5m21s
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 241h44m7s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 241h46m52s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 241h46m53s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 241h47m15s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 241h47m22s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 241h47m25s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 241h47m26s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 241h47m27s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 241h47m38s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 241h47m27s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 241h47m39s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 241h47m41s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 241h47m39s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 241h47m42s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 241h48m8s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 241h48m9s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 241h48m21s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 241h48m22s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 241h48m22s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Failing after 241h48m23s
CI/CD Pipeline / Validate - Security (pull_request) Failing after 241h48m24s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 241h48m24s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 241h48m25s
CI/CD Pipeline / PR Build Web Image (push) Failing after 241h48m25s
CI/CD Pipeline / Frontend Lint (push) Failing after 241h48m27s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 241h48m41s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 241h48m43s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 242h21m52s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 242h22m16s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 242h22m43s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 242h22m57s
CI/CD Pipeline / PR Build API Image (push) Failing after 242h23m2s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
/**
|
||
* 发版后旧标签页懒加载 chunk 失效(白屏)的识别与恢复工具。
|
||
*
|
||
* 背景:页面 React Router 的 lazy 动态 import,发版后旧 chunk 文件名被删除,
|
||
* 停留在旧标签页的用户点菜单时 import 404,抛出
|
||
* "Failed to fetch dynamically imported module"(Vite)/ ChunkLoadError,
|
||
* 不捕获就是整页白屏。
|
||
*/
|
||
|
||
/** sessionStorage 标记:最近已经为 chunk 失效自动刷新过一次(带时间戳,10min 有效) */
|
||
const RELOAD_FLAG_KEY = "chunk_error_reloaded_at"
|
||
/** 标记有效期:超过后允许再次自动刷新,避免用户手动正常刷新后标记永久残留 */
|
||
const RELOAD_FLAG_TTL_MS = 10 * 60 * 1000
|
||
|
||
/**
|
||
* Storage 在 Safari 无痕模式 / 禁用 Cookie 的浏览器 / 严格 iframe 策略下
|
||
* 访问可能抛异常;此处统一容错,拿不到存储就降级为"无标记",绝不能让
|
||
* 错误边界本身因读存储而崩溃。
|
||
*/
|
||
const safeStorage = {
|
||
getItem: (key: string): string | null => {
|
||
try {
|
||
return sessionStorage.getItem(key)
|
||
} catch {
|
||
return null
|
||
}
|
||
},
|
||
setItem: (key: string, value: string): void => {
|
||
try {
|
||
sessionStorage.setItem(key, value)
|
||
} catch {
|
||
/* 存储不可用时静默降级:仅丢失"已刷新"标记,不影响恢复动作 */
|
||
}
|
||
},
|
||
removeItem: (key: string): void => {
|
||
try {
|
||
sessionStorage.removeItem(key)
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
},
|
||
}
|
||
|
||
/** 判断错误是否为懒加载 chunk 加载失败(发版 404 / 网络中断 / 动态 import 失败) */
|
||
export const isChunkLoadError = (error: unknown): boolean => {
|
||
if (!error) return false
|
||
// Vite: Failed to fetch dynamically imported module: /assets/xxx-yyy.js
|
||
// Webpack: ChunkLoadError: Loading chunk xxx failed.
|
||
const needle =
|
||
error instanceof Error
|
||
? `${error.name} ${error.message}`
|
||
: typeof error === "string"
|
||
? error
|
||
: ""
|
||
return /failed to fetch dynamically imported module|chunkloaderror|loading chunk \d+ failed|error loading dynamically imported module|importing a module script failed/i.test(
|
||
needle,
|
||
)
|
||
}
|
||
|
||
/** 读取上次自动刷新时间戳;过期或不存在返回 null */
|
||
export const getChunkReloadedAt = (): number | null => {
|
||
const raw = safeStorage.getItem(RELOAD_FLAG_KEY)
|
||
if (!raw) return null
|
||
const ts = Number(raw)
|
||
if (!Number.isFinite(ts)) return null
|
||
if (Date.now() - ts > RELOAD_FLAG_TTL_MS) return null
|
||
return ts
|
||
}
|
||
|
||
/** 标记"已为 chunk 失效自动刷新过",然后刷新页面 */
|
||
export const reloadForChunkError = (): void => {
|
||
safeStorage.setItem(RELOAD_FLAG_KEY, String(Date.now()))
|
||
window.location.reload()
|
||
}
|
||
|
||
/**
|
||
* 硬恢复:清掉标记后回到首页(整页导航,不是当前 URL 刷新)。
|
||
* - chunk 失效兜底:回到首页会拉取最新 index.html,彻底脱离旧 chunk 引用
|
||
* - 非 chunk 的页面级崩溃:跳首页能绕开当前报错路由,避免"刷新-再崩"死循环
|
||
*/
|
||
export const goHomeRecover = (): void => {
|
||
safeStorage.removeItem(RELOAD_FLAG_KEY)
|
||
window.location.href = "/"
|
||
}
|