fix(admin-web): auto-reload on stale dynamic-import chunks after deploy
Listen for vite:preloadError and router onError, force a one-shot reload (10s cooldown via sessionStorage) when a hashed lazy-loaded module 404s because a new deploy replaced it. Eliminates the user-visible "Failed to fetch dynamically imported module" error on login and route navigation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
const RELOAD_FLAG_KEY = '__chunk_reload_at'
|
||||
const RELOAD_COOLDOWN_MS = 10_000
|
||||
|
||||
function shouldReload(): boolean {
|
||||
try {
|
||||
const raw = sessionStorage.getItem(RELOAD_FLAG_KEY)
|
||||
if (!raw) return true
|
||||
const at = Number(raw)
|
||||
if (!Number.isFinite(at)) return true
|
||||
return Date.now() - at > RELOAD_COOLDOWN_MS
|
||||
} catch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function markReload(): void {
|
||||
try {
|
||||
sessionStorage.setItem(RELOAD_FLAG_KEY, String(Date.now()))
|
||||
} catch {
|
||||
// ignore storage failures (private mode, quota)
|
||||
}
|
||||
}
|
||||
|
||||
export function reloadForStaleChunk(): boolean {
|
||||
if (!shouldReload()) return false
|
||||
markReload()
|
||||
location.reload()
|
||||
return true
|
||||
}
|
||||
|
||||
const CHUNK_ERROR_PATTERNS = [
|
||||
/Failed to fetch dynamically imported module/i,
|
||||
/error loading dynamically imported module/i,
|
||||
/Importing a module script failed/i,
|
||||
/Unable to preload CSS/i,
|
||||
]
|
||||
|
||||
export function isChunkLoadError(err: unknown): boolean {
|
||||
const msg =
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: typeof err === 'string'
|
||||
? err
|
||||
: ''
|
||||
return CHUNK_ERROR_PATTERNS.some((re) => re.test(msg))
|
||||
}
|
||||
|
||||
export function registerChunkReloadHandlers(): void {
|
||||
window.addEventListener('vite:preloadError', (event) => {
|
||||
if (reloadForStaleChunk()) {
|
||||
event.preventDefault()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -53,11 +53,14 @@ import { createApp } from 'vue'
|
||||
import 'ant-design-vue/dist/reset.css'
|
||||
import App from './App.vue'
|
||||
import { i18n } from './i18n'
|
||||
import { registerChunkReloadHandlers } from './lib/chunk-reload'
|
||||
import { subscribeAuthExpired, subscribeStoredSession } from './lib/session'
|
||||
import { router } from './router'
|
||||
import { pinia } from './stores/pinia'
|
||||
import './styles.css'
|
||||
|
||||
registerChunkReloadHandlers()
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
import { isChunkLoadError, reloadForStaleChunk } from '@/lib/chunk-reload'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { pinia } from '@/stores/pinia'
|
||||
|
||||
@@ -282,4 +283,10 @@ router.beforeEach(async (to) => {
|
||||
return true
|
||||
})
|
||||
|
||||
router.onError((err) => {
|
||||
if (isChunkLoadError(err)) {
|
||||
reloadForStaleChunk()
|
||||
}
|
||||
})
|
||||
|
||||
export { router }
|
||||
|
||||
Reference in New Issue
Block a user