perf(compiler): share client function state closures
This commit is contained in:
@@ -308,21 +308,82 @@ export function generateBrowserModule(ast: PageAst): string {
|
|||||||
const selectedImports = selectedBrowserImports(ast, functions);
|
const selectedImports = selectedBrowserImports(ast, functions);
|
||||||
const imports = selectedImports.map((entry) => entry.code).join("\n");
|
const imports = selectedImports.map((entry) => entry.code).join("\n");
|
||||||
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))];
|
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))];
|
||||||
|
const sharedState = state.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name));
|
||||||
|
const sharedProps = ast.props
|
||||||
|
.map((entry) => entry.name)
|
||||||
|
.filter(
|
||||||
|
(name) =>
|
||||||
|
safeIdentifier(name) &&
|
||||||
|
!RUNTIME_BINDINGS.has(name) &&
|
||||||
|
!sharedState.includes(name),
|
||||||
|
);
|
||||||
|
const callableAliases = functionNames.filter(
|
||||||
|
(name) =>
|
||||||
|
safeIdentifier(name) &&
|
||||||
|
!RUNTIME_BINDINGS.has(name) &&
|
||||||
|
!sharedState.includes(name) &&
|
||||||
|
!sharedProps.includes(name),
|
||||||
|
);
|
||||||
|
const sharedCommit = sharedState.map((name) => `context.state.${name} = ${name};`).join(" ");
|
||||||
|
const sharedRestore = [
|
||||||
|
...sharedState.map((name) => `${name} = context.state.${name};`),
|
||||||
|
...sharedProps.map((name) => `${name} = context.props.${name};`),
|
||||||
|
].join(" ");
|
||||||
|
const hasAuthoredCommit = callableAliases.includes("commit");
|
||||||
|
const hasAuthoredSetTimeout = callableAliases.includes("setTimeout");
|
||||||
|
const implementations = functions
|
||||||
|
.map((fn) => {
|
||||||
|
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
|
||||||
|
return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(${parameters}) {
|
||||||
|
try { ${eraseFunctionTypes(fn.body)} } finally { __wrnexusCommit(); }
|
||||||
|
}`;
|
||||||
|
})
|
||||||
|
.join(",\n");
|
||||||
return `// generated WRNexusJS browser module for ${ast.name}
|
return `// generated WRNexusJS browser module for ${ast.name}
|
||||||
${imports}
|
${imports}
|
||||||
export const __wrnexusClientFunctions = {
|
export const __wrnexusClientFunctions = {
|
||||||
${functions.map((fn) => ` ${functionEntry(ast, fn, functionNames)}`).join(",\n")}
|
${functions
|
||||||
|
.map(
|
||||||
|
(fn) =>
|
||||||
|
` ${JSON.stringify(fn.name)}: (context, ...args) => __wrnexusBindings(context)[${JSON.stringify(fn.name)}](...args)`,
|
||||||
|
)
|
||||||
|
.join(",\n")}
|
||||||
};
|
};
|
||||||
export const __wrnexusClientState = ${JSON.stringify(state)};
|
export const __wrnexusClientState = ${JSON.stringify(state)};
|
||||||
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
|
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
|
||||||
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} };
|
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} };
|
||||||
export function bindClientScope(context) {
|
function __wrnexusCreateClientFunctions(context) {
|
||||||
|
${sharedState.length ? `let { ${sharedState.join(", ")} } = context.state;` : ""}
|
||||||
|
${sharedProps.length ? `let { ${sharedProps.join(", ")} } = context.props;` : ""}
|
||||||
|
const output = context.output;
|
||||||
|
const server = context.server;
|
||||||
|
const props = context.props;
|
||||||
|
const refs = context.refs;
|
||||||
|
const __wrnexusCommit = () => { ${sharedCommit} };
|
||||||
|
const __wrnexusRestore = () => { ${sharedRestore} };
|
||||||
|
${!hasAuthoredCommit ? "const commit = __wrnexusCommit;" : ""}
|
||||||
|
${!hasAuthoredSetTimeout ? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
|
||||||
|
try { return callback(...args); } finally { __wrnexusCommit(); }
|
||||||
|
}, delay);` : ""}
|
||||||
|
const implementations = {
|
||||||
|
${implementations}
|
||||||
|
};
|
||||||
|
${callableAliases.map((name) => `const ${name} = (...args) => implementations[${JSON.stringify(name)}](...args);`).join("\n ")}
|
||||||
const functions = {};
|
const functions = {};
|
||||||
const scopedContext = { ...context, functions };
|
for (const name of Object.keys(implementations)) {
|
||||||
for (const [name, handler] of Object.entries(__wrnexusClientFunctions)) {
|
functions[name] = (...args) => {
|
||||||
functions[name] = (...args) => handler(scopedContext, ...args);
|
__wrnexusRestore();
|
||||||
|
return implementations[name](...args);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return functions;
|
return functions;
|
||||||
}
|
}
|
||||||
|
function __wrnexusBindings(context) {
|
||||||
|
return context.__wrnexusBoundFunctions ||
|
||||||
|
(context.__wrnexusBoundFunctions = __wrnexusCreateClientFunctions(context));
|
||||||
|
}
|
||||||
|
export function bindClientScope(context) {
|
||||||
|
return __wrnexusBindings(context);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { expect, test } from "bun:test";
|
import { expect, test } from "bun:test";
|
||||||
|
import { gzipSync } from "bun";
|
||||||
import { generateTargets, parse } from "../src/index.ts";
|
import { generateTargets, parse } from "../src/index.ts";
|
||||||
|
|
||||||
const ast = parse(`component ConfirmDialog {
|
const ast = parse(`component ConfirmDialog {
|
||||||
@@ -130,7 +131,7 @@ test("browser codegen binds peer client functions through the scoped function ta
|
|||||||
view { <button @click='run()'>{value}</button> }
|
view { <button @click='run()'>{value}</button> }
|
||||||
}`),
|
}`),
|
||||||
);
|
);
|
||||||
expect(targets.browser).toContain("context.functions");
|
expect(targets.browser).toContain('implementations["increment"]');
|
||||||
const executable = new Function(
|
const executable = new Function(
|
||||||
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn { bindClientScope };`,
|
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn { bindClientScope };`,
|
||||||
)() as { bindClientScope: (context: Record<string, unknown>) => Record<string, () => void> };
|
)() as { bindClientScope: (context: Record<string, unknown>) => Record<string, () => void> };
|
||||||
@@ -200,3 +201,27 @@ test("the deferred commit helper does not shadow an authored commit function", (
|
|||||||
expect(() => new Function(targets.browser.replace(/^export\s+/gm, ""))).not.toThrow();
|
expect(() => new Function(targets.browser.replace(/^export\s+/gm, ""))).not.toThrow();
|
||||||
expect(targets.browser).not.toContain("const commit = __wrnexusCommit");
|
expect(targets.browser).not.toContain("const commit = __wrnexusCommit");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("generated client modules stay below decoded-size and duplication ratchets", () => {
|
||||||
|
const states = Array.from({ length: 12 }, (_, index) => `s${index}: number = ${index}`).join(" ");
|
||||||
|
const functions = Array.from(
|
||||||
|
{ length: 20 },
|
||||||
|
(_, index) =>
|
||||||
|
`client function f${index}(): void { s${index % 12} += 1 ${Array.from(
|
||||||
|
{ length: 20 },
|
||||||
|
(_unused, peer) => (peer === index ? "" : `if (false) f${peer}()`),
|
||||||
|
).join(" ")} }`,
|
||||||
|
).join("\n");
|
||||||
|
const browser = generateTargets(
|
||||||
|
parse(`component ClientSizeStress {
|
||||||
|
state { ${states} }
|
||||||
|
functions { ${functions} }
|
||||||
|
view { <button></button> }
|
||||||
|
}`),
|
||||||
|
).browser;
|
||||||
|
const decoded = new TextEncoder().encode(browser).length;
|
||||||
|
const compressed = gzipSync(new TextEncoder().encode(browser)).length;
|
||||||
|
|
||||||
|
expect(decoded).toBeLessThanOrEqual(13_000);
|
||||||
|
expect(decoded / compressed).toBeLessThanOrEqual(10);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user