mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-09 03:17:52 +00:00
fix(auth): prevent repeated stale chunk reloads
Signed-off-by: ylhu16 <ylhu16@iflytek.com>
This commit is contained in:
parent
92ab2ddae7
commit
3c0aa56781
3 changed files with 105 additions and 9 deletions
|
|
@ -32,6 +32,9 @@ function createLazyRouteComponent<TModule extends Record<string, unknown>>(
|
|||
}
|
||||
throw error
|
||||
})
|
||||
// Router resolution can finish before React.lazy imports the route module. Only clear the
|
||||
// one-time reload guard after the chunk itself has loaded successfully.
|
||||
clearDynamicImportReloadGuard()
|
||||
return { default: module[exportName] as ComponentType<Record<string, unknown>> }
|
||||
})
|
||||
|
||||
|
|
@ -483,10 +486,6 @@ export const router = createRouter({
|
|||
defaultNotFoundComponent: DefaultNotFound,
|
||||
})
|
||||
|
||||
router.subscribe('onResolved', () => {
|
||||
clearDynamicImportReloadGuard()
|
||||
})
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: typeof router
|
||||
|
|
|
|||
87
web/src/shared/lib/dynamic-import-recovery.test.ts
Normal file
87
web/src/shared/lib/dynamic-import-recovery.test.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
clearDynamicImportReloadGuard,
|
||||
isDynamicImportFetchError,
|
||||
recoverFromDynamicImportError,
|
||||
} from './dynamic-import-recovery'
|
||||
|
||||
const values = new Map<string, string>()
|
||||
const reload = vi.fn()
|
||||
const sessionStorage = {
|
||||
get length() {
|
||||
return values.size
|
||||
},
|
||||
clear: vi.fn(() => values.clear()),
|
||||
getItem: vi.fn((key: string) => values.get(key) ?? null),
|
||||
key: vi.fn((index: number) => Array.from(values.keys())[index] ?? null),
|
||||
removeItem: vi.fn((key: string) => values.delete(key)),
|
||||
setItem: vi.fn((key: string, value: string) => values.set(key, value)),
|
||||
} satisfies Storage
|
||||
|
||||
describe('dynamic import recovery', () => {
|
||||
beforeEach(() => {
|
||||
values.clear()
|
||||
reload.mockClear()
|
||||
vi.stubGlobal('window', {
|
||||
location: { reload },
|
||||
sessionStorage,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it.each([
|
||||
'Failed to fetch dynamically imported module: /assets/login.js',
|
||||
'error loading dynamically imported module: /assets/login.js',
|
||||
'Importing a module script failed',
|
||||
'ChunkLoadError: Loading chunk 42 failed',
|
||||
])('recognizes a stale dynamic import error: %s', (message) => {
|
||||
expect(isDynamicImportFetchError(new Error(message))).toBe(true)
|
||||
})
|
||||
|
||||
it('ignores unrelated errors', () => {
|
||||
expect(isDynamicImportFetchError(new Error('Request failed with status 500'))).toBe(false)
|
||||
})
|
||||
|
||||
it('recognizes errors whose name is ChunkLoadError', () => {
|
||||
const error = new Error('Loading chunk 42 failed')
|
||||
error.name = 'ChunkLoadError'
|
||||
|
||||
expect(isDynamicImportFetchError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it('reloads only once while the recovery guard is active', () => {
|
||||
const error = new Error('Failed to fetch dynamically imported module')
|
||||
|
||||
expect(recoverFromDynamicImportError(error)).toBe(true)
|
||||
expect(recoverFromDynamicImportError(error)).toBe(false)
|
||||
expect(recoverFromDynamicImportError(error)).toBe(false)
|
||||
expect(reload).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('allows recovery again after a dynamic import succeeds', () => {
|
||||
const error = new Error('Failed to fetch dynamically imported module')
|
||||
|
||||
expect(recoverFromDynamicImportError(error)).toBe(true)
|
||||
clearDynamicImportReloadGuard()
|
||||
expect(recoverFromDynamicImportError(error)).toBe(true)
|
||||
expect(reload).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('does not mask the original import error when session storage is unavailable', () => {
|
||||
vi.stubGlobal('window', {
|
||||
location: { reload },
|
||||
get sessionStorage() {
|
||||
throw new DOMException('Access denied', 'SecurityError')
|
||||
},
|
||||
})
|
||||
|
||||
const error = new Error('Failed to fetch dynamically imported module')
|
||||
|
||||
expect(recoverFromDynamicImportError(error)).toBe(false)
|
||||
expect(() => clearDynamicImportReloadGuard()).not.toThrow()
|
||||
expect(reload).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
@ -9,7 +9,8 @@ function resolveErrorMessage(error: unknown): string {
|
|||
|
||||
export function isDynamicImportFetchError(error: unknown): boolean {
|
||||
const message = resolveErrorMessage(error)
|
||||
return message.includes('Failed to fetch dynamically imported module')
|
||||
return (error instanceof Error && error.name === 'ChunkLoadError')
|
||||
|| message.includes('Failed to fetch dynamically imported module')
|
||||
|| message.includes('error loading dynamically imported module')
|
||||
|| message.includes('Importing a module script failed')
|
||||
|| message.includes('ChunkLoadError')
|
||||
|
|
@ -20,12 +21,17 @@ export function recoverFromDynamicImportError(error: unknown): boolean {
|
|||
return false
|
||||
}
|
||||
|
||||
if (window.sessionStorage.getItem(RELOAD_GUARD_KEY) === '1') {
|
||||
window.sessionStorage.removeItem(RELOAD_GUARD_KEY)
|
||||
let sessionStorage: Storage
|
||||
try {
|
||||
sessionStorage = window.sessionStorage
|
||||
if (sessionStorage.getItem(RELOAD_GUARD_KEY) === '1') {
|
||||
return false
|
||||
}
|
||||
sessionStorage.setItem(RELOAD_GUARD_KEY, '1')
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
|
||||
window.sessionStorage.setItem(RELOAD_GUARD_KEY, '1')
|
||||
window.location.reload()
|
||||
return true
|
||||
}
|
||||
|
|
@ -34,5 +40,9 @@ export function clearDynamicImportReloadGuard(): void {
|
|||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
window.sessionStorage.removeItem(RELOAD_GUARD_KEY)
|
||||
try {
|
||||
window.sessionStorage.removeItem(RELOAD_GUARD_KEY)
|
||||
} catch {
|
||||
// Session storage can be unavailable in restricted browsing contexts.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue