perf(csr): load component controllers on demand
This commit is contained in:
@@ -23,7 +23,7 @@ import {
|
|||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
||||||
import { buildRouter, type Route } from "@wrnexus/router";
|
import { buildRouter, type Route } from "@wrnexus/router";
|
||||||
import { getReactiveRuntime } from "@wrnexus/csr";
|
import { getComponentControllerRuntime, getReactiveRuntime } from "@wrnexus/csr";
|
||||||
import {
|
import {
|
||||||
analyzeRuntimeImports,
|
analyzeRuntimeImports,
|
||||||
analyzeRuntimeRequirements,
|
analyzeRuntimeRequirements,
|
||||||
@@ -109,6 +109,7 @@ export async function runBuild(appRoot: string): Promise<void> {
|
|||||||
const compiledDir = join(distDir, "compiled");
|
const compiledDir = join(distDir, "compiled");
|
||||||
const clientModulesDir = join(distDir, "client");
|
const clientModulesDir = join(distDir, "client");
|
||||||
const reactivePath = join(distDir, "reactive.js");
|
const reactivePath = join(distDir, "reactive.js");
|
||||||
|
const controllersPath = join(distDir, "controllers.js");
|
||||||
const publicDir = join(root, "public");
|
const publicDir = join(root, "public");
|
||||||
const distPublicDir = join(distDir, "public");
|
const distPublicDir = join(distDir, "public");
|
||||||
const config = await loadAppConfig(root);
|
const config = await loadAppConfig(root);
|
||||||
@@ -477,6 +478,13 @@ export async function runBuild(appRoot: string): Promise<void> {
|
|||||||
);
|
);
|
||||||
assetHash.update(reactiveCode);
|
assetHash.update(reactiveCode);
|
||||||
console.log(`✓ Runtime: ${reactivePath}`);
|
console.log(`✓ Runtime: ${reactivePath}`);
|
||||||
|
const controllerCode = await buildBrowserRuntime(
|
||||||
|
getComponentControllerRuntime(),
|
||||||
|
controllersPath,
|
||||||
|
join(compiledDir, "controllers.entry.js"),
|
||||||
|
);
|
||||||
|
assetHash.update(controllerCode);
|
||||||
|
console.log(`✓ Controllers: ${controllersPath}`);
|
||||||
|
|
||||||
// 1a) Theme tokens + client switcher (always emitted; built-in light/dark).
|
// 1a) Theme tokens + client switcher (always emitted; built-in light/dark).
|
||||||
const theme = resolveThemeConfig(config.theme);
|
const theme = resolveThemeConfig(config.theme);
|
||||||
@@ -693,6 +701,7 @@ await createProductionServer(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
reactivePath: join(import.meta.dir, "reactive.js"),
|
reactivePath: join(import.meta.dir, "reactive.js"),
|
||||||
|
controllersPath: join(import.meta.dir, "controllers.js"),
|
||||||
clientModulesDir: join(import.meta.dir, "client"),
|
clientModulesDir: join(import.meta.dir, "client"),
|
||||||
themePath: join(import.meta.dir, "theme.css"),
|
themePath: join(import.meta.dir, "theme.css"),
|
||||||
themeJsPath: join(import.meta.dir, "theme.js"),
|
themeJsPath: join(import.meta.dir, "theme.js"),
|
||||||
|
|||||||
+119
-5
@@ -17,12 +17,14 @@ export { NAV_RUNTIME } from "./nav-runtime.ts";
|
|||||||
export { REALTIME_RUNTIME } from "./realtime-runtime.ts";
|
export { REALTIME_RUNTIME } from "./realtime-runtime.ts";
|
||||||
export { ACTION_RUNTIME } from "./action-runtime.ts";
|
export { ACTION_RUNTIME } from "./action-runtime.ts";
|
||||||
|
|
||||||
/** The reactive runtime served at `/__wrnexus/reactive.js` (plain browser JS). */
|
const CONTROLLER_SECTIONS = ["PRIMARY", "UI", "PIN"] as const;
|
||||||
export function getReactiveRuntime(development = false): string {
|
|
||||||
|
function runtimeForMode(development: boolean): string {
|
||||||
if (development) {
|
if (development) {
|
||||||
return REACTIVE_RUNTIME
|
return REACTIVE_RUNTIME.replace(/\/\*__WRNEXUS_DEV_START__\*\//g, "").replace(
|
||||||
.replace(/\/\*__WRNEXUS_DEV_START__\*\//g, "")
|
/\/\*__WRNEXUS_DEV_END__\*\//g,
|
||||||
.replace(/\/\*__WRNEXUS_DEV_END__\*\//g, "");
|
"",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return REACTIVE_RUNTIME.replace(
|
return REACTIVE_RUNTIME.replace(
|
||||||
/\/\*__WRNEXUS_DEV_START__\*\/[\s\S]*?\/\*__WRNEXUS_DEV_END__\*\//g,
|
/\/\*__WRNEXUS_DEV_START__\*\/[\s\S]*?\/\*__WRNEXUS_DEV_END__\*\//g,
|
||||||
@@ -30,6 +32,118 @@ export function getReactiveRuntime(development = false): string {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function controllerSection(source: string, name: (typeof CONTROLLER_SECTIONS)[number]): string {
|
||||||
|
const pattern = new RegExp(
|
||||||
|
`/\\*__WRNEXUS_CONTROLLERS_${name}_START__\\*/([\\s\\S]*?)/\\*__WRNEXUS_CONTROLLERS_${name}_END__\\*/`,
|
||||||
|
);
|
||||||
|
const match = pattern.exec(source);
|
||||||
|
if (!match?.[1]) throw new Error(`Missing reactive runtime controller section: ${name}`);
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripControllerSections(source: string): string {
|
||||||
|
for (const name of CONTROLLER_SECTIONS) {
|
||||||
|
source = source.replace(
|
||||||
|
new RegExp(
|
||||||
|
`/\\*__WRNEXUS_CONTROLLERS_${name}_START__\\*/[\\s\\S]*?/\\*__WRNEXUS_CONTROLLERS_${name}_END__\\*/`,
|
||||||
|
),
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONTROLLER_LOADER = String.raw`
|
||||||
|
var componentControllerSelector = "[data-wrn-anchored],[data-wrn-dialog],[data-wrn-roving],[data-wrn-scrollspy],[data-wrn-splitter],[data-wrn-navbar],[data-wrn-preferences],[data-wrn-select],[data-wrn-pin-input]";
|
||||||
|
var componentControllerPromise = null;
|
||||||
|
var componentControllerUrl = (document.currentScript && document.currentScript.src)
|
||||||
|
? new URL("./controllers.js", document.currentScript.src).href
|
||||||
|
: "/__wrnexus/controllers.js";
|
||||||
|
function needsComponentControllers(root) {
|
||||||
|
var host = root || document;
|
||||||
|
return !!((host.matches && host.matches(componentControllerSelector)) ||
|
||||||
|
(host.querySelector && host.querySelector(componentControllerSelector)));
|
||||||
|
}
|
||||||
|
function loadComponentControllers(root) {
|
||||||
|
if (!needsComponentControllers(root)) return;
|
||||||
|
if (window.__wrnexusComponentControllers) {
|
||||||
|
window.__wrnexusComponentControllers.hydrate(root || document);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!componentControllerPromise) {
|
||||||
|
componentControllerPromise = new Promise(function (resolve, reject) {
|
||||||
|
var script = document.createElement("script");
|
||||||
|
script.src = componentControllerUrl;
|
||||||
|
script.defer = true;
|
||||||
|
script.onload = resolve;
|
||||||
|
script.onerror = reject;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
componentControllerPromise.then(function () {
|
||||||
|
if (window.__wrnexusComponentControllers) window.__wrnexusComponentControllers.hydrate(root || document);
|
||||||
|
}).catch(function (error) { console.error("[wrnexus] failed to load component controllers", error); });
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/** The reactive runtime served at `/__wrnexus/reactive.js` (plain browser JS). */
|
||||||
|
export function getReactiveRuntime(development = false): string {
|
||||||
|
let source = stripControllerSections(runtimeForMode(development));
|
||||||
|
source = source.replace(" function hydrateScopes(root) {", `${CONTROLLER_LOADER}\n function hydrateScopes(root) {`);
|
||||||
|
source = source.replace(
|
||||||
|
" hydrateNavbarControllers(host);\n hydratePreferenceControllers(host);\n hydrateSelectControllers(host);\n hydratePinInputControllers(host);",
|
||||||
|
" loadComponentControllers(host);",
|
||||||
|
);
|
||||||
|
source = source.replace(" startDocumentWatch();\n", "");
|
||||||
|
source = source.replace(
|
||||||
|
" setupAnchoredOverlays();\n setupModalDialogs();\n setupRovingFocus();\n setupScrollspy();\n setupSplitters();",
|
||||||
|
" loadComponentControllers(document);",
|
||||||
|
);
|
||||||
|
source = source.replace(
|
||||||
|
" window.__wrnexusRepositionAnchored = repositionAnchored;",
|
||||||
|
" window.__wrnexusRepositionAnchored = function (root) { if (window.__wrnexusComponentControllers) window.__wrnexusComponentControllers.reposition(root); };",
|
||||||
|
);
|
||||||
|
source = source.replace(
|
||||||
|
" window.__wrnexusMountClientRoots = mountClientRoots;",
|
||||||
|
` window.__wrnexusControllerBridge = { invokeComponentOutput: invokeComponentOutput, callServerFunction: callServerFunction, dispatchComponentEvent: dispatchComponentEvent, emitPinInputEvent: emitPinInputEvent, parseScopeDecl: parseScopeDecl, warn: ${development ? "warnOnce" : "function () {}"} };\n window.__wrnexusMountClientRoots = mountClientRoots;`,
|
||||||
|
);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Component-specific controllers, loaded only when their marker is present. */
|
||||||
|
export function getComponentControllerRuntime(development = false): string {
|
||||||
|
const source = runtimeForMode(development);
|
||||||
|
const sections = CONTROLLER_SECTIONS.map((name) => controllerSection(source, name)).join("\n");
|
||||||
|
return `
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
var bridge = window.__wrnexusControllerBridge || {};
|
||||||
|
var invokeComponentOutput = bridge.invokeComponentOutput;
|
||||||
|
var callServerFunction = bridge.callServerFunction;
|
||||||
|
var dispatchComponentEvent = bridge.dispatchComponentEvent;
|
||||||
|
var emitPinInputEvent = bridge.emitPinInputEvent;
|
||||||
|
var parseScopeDecl = bridge.parseScopeDecl;
|
||||||
|
var warnOnce = bridge.warn || function () {};
|
||||||
|
${sections}
|
||||||
|
function hydrate(root) {
|
||||||
|
var host = root || document;
|
||||||
|
hydrateNavbarControllers(host);
|
||||||
|
hydratePreferenceControllers(host);
|
||||||
|
hydrateSelectControllers(host);
|
||||||
|
hydratePinInputControllers(host);
|
||||||
|
syncRovingGroups(host);
|
||||||
|
}
|
||||||
|
setupAnchoredOverlays();
|
||||||
|
setupModalDialogs();
|
||||||
|
setupRovingFocus();
|
||||||
|
setupScrollspy();
|
||||||
|
setupSplitters();
|
||||||
|
window.__wrnexusComponentControllers = { hydrate: hydrate, reposition: repositionAnchored };
|
||||||
|
hydrate(document);
|
||||||
|
})();
|
||||||
|
`.trim();
|
||||||
|
}
|
||||||
|
|
||||||
/** The client-side navigation runtime served at `/__wrnexus/nav.js`. */
|
/** The client-side navigation runtime served at `/__wrnexus/nav.js`. */
|
||||||
export function getNavRuntime(): string {
|
export function getNavRuntime(): string {
|
||||||
return NAV_RUNTIME;
|
return NAV_RUNTIME;
|
||||||
|
|||||||
@@ -2864,6 +2864,7 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
* cleared, so repositioning is idempotent -- re-running it on a panel that
|
* cleared, so repositioning is idempotent -- re-running it on a panel that
|
||||||
* is already on screen changes nothing and cannot drift.
|
* is already on screen changes nothing and cannot drift.
|
||||||
*/
|
*/
|
||||||
|
/*__WRNEXUS_CONTROLLERS_PRIMARY_START__*/
|
||||||
var ANCHORED_SELECTOR = "[data-wrn-anchored]";
|
var ANCHORED_SELECTOR = "[data-wrn-anchored]";
|
||||||
var ANCHOR_MARGIN = 8;
|
var ANCHOR_MARGIN = 8;
|
||||||
var anchoredScheduled = false;
|
var anchoredScheduled = false;
|
||||||
@@ -3480,6 +3481,8 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
document.addEventListener("pointercancel", endDrag);
|
document.addEventListener("pointercancel", endDrag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*__WRNEXUS_CONTROLLERS_PRIMARY_END__*/
|
||||||
|
|
||||||
function hydrateScopes(root) {
|
function hydrateScopes(root) {
|
||||||
var host = root || document;
|
var host = root || document;
|
||||||
|
|
||||||
@@ -3510,6 +3513,7 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
/*__WRNEXUS_DEV_END__*/
|
/*__WRNEXUS_DEV_END__*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*__WRNEXUS_CONTROLLERS_UI_START__*/
|
||||||
var navbarOutsideClickBound = false;
|
var navbarOutsideClickBound = false;
|
||||||
var preferenceOutsideClickBound = false;
|
var preferenceOutsideClickBound = false;
|
||||||
|
|
||||||
@@ -4028,6 +4032,8 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*__WRNEXUS_CONTROLLERS_UI_END__*/
|
||||||
|
|
||||||
function invokeComponentOutput(root, name, payload) {
|
function invokeComponentOutput(root, name, payload) {
|
||||||
if (!root || !name) return undefined;
|
if (!root || !name) return undefined;
|
||||||
var registry = root.__wrnexusOutputHandlers;
|
var registry = root.__wrnexusOutputHandlers;
|
||||||
@@ -4135,6 +4141,7 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*__WRNEXUS_CONTROLLERS_PIN_START__*/
|
||||||
function setupPinInputController(root) {
|
function setupPinInputController(root) {
|
||||||
if (!root || root.__wrnexusPinInputController) return;
|
if (!root || root.__wrnexusPinInputController) return;
|
||||||
var cells = Array.prototype.slice.call(root.querySelectorAll("[data-pin-cell]"));
|
var cells = Array.prototype.slice.call(root.querySelectorAll("[data-pin-cell]"));
|
||||||
@@ -4310,6 +4317,8 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
inputs.forEach(setupPinInputController);
|
inputs.forEach(setupPinInputController);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*__WRNEXUS_CONTROLLERS_PIN_END__*/
|
||||||
|
|
||||||
function parseScopeDecl(decl) {
|
function parseScopeDecl(decl) {
|
||||||
var initial = {};
|
var initial = {};
|
||||||
splitTopLevel(decl, ",").forEach(function (part) {
|
splitTopLevel(decl, ",").forEach(function (part) {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { test, expect, beforeEach } from "bun:test";
|
import { test, expect, beforeEach } from "bun:test";
|
||||||
import { Window } from "happy-dom";
|
import { Window } from "happy-dom";
|
||||||
import { REACTIVE_RUNTIME } from "../src/reactive-runtime.ts";
|
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";
|
import { mountHtml } from "@wrnexus/test";
|
||||||
|
|
||||||
// Fresh DOM per test, with the runtime's globals bound.
|
// 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>;
|
const win = new Window() as unknown as Window & Record<string, unknown>;
|
||||||
win.document.body.innerHTML = `<div id="app">${html}</div>`;
|
win.document.body.innerHTML = `<div id="app">${html}</div>`;
|
||||||
(globalThis as Record<string, unknown>).window = win;
|
(globalThis as Record<string, unknown>).window = win;
|
||||||
@@ -25,7 +25,8 @@ function mount(html: string): Window {
|
|||||||
(globalThis as Record<string, unknown>).CustomEvent = (
|
(globalThis as Record<string, unknown>).CustomEvent = (
|
||||||
win as unknown as { CustomEvent: unknown }
|
win as unknown as { CustomEvent: unknown }
|
||||||
).CustomEvent;
|
).CustomEvent;
|
||||||
(0, eval)(REACTIVE_RUNTIME);
|
(0, eval)(runtime);
|
||||||
|
if (controllers) (0, eval)(controllers);
|
||||||
// Hydrate deterministically (auto-init waits on DOMContentLoaded, which the
|
// Hydrate deterministically (auto-init waits on DOMContentLoaded, which the
|
||||||
// test window may not fire). setupScope is idempotent, so this is safe.
|
// test window may not fire). setupScope is idempotent, so this is safe.
|
||||||
const w = win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void };
|
const w = win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void };
|
||||||
@@ -41,6 +42,28 @@ beforeEach(() => {
|
|||||||
delete (globalThis as Record<string, unknown>).MutationObserver;
|
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", () => {
|
test("hydrates {expr} mustaches from data-scope", () => {
|
||||||
const win = mount(`<div data-scope="count: 0"><span>{count}, {count * 2}</span></div>`);
|
const win = mount(`<div data-scope="count: 0"><span>{count}, {count * 2}</span></div>`);
|
||||||
expect(win.document.querySelector("span")!.textContent).toBe("0, 0");
|
expect(win.document.querySelector("span")!.textContent).toBe("0, 0");
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
getActionRuntime,
|
getActionRuntime,
|
||||||
|
getComponentControllerRuntime,
|
||||||
getReactiveRuntime,
|
getReactiveRuntime,
|
||||||
getNavRuntime,
|
getNavRuntime,
|
||||||
getRealtimeRuntime,
|
getRealtimeRuntime,
|
||||||
@@ -94,6 +95,8 @@ export function createDevAssetServer(
|
|||||||
return serveWrnBrowserArtifact(pathname) ?? new Response("Not Found", { status: 404 });
|
return serveWrnBrowserArtifact(pathname) ?? new Response("Not Found", { status: 404 });
|
||||||
}
|
}
|
||||||
if (pathname === "/__wrnexus/reactive.js") return jsResponse(getReactiveRuntime(true));
|
if (pathname === "/__wrnexus/reactive.js") return jsResponse(getReactiveRuntime(true));
|
||||||
|
if (pathname === "/__wrnexus/controllers.js")
|
||||||
|
return jsResponse(getComponentControllerRuntime(true));
|
||||||
if (pathname === "/__wrnexus/nav.js") return jsResponse(getNavRuntime());
|
if (pathname === "/__wrnexus/nav.js") return jsResponse(getNavRuntime());
|
||||||
if (pathname === "/__wrnexus/realtime.js") return jsResponse(getRealtimeRuntime());
|
if (pathname === "/__wrnexus/realtime.js") return jsResponse(getRealtimeRuntime());
|
||||||
if (pathname === "/__wrnexus/actions.js") return jsResponse(getActionRuntime());
|
if (pathname === "/__wrnexus/actions.js") return jsResponse(getActionRuntime());
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
} from "@wrnexus/router";
|
} from "@wrnexus/router";
|
||||||
import {
|
import {
|
||||||
getActionRuntime,
|
getActionRuntime,
|
||||||
|
getComponentControllerRuntime,
|
||||||
getReactiveRuntime,
|
getReactiveRuntime,
|
||||||
getNavRuntime,
|
getNavRuntime,
|
||||||
getRealtimeRuntime,
|
getRealtimeRuntime,
|
||||||
@@ -92,6 +93,8 @@ export interface ProdOptions {
|
|||||||
stylesIncludeFramework?: boolean;
|
stylesIncludeFramework?: boolean;
|
||||||
/** Absolute path to the pre-built reactive runtime. */
|
/** Absolute path to the pre-built reactive runtime. */
|
||||||
reactivePath?: string;
|
reactivePath?: string;
|
||||||
|
/** Absolute path to the on-demand component controller runtime. */
|
||||||
|
controllersPath?: string;
|
||||||
/** Absolute directory containing bundled per-WRN browser modules. */
|
/** Absolute directory containing bundled per-WRN browser modules. */
|
||||||
clientModulesDir?: string;
|
clientModulesDir?: string;
|
||||||
/** Absolute path to the pre-built theme stylesheet (`theme.css`). */
|
/** Absolute path to the pre-built theme stylesheet (`theme.css`). */
|
||||||
@@ -341,6 +344,10 @@ function createProdAssetServer(opts: ProdOptions): AssetServer {
|
|||||||
}
|
}
|
||||||
return new Response(getReactiveRuntime(), { headers: JS_HEADERS });
|
return new Response(getReactiveRuntime(), { headers: JS_HEADERS });
|
||||||
}
|
}
|
||||||
|
if (pathname === "/__wrnexus/controllers.js") {
|
||||||
|
if (opts.controllersPath) return serveFile(opts.controllersPath, JS_HEADERS);
|
||||||
|
return new Response(getComponentControllerRuntime(), { headers: JS_HEADERS });
|
||||||
|
}
|
||||||
if (pathname === "/__wrnexus/nav.js")
|
if (pathname === "/__wrnexus/nav.js")
|
||||||
return new Response(getNavRuntime(), { headers: JS_HEADERS });
|
return new Response(getNavRuntime(), { headers: JS_HEADERS });
|
||||||
if (pathname === "/__wrnexus/realtime.js")
|
if (pathname === "/__wrnexus/realtime.js")
|
||||||
|
|||||||
@@ -12,12 +12,14 @@
|
|||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
getReactiveRuntime,
|
getReactiveRuntime,
|
||||||
|
getComponentControllerRuntime,
|
||||||
getNavRuntime,
|
getNavRuntime,
|
||||||
getRealtimeRuntime,
|
getRealtimeRuntime,
|
||||||
} from "../../packages/csr/src/index.ts";
|
} from "../../packages/csr/src/index.ts";
|
||||||
|
|
||||||
const runtimes: Record<string, string> = {
|
const runtimes: Record<string, string> = {
|
||||||
"reactive-runtime.ts": getReactiveRuntime(),
|
"reactive-runtime.ts": getReactiveRuntime(),
|
||||||
|
"component-controllers.ts": getComponentControllerRuntime(),
|
||||||
"nav-runtime.ts": getNavRuntime(),
|
"nav-runtime.ts": getNavRuntime(),
|
||||||
"realtime-runtime.ts": getRealtimeRuntime(),
|
"realtime-runtime.ts": getRealtimeRuntime(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -169,7 +169,8 @@ addCheck(
|
|||||||
* minified transfer once. That is the number worth defending.
|
* minified transfer once. That is the number worth defending.
|
||||||
*/
|
*/
|
||||||
const runtimeBudgets = {
|
const runtimeBudgets = {
|
||||||
"reactive-runtime.ts": 71_000,
|
"reactive-runtime.ts": 49_000,
|
||||||
|
"component-controllers.ts": 24_100,
|
||||||
"nav-runtime.ts": 12_000,
|
"nav-runtime.ts": 12_000,
|
||||||
"realtime-runtime.ts": 8_000,
|
"realtime-runtime.ts": 8_000,
|
||||||
};
|
};
|
||||||
@@ -182,7 +183,8 @@ const minifiedSizes = JSON.parse(
|
|||||||
);
|
);
|
||||||
for (const [file, budget] of Object.entries(runtimeBudgets)) {
|
for (const [file, budget] of Object.entries(runtimeBudgets)) {
|
||||||
const bytes = minifiedSizes[file];
|
const bytes = minifiedSizes[file];
|
||||||
const raw = statSync(join(root, "packages", "csr", "src", file)).size;
|
const sourceFile = file === "component-controllers.ts" ? "reactive-runtime.ts" : file;
|
||||||
|
const raw = statSync(join(root, "packages", "csr", "src", sourceFile)).size;
|
||||||
addCheck(
|
addCheck(
|
||||||
`PERF-RUNTIME-SIZE-${file.replace(/-runtime\.ts$/, "").toUpperCase()}`,
|
`PERF-RUNTIME-SIZE-${file.replace(/-runtime\.ts$/, "").toUpperCase()}`,
|
||||||
typeof bytes === "number" && bytes <= budget,
|
typeof bytes === "number" && bytes <= budget,
|
||||||
|
|||||||
Reference in New Issue
Block a user