perf(web): bound speculative route warming

This commit is contained in:
lingniu
2026-07-16 03:39:47 +08:00
parent cad39d6326
commit 2f26fd25b9
5 changed files with 135 additions and 37 deletions

View File

@@ -8,6 +8,14 @@ The 15-second monitor refresh previously treated every new response object as a
FleetMap now fingerprints only fields that affect map rendering. An unchanged refresh performs no AMap data/style/label mutation. A moving vehicle still updates MassMarks once, but the LabelsLayer removes and replaces only that vehicle's changed marker; full label-layer replacement is reserved for crossing the normal/dense zoom boundary. The selected ripple marker also skips redundant position writes. This uses the official AMap JS API 2.0 [`LabelsLayer.remove`](https://lbs.amap.com/api/javascript-api-v2/guide/amap-massmarker/label-marker) and `LabelMarker` update model instead of reconstructing all overlays. Unit tests assert zero map mutations for an `asOf`-only refresh and exactly one label replacement for one moved vehicle.
## 2026-07-16: bounded predictive route warming
The route shell split every primary page into a lazy chunk, but its idle queue then imported every inactive page during the first session. Successful ES-module imports cannot be unloaded, so this gradually made all route code resident even when an operator stayed on the monitor. It also let speculative parsing overlap live map work and defeated much of the memory benefit of route splitting.
Background warming is now predictive and bounded. Each active page has two likely next destinations; a fast visible session warms at most those two, one idle task at a time. A 3G or 4 GB device warms one, while `saveData`, 2G, hidden tabs and devices reporting 2 GB or less warm none. Pointer, focus and pointer-down intent still warm the exact destination immediately on capable connections, so deliberate navigation keeps the short transition while unused pages stay lazy. This follows web.dev's guidance to prefetch only high-confidence future resources, avoid slow or data-saving connections and prefer likely links over every link: <https://web.dev/articles/link-prefetch>, <https://web.dev/learn/performance/prefetching-prerendering-precaching>.
Unit tests protect route predictions, memory/network budgets, sequential scheduling, background-tab cancellation and cleanup. The production build remains guarded by a visible Suspense skeleton and the route chunk recovery boundary, so a page transition cannot become an empty content area while a non-warmed chunk loads.
## 2026-07-16: high-cardinality address cache lifecycle
The monitor list previously retained every manually resolved coordinate for 24 hours, and track replay inherited the global five-minute cache for every paused point. Long-running dispatch sessions could therefore accumulate address payloads across pages and arbitrary playback coordinates even after their components disappeared.
@@ -86,7 +94,7 @@ 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: <https://react.dev/reference/react/lazy>. React's Suspense documentation also distinguishes a loading fallback from error handling: <https://react.dev/reference/react/Suspense>.
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 first continuity pass only warmed Monitor, Tracks, History and Statistics in the background. A later pass expanded that queue to every route to reduce a 450 ms simulated cold transition, but doing so made all successful ES-module imports resident for every session. The current policy keeps pointer/focus/down intent warming for the exact destination and limits idle warming to one or two likely routes according to visibility, memory and connection constraints. This preserves the no-white-screen loading boundary without paying the memory cost of importing every page speculatively.
A production route-memory loop did not prove a retained application leak: after natural collection, JS heap returned to roughly its initial 31 MB range and old map documents/frames were released. It did expose an ineffective rendering hint: `content-visibility: auto` was attached to the always-visible vehicle scroll viewport instead of the 200 independently off-screen rows. The containment boundary now lives on every desktop rail row and mobile list card, paired with `contain-intrinsic-size` so skipped content keeps stable scroll geometry. This follows the CSS Containment specification's long-scroll-list use case while avoiding the documented scrollbar jump caused by missing intrinsic size: <https://www.w3.org/TR/css-contain-2/#content-visibility>.