feat(admin): reload once when a chunk fails to load after a deploy (quick-261028-rqb)
- installChunkReloadGuard reloads on vite:preloadError, at most once per 60 s through a sessionStorage timestamp; no storage means no reload
This commit is contained in:
63
admin/src/app/chunkReload.ts
Normal file
63
admin/src/app/chunkReload.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
// Reload once when a chunk fails to load. After a deploy, an old tab that
|
||||
// asks for a hashed chunk the new build removed fails the dynamic import, and
|
||||
// Vite's preload helper dispatches vite:preloadError on window. One reload
|
||||
// loads the new index and its chunks. A timestamp in sessionStorage stops a
|
||||
// reload loop when the reload does not help, and without usable storage the
|
||||
// guard never reloads. It does not call preventDefault: the error still
|
||||
// reaches the importer (RichEditorField switches to source mode), so when the
|
||||
// guard declines, the existing failure state shows.
|
||||
|
||||
export const CHUNK_RELOAD_KEY = 'summer-admin.chunk-reload'
|
||||
/** No second automatic reload within 60 seconds of the last one. */
|
||||
export const CHUNK_RELOAD_WINDOW = 60_000
|
||||
|
||||
/**
|
||||
* Whether the guard may reload now. It records now under CHUNK_RELOAD_KEY
|
||||
* when it says yes. Null storage, a reload within CHUNK_RELOAD_WINDOW or
|
||||
* storage that throws means no.
|
||||
*/
|
||||
export function shouldReload(storage: Storage | null, now: number): boolean {
|
||||
if (storage === null) {
|
||||
return false
|
||||
}
|
||||
try {
|
||||
const last = Number.parseInt(storage.getItem(CHUNK_RELOAD_KEY) ?? '', 10)
|
||||
if (Number.isFinite(last) && now - last >= 0 && now - last < CHUNK_RELOAD_WINDOW) {
|
||||
return false
|
||||
}
|
||||
storage.setItem(CHUNK_RELOAD_KEY, String(now))
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function sessionStore(): Storage | null {
|
||||
try {
|
||||
return window.sessionStorage
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export interface ChunkReloadOptions {
|
||||
target?: Pick<EventTarget, 'addEventListener' | 'removeEventListener'>
|
||||
storage?: () => Storage | null
|
||||
reload?: () => void
|
||||
now?: () => number
|
||||
}
|
||||
|
||||
/** Installs the vite:preloadError guard and returns its uninstall function. */
|
||||
export function installChunkReloadGuard(options: ChunkReloadOptions = {}): () => void {
|
||||
const target = options.target ?? window
|
||||
const storage = options.storage ?? sessionStore
|
||||
const reload = options.reload ?? (() => window.location.reload())
|
||||
const now = options.now ?? Date.now
|
||||
const listener = (): void => {
|
||||
if (shouldReload(storage(), now())) {
|
||||
reload()
|
||||
}
|
||||
}
|
||||
target.addEventListener('vite:preloadError', listener)
|
||||
return () => target.removeEventListener('vite:preloadError', listener)
|
||||
}
|
||||
Reference in New Issue
Block a user