fix(web): bound lazy route recovery
This commit is contained in:
@@ -5,6 +5,10 @@ type RouteModule = { default: ComponentType };
|
||||
type RoutePreloadConnection = { saveData?: boolean; effectiveType?: string };
|
||||
type RoutePreloadRuntime = { visibilityState?: DocumentVisibilityState; deviceMemory?: number };
|
||||
type RoutePreloadScheduler = (callback: () => void) => () => void;
|
||||
type RouteLoadOptions = { timeoutMs?: number; retryDelayMs?: number; retries?: number };
|
||||
|
||||
const ROUTE_LOAD_TIMEOUT_MS = 10_000;
|
||||
const ROUTE_RETRY_DELAY_MS = 180;
|
||||
|
||||
const importers: Record<RouteSection, () => Promise<RouteModule>> = {
|
||||
monitor: () => import('../pages/MonitorPage'),
|
||||
@@ -29,10 +33,50 @@ const likelyNextRoutes: Record<RouteSection, RouteSection[]> = {
|
||||
operations: ['access', 'monitor']
|
||||
};
|
||||
|
||||
function routeLoadTimeout<T>(promise: Promise<T>, section: string, timeoutMs: number) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const timer = window.setTimeout(() => {
|
||||
const error = new Error(`Route chunk load timed out after ${timeoutMs}ms: ${section}`);
|
||||
error.name = 'RouteChunkLoadTimeoutError';
|
||||
reject(error);
|
||||
}, timeoutMs);
|
||||
promise.then(
|
||||
(value) => { window.clearTimeout(timer); resolve(value); },
|
||||
(error) => { window.clearTimeout(timer); reject(error); }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function retryDelay(delayMs: number) {
|
||||
return new Promise<void>((resolve) => window.setTimeout(resolve, delayMs));
|
||||
}
|
||||
|
||||
function isRouteLoadTimeout(error: unknown) {
|
||||
return error instanceof Error && error.name === 'RouteChunkLoadTimeoutError';
|
||||
}
|
||||
|
||||
export async function loadRouteModule<T>(
|
||||
loader: () => Promise<T>,
|
||||
section: string,
|
||||
{ timeoutMs = ROUTE_LOAD_TIMEOUT_MS, retryDelayMs = ROUTE_RETRY_DELAY_MS, retries = 1 }: RouteLoadOptions = {}
|
||||
) {
|
||||
let lastError: unknown;
|
||||
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
||||
try {
|
||||
return await routeLoadTimeout(Promise.resolve().then(loader), section, timeoutMs);
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
if (attempt >= retries || isRouteLoadTimeout(error)) break;
|
||||
await retryDelay(retryDelayMs);
|
||||
}
|
||||
}
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
function loadRoute(section: RouteSection) {
|
||||
const pending = pendingModules.get(section);
|
||||
if (pending) return pending;
|
||||
const next = importers[section]();
|
||||
const next = loadRouteModule(importers[section], section);
|
||||
pendingModules.set(section, next);
|
||||
void next.catch(() => pendingModules.delete(section));
|
||||
return next;
|
||||
|
||||
Reference in New Issue
Block a user