perf(library): load a collection when a view needs it, not at startup
All four collections were fetched at DOMContentLoaded and refetched on every invalidation, whichever view was showing. On a 26 138-track library the track list was 20.5 MB of that, and encoding it cost the backend ~170 MB of transient allocation — paid by someone looking at Home, which draws none of it. - The store warms only albums, artists and genres, on idle after first paint: 1.6 MB together, and what made those views instant. - A nav item prefetches on hover and on keyboard focus, which is the ~100 ms before the click that a cold open would otherwise wait. - An invalidation refetches what something had loaded, and nothing else — a scan no longer loads the track list of a library whose Tracks view nobody has opened. - `index.html`'s first-paint `<track-list>` is `view-hidden`, and `index.ts` no longer activates it: it is markup, not a decision about which view the launch lands on, and activating it was what fetched the whole list for a landing on Home. The track list itself loads on view activation, which the shell drives. Measured on 50 000 tracks: backend RSS at rest 543 → 296 MB, peak 571 → 296 MB, Go heap held 361 → 125 MB, JS heap 31.8 → 18 MB, binding bytes at rest 35.9 → 12.1 MB, heap after a browse 36.5 → 22.7 MB. Tracks first open 26 ms; slowest view open 57 ms. Closes #280
This commit is contained in:
1 parent
3d9828b847
commit
620151aa41
11 files changed
+538
-73
No files matched your search
+15
-2
@@ -200,13 +200,18 @@ const mainContent = document.getElementById('main-content');
|
||||
// tracked as currentViewEl, is never hidden: two visible primary views
|
||||
// splitting the main panel between them regardless of which is
|
||||
// selected.
|
||||
//
|
||||
// It is deliberately not activated here. It is markup, not a decision:
|
||||
// the shell does not yet know which view the launch lands on, and
|
||||
// activating it starts the Tracks view's work — its data fetch, above
|
||||
// all (#280) — for a launch that is about to land on Home. The first
|
||||
// navigation is what activates whichever view it lands on.
|
||||
if (mainContent) {
|
||||
const initialTrackList = mainContent.querySelector('track-list');
|
||||
|
||||
if (initialTrackList) {
|
||||
viewCache.set('tracks', initialTrackList as HTMLElement);
|
||||
currentViewEl = initialTrackList as HTMLElement;
|
||||
activateView(currentViewEl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,10 +585,14 @@ async function handleNavigate(
|
||||
}
|
||||
default: {
|
||||
const fallback = document.createElement('div');
|
||||
const message = document.createElement('p');
|
||||
|
||||
fallback.style.padding = '1em';
|
||||
fallback.style.color = 'var(--yj-text-secondary, #b3b3b3)';
|
||||
fallback.innerHTML = `<p>Coming soon: ${view}</p>`;
|
||||
// textContent, not innerHTML: `view` comes from a navigation
|
||||
// detail, which is app-supplied but not app-owned.
|
||||
message.textContent = `Coming soon: ${view}`;
|
||||
fallback.append(message);
|
||||
mainContent.appendChild(fallback);
|
||||
currentDetailEl = fallback;
|
||||
}
|
||||
@@ -620,6 +629,10 @@ function warmViewChunks(): void {
|
||||
|
||||
/** requestIdleCallback where it exists; WebKit2GTK does not have it. */
|
||||
function schedule(fn: () => void): void {
|
||||
// SAFETY: `requestIdleCallback` is not in this project's DOM lib
|
||||
// types, so it is read through an optional-property shape; the
|
||||
// property is either absent (undefined) or the browser's own
|
||||
// function, which is what the guard below checks.
|
||||
const ric = (
|
||||
window as unknown as {
|
||||
requestIdleCallback?: (cb: () => void) => number;
|
||||
|
||||
Reference in new issue
Block a user