perf(csr): load component controllers on demand

This commit is contained in:
2026-08-09 14:10:15 +05:30
parent 324928cfca
commit 1660044ed2
8 changed files with 180 additions and 11 deletions
+26 -3
View File
@@ -1,11 +1,11 @@
import { test, expect, beforeEach } from "bun:test";
import { Window } from "happy-dom";
import { REACTIVE_RUNTIME } from "../src/reactive-runtime.ts";
import { getReactiveRuntime } from "../src/index.ts";
import { getComponentControllerRuntime, getReactiveRuntime } from "../src/index.ts";
import { mountHtml } from "@wrnexus/test";
// Fresh DOM per test, with the runtime's globals bound.
function mount(html: string): Window {
function mount(html: string, runtime = REACTIVE_RUNTIME, controllers = ""): Window {
const win = new Window() as unknown as Window & Record<string, unknown>;
win.document.body.innerHTML = `<div id="app">${html}</div>`;
(globalThis as Record<string, unknown>).window = win;
@@ -25,7 +25,8 @@ function mount(html: string): Window {
(globalThis as Record<string, unknown>).CustomEvent = (
win as unknown as { CustomEvent: unknown }
).CustomEvent;
(0, eval)(REACTIVE_RUNTIME);
(0, eval)(runtime);
if (controllers) (0, eval)(controllers);
// Hydrate deterministically (auto-init waits on DOMContentLoaded, which the
// test window may not fire). setupScope is idempotent, so this is safe.
const w = win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void };
@@ -41,6 +42,28 @@ beforeEach(() => {
delete (globalThis as Record<string, unknown>).MutationObserver;
});
test("split runtime hydrates a controller only from the controller asset", () => {
const core = getReactiveRuntime(true);
const controllers = getComponentControllerRuntime(true);
expect(core).not.toContain("function setupRovingFocus");
expect(controllers).toContain("function setupRovingFocus");
const win = mount(
`<div data-wrn-roving="horizontal"><button data-wrn-roving-item>One</button><button data-wrn-roving-item>Two</button></div>`,
core,
controllers,
);
const buttons = win.document.querySelectorAll("button");
(buttons[0] as unknown as HTMLButtonElement).focus();
buttons[0]!.dispatchEvent(new win.KeyboardEvent("keydown", { key: "ArrowRight", bubbles: true }));
expect(win.document.activeElement).toBe(buttons[1]);
});
test("a page without controller markers does not request the controller asset", () => {
const win = mount(`<main data-scope="count: 1"><span>{count}</span></main>`, getReactiveRuntime(true));
expect(win.document.querySelector('script[src$="controllers.js"]')).toBeNull();
});
test("hydrates {expr} mustaches from data-scope", () => {
const win = mount(`<div data-scope="count: 0"><span>{count}, {count * 2}</span></div>`);
expect(win.document.querySelector("span")!.textContent).toBe("0, 0");