A page used to be listed three times (lib/modules for its label, icon and gate; shell/moduleRegistry for its page and rail; lib/pageMeta for its title), and App held every rail's layout and collapse flag. Now each module's `module.ts` says it all — label, icon, gate, its lazy page and rail, its width — handed out through its index.ts, and features/navigation/moduleRegistry lists them in nav order for the rail, the palette, the tab title, the reachable set and the shell's render. A contract test holds the registry to the page ids and each definition to its own folder, with no static import of a page. Every rail is lazy now, loaded together with its page so a pinned rail never arrives after it and shoves the content aside; the feed's and the playlists' rails took @dnd-kit off the boot path (boot 428.5 → 393.9 KiB gzip, the App chunk 122 → 32 KiB). A rail reads its own layout and collapse flag (lib/useRail) and writes a layout change to the `me` cache too, since it now mounts anew on each visit. App's session effects, its one-shot entry-URL handlers and the admin nudge move into shell hooks; App is layout. The registry's three layer suppressions are gone: none is left.
55 lines
2.6 KiB
TypeScript
55 lines
2.6 KiB
TypeScript
import { createElement, lazy, type ComponentType } from "react";
|
|
|
|
// A route-level lazy component whose code can be fetched BEFORE its first render.
|
|
//
|
|
// React.lazy alone suspends on every first render, even when the chunk is already in the browser: the
|
|
// module promise settles a tick later, so the Suspense fallback is committed and painted for a frame or
|
|
// several. On a module switch that is a "Loading…" flash in place of the page, on every first visit.
|
|
// Here, once `preload()` has resolved, the component renders the loaded module synchronously and never
|
|
// suspends at all.
|
|
//
|
|
// Choosing per render (`loaded ?? Lazy`) cannot swap a mounted page's component type and remount it:
|
|
// a render that suspends on `Lazy` is never committed (React discards a suspended new mount), and
|
|
// `loaded` is set before `Lazy`'s own promise resolves — so every committed render of a mount sees the
|
|
// loaded module. Measured, not assumed: the state test in lazyWithPreload.dom.test.tsx stayed green
|
|
// with a per-mount pin removed, in a boundary that was already showing content.
|
|
|
|
export type PreloadableComponent<P extends object> = ComponentType<P> & {
|
|
/** Fetch the component's code now. Idempotent; a failed fetch is retried by the next call. */
|
|
preload: () => Promise<void>;
|
|
};
|
|
|
|
export function lazyWithPreload<P extends object>(
|
|
factory: () => Promise<{ default: ComponentType<P> }>,
|
|
): PreloadableComponent<P> {
|
|
let loaded: ComponentType<P> | null = null;
|
|
let pending: Promise<void> | null = null;
|
|
const preload = () =>
|
|
(pending ??= factory().then(
|
|
(m) => {
|
|
loaded = m.default;
|
|
},
|
|
(err: unknown) => {
|
|
pending = null;
|
|
throw err;
|
|
},
|
|
));
|
|
const Lazy = lazy(() => preload().then(() => ({ default: loaded as ComponentType<P> })));
|
|
|
|
function Preloadable(props: P) {
|
|
return createElement(loaded ?? (Lazy as unknown as ComponentType<P>), props);
|
|
}
|
|
return Object.assign(Preloadable, { preload });
|
|
}
|
|
|
|
/** A lazy page that resolves only once `alongside` (its side rail) has loaded too. The rail renders in
|
|
* its own Suspense beside the page, and a pinned rail sits in the page's flow: arriving after the page,
|
|
* it would shove the page's content sideways on its first visit. Loaded together, the rail is already
|
|
* there (it renders synchronously once loaded) when the page commits. */
|
|
export function lazyWithPreloadAlongside<P extends object>(
|
|
factory: () => Promise<{ default: ComponentType<P> }>,
|
|
alongside: PreloadableComponent<object>,
|
|
): PreloadableComponent<P> {
|
|
return lazyWithPreload(() => Promise.all([factory(), alongside.preload()]).then(([m]) => m));
|
|
}
|