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)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import App from './App.vue'
|
||||
import { createAdminRouter } from './app/router'
|
||||
import { onUnauthorized } from './api/client'
|
||||
import { startBuildWatch } from './app/buildUpdate'
|
||||
import { installChunkReloadGuard } from './app/chunkReload'
|
||||
import { loadFieldTypes } from './app/fieldTypes'
|
||||
import { loadStrings } from './app/i18n'
|
||||
import { expireSession, me } from './state/useAuth'
|
||||
@@ -12,6 +13,8 @@ import { loadSettings } from './state/useSettings'
|
||||
import './styles/main.css'
|
||||
|
||||
async function boot(): Promise<void> {
|
||||
// A chunk that fails to load after a deploy reloads the page once.
|
||||
installChunkReloadGuard()
|
||||
// The last theme choice (system unless the admin picked light or dark) is
|
||||
// applied before the first await, so nothing renders in the wrong theme;
|
||||
// me() then applies the admin's stored preference.
|
||||
|
||||
Reference in New Issue
Block a user