From 7396544269cdf57a2a1f6819771b7b3a545a34f7 Mon Sep 17 00:00:00 2001 From: lingniu Date: Thu, 16 Jul 2026 02:35:10 +0800 Subject: [PATCH] perf(web): warm every primary route --- .../web/src/v2/routing/routeModules.test.ts | 19 ++++++++++++++----- .../apps/web/src/v2/routing/routeModules.ts | 11 +++++------ .../docs/frontend-production-readiness.md | 2 ++ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/v2/routing/routeModules.test.ts b/vehicle-data-platform/apps/web/src/v2/routing/routeModules.test.ts index f8c697df..cc1a2362 100644 --- a/vehicle-data-platform/apps/web/src/v2/routing/routeModules.test.ts +++ b/vehicle-data-platform/apps/web/src/v2/routing/routeModules.test.ts @@ -21,14 +21,14 @@ describe('route module preloading', () => { expect(shouldPreloadRoutes({ effectiveType: '4g' })).toBe(true); }); - it('warms inactive routes one idle period at a time and stops after cleanup', async () => { + it('warms every inactive primary route in bounded idle batches and stops after cleanup', async () => { const callbacks: Array<() => void> = []; const cancelled: number[] = []; const schedule = vi.fn((callback: () => void) => { const index = callbacks.push(callback) - 1; return () => { cancelled.push(index); }; }); - const preload = vi.fn(async () => undefined); + const preload = vi.fn(async (_pathname: string) => undefined); const cleanup = scheduleIdleRoutePreloads({ activePathname: '/monitor', connection: { effectiveType: '4g' }, @@ -39,13 +39,22 @@ describe('route module preloading', () => { expect(schedule).toHaveBeenCalledTimes(1); callbacks[0](); await flushPreloadQueue(); - expect(preload).toHaveBeenCalledWith('/tracks'); + expect(preload.mock.calls.map(([path]) => path)).toEqual(['/vehicles', '/tracks']); expect(schedule).toHaveBeenCalledTimes(2); callbacks[1](); await flushPreloadQueue(); - expect(preload).toHaveBeenLastCalledWith('/history'); + expect(preload.mock.calls.map(([path]) => path)).toEqual(['/vehicles', '/tracks', '/history', '/statistics']); + + callbacks[2](); + await flushPreloadQueue(); + callbacks[3](); + await flushPreloadQueue(); + expect(preload.mock.calls.map(([path]) => path)).toEqual([ + '/vehicles', '/tracks', '/history', '/statistics', '/alerts', '/access', '/operations' + ]); + cleanup(); - expect(cancelled).toContain(2); + expect(cancelled).toContain(4); }); }); diff --git a/vehicle-data-platform/apps/web/src/v2/routing/routeModules.ts b/vehicle-data-platform/apps/web/src/v2/routing/routeModules.ts index d216380a..2f44c6d2 100644 --- a/vehicle-data-platform/apps/web/src/v2/routing/routeModules.ts +++ b/vehicle-data-platform/apps/web/src/v2/routing/routeModules.ts @@ -18,8 +18,9 @@ const importers: Record Promise> = { const pendingModules = new Map>(); const backgroundPreloadOrder: RouteSection[] = [ - 'monitor', 'tracks', 'history', 'statistics' + 'monitor', 'vehicles', 'tracks', 'history', 'statistics', 'alerts', 'access', 'operations' ]; +const backgroundPreloadBatchSize = 2; function loadRoute(section: RouteSection) { const pending = pendingModules.get(section); @@ -84,11 +85,9 @@ export function scheduleIdleRoutePreloads({ const warmNext = () => { if (cancelled) return; - const section = queue.shift(); - if (!section) return; - void Promise.resolve() - .then(() => preload(`/${section}`)) - .catch(() => undefined) + const batch = queue.splice(0, backgroundPreloadBatchSize); + if (!batch.length) return; + void Promise.allSettled(batch.map((section) => Promise.resolve().then(() => preload(`/${section}`)))) .finally(() => { if (!cancelled) cancelScheduled = schedule(warmNext); }); diff --git a/vehicle-data-platform/docs/frontend-production-readiness.md b/vehicle-data-platform/docs/frontend-production-readiness.md index 08a57939..6b154deb 100644 --- a/vehicle-data-platform/docs/frontend-production-readiness.md +++ b/vehicle-data-platform/docs/frontend-production-readiness.md @@ -86,6 +86,8 @@ The default `test` command is now the production V2 suite. The old `App.tsx` int React documents that `lazy` caches the import promise and propagates a rejected module load to the nearest Error Boundary; this is why a route-level boundary is required rather than a loading fallback alone: . React's Suspense documentation also distinguishes a loading fallback from error handling: . +The first continuity pass only warmed Monitor, Tracks, History and Statistics in the background. A production cold-cache audit with 450 ms simulated latency still measured roughly 1.66 seconds for `/monitor` to `/access`, leaving Vehicles, Alerts, Access and Operations dependent on click-time loading. The idle queue now covers all eight route sections in bounded batches of two. It still skips `saveData`, `slow-2g` and `2g` connections, while pointer/focus/down intent preloading remains the fastest path for an immediately selected destination. Unit coverage proves every inactive route is queued and that cleanup cancels the next idle batch. + The navigation and progressive-loading direction follows mature observability systems: persistent global controls, collapsible sections and loading only the content needed for the current task. Elastic documents these dashboard interaction and panel-organization patterns here: and . ### Remaining audit queue