feat: replace the ssr/client data blocks with apis blocks
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -378,14 +378,13 @@ function clientCalledApiNames(ast: PageAst): Set<string> {
|
||||
* never does either.
|
||||
*/
|
||||
function assertNoDynamicApiAccess(ast: PageAst): void {
|
||||
// Only pages with a mode "any" block have anything at stake here: those
|
||||
// Only pages with a sectioned api block have anything at stake here: those
|
||||
// blocks are emitted solely because usage detection saw `api.<name>`, so a
|
||||
// dynamic reference this scan can't see is the one that silently drops a
|
||||
// block from the bundle. Mode "client" blocks always ship regardless of
|
||||
// usage, and a page with no api blocks at all may still declare an
|
||||
// ordinary `state api` (see the B5 regression test) where a bare "api"
|
||||
// block from the bundle. A page with no api blocks at all may still declare
|
||||
// an ordinary `state api` (see the B5 regression test) where a bare "api"
|
||||
// identifier is just that state, not a missed block reference.
|
||||
if (!ast.dataApis.some((block) => block.mode === "any" && block.sections)) return;
|
||||
if (!ast.dataApis.some((block) => block.sections)) return;
|
||||
|
||||
for (const fn of ast.runtimeFunctions.filter((fn) => ["client", "shared"].includes(fn.runtime))) {
|
||||
const masked = maskStringsAndComments(fn.body);
|
||||
@@ -403,16 +402,13 @@ function assertNoDynamicApiAccess(ast: PageAst): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* A block is emitted into the browser module when it is authored as
|
||||
* client-only, or when it is mode "any" and a client function actually calls
|
||||
* it. `hasClientApi` below must use this exact predicate so the `api`
|
||||
* reserved-binding exclusion and the emitted object can never disagree.
|
||||
* A block is emitted into the browser module when it declares typed sections
|
||||
* and a client function actually calls it. `hasClientApi` below must use this
|
||||
* exact predicate so the `api` reserved-binding exclusion and the emitted
|
||||
* object can never disagree.
|
||||
*/
|
||||
function isClientEmittedApiBlock(block: PageAst["dataApis"][number], called: Set<string>): boolean {
|
||||
return (
|
||||
Boolean(block.sections) &&
|
||||
(block.mode === "client" || (block.mode === "any" && called.has(block.name)))
|
||||
);
|
||||
return Boolean(block.sections) && called.has(block.name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -567,29 +567,6 @@ function compileIfExpr(node: IfNode): string {
|
||||
return "${" + expr + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect every server-control expression in a view (recursively): `{#each}` list
|
||||
* expressions and `{#if}` conditions. Used to wrn up raw SSR data consts.
|
||||
*/
|
||||
function collectControlExprs(nodes: ViewNode[], out: string[] = []): string[] {
|
||||
for (const node of nodes) {
|
||||
if (node.type === "text") continue;
|
||||
if (node.type === "each") {
|
||||
out.push(node.list);
|
||||
collectControlExprs(node.body, out);
|
||||
collectControlExprs(node.empty, out);
|
||||
} else if (node.type === "if") {
|
||||
for (const b of node.branches) {
|
||||
if (b.cond) out.push(b.cond);
|
||||
collectControlExprs(b.body, out);
|
||||
}
|
||||
} else if (node.type === "element") {
|
||||
collectControlExprs(node.children, out);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function renderNode(
|
||||
node: ViewNode,
|
||||
ssrBindings: SsrBinding[],
|
||||
@@ -741,39 +718,32 @@ function renderNode(
|
||||
const csrText = attrValue(node.attrs, "csrText");
|
||||
|
||||
const csrId =
|
||||
apiBinding?.mode === "client"
|
||||
? csrMarker(csrBindings, renderBinding(apiBinding))
|
||||
: csrGet && csrText
|
||||
? csrMarker(csrBindings, {
|
||||
method: "GET",
|
||||
path: apiRoutePath(csrGet),
|
||||
body: expressionBody(csrText),
|
||||
helpers: "",
|
||||
})
|
||||
: undefined;
|
||||
csrGet && csrText
|
||||
? csrMarker(csrBindings, {
|
||||
method: "GET",
|
||||
path: apiRoutePath(csrGet),
|
||||
body: expressionBody(csrText),
|
||||
helpers: "",
|
||||
})
|
||||
: undefined;
|
||||
|
||||
// Void elements (<br>, <img>, …) have no closing tag and no children.
|
||||
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
|
||||
return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive, loops)}>`;
|
||||
}
|
||||
|
||||
const inner =
|
||||
apiBinding?.mode === "ssr"
|
||||
? ssrMarker(ssrBindings, renderBinding(apiBinding))
|
||||
: apiBinding?.mode === "any"
|
||||
? apiCallMarker(loops, parsedApi!.name, parsedApi!.args)
|
||||
: ssrGet && ssrText
|
||||
? ssrMarker(ssrBindings, {
|
||||
method: "GET",
|
||||
path: apiRoutePath(ssrGet),
|
||||
body: expressionBody(ssrText),
|
||||
helpers: "",
|
||||
})
|
||||
: node.children
|
||||
.map((child) =>
|
||||
renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive),
|
||||
)
|
||||
.join("");
|
||||
const inner = apiBinding
|
||||
? apiCallMarker(loops, parsedApi!.name, parsedApi!.args)
|
||||
: ssrGet && ssrText
|
||||
? ssrMarker(ssrBindings, {
|
||||
method: "GET",
|
||||
path: apiRoutePath(ssrGet),
|
||||
body: expressionBody(ssrText),
|
||||
helpers: "",
|
||||
})
|
||||
: node.children
|
||||
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
|
||||
.join("");
|
||||
|
||||
return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive, loops)}>${inner}</${node.tag}>`;
|
||||
}
|
||||
@@ -912,16 +882,6 @@ function csrMarker(bindings: CsrBinding[], binding: RenderBinding): string {
|
||||
return id;
|
||||
}
|
||||
|
||||
function renderBinding(binding: NamedDataBinding): RenderBinding {
|
||||
return {
|
||||
method: binding.method,
|
||||
path: binding.path,
|
||||
body: binding.body,
|
||||
helpers: binding.helpers,
|
||||
...(binding.errorBody ? { errorBody: binding.errorBody } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function hasClientBehavior(nodes: ViewNode[]): boolean {
|
||||
return nodes.some((node) => {
|
||||
// `{t:key}` is i18n sugar resolved server-side — not client reactivity.
|
||||
@@ -963,18 +923,6 @@ function dataBody(source: string): string {
|
||||
return /\breturn\b/.test(trimmed) ? trimmed : expressionBody(trimmed);
|
||||
}
|
||||
|
||||
function modeHelpers(ast: PageAst, mode: DataMode, sharedHelpers: string): string {
|
||||
return [
|
||||
sharedHelpers,
|
||||
...ast.modeFunctions
|
||||
.filter((block) => block.mode === mode)
|
||||
.map((block) => block.body.trim())
|
||||
.filter(Boolean),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n");
|
||||
}
|
||||
|
||||
function apiBindingMap(ast: PageAst, sharedHelpers: string): Map<string, NamedDataBinding> {
|
||||
const bindings = new Map<string, NamedDataBinding>();
|
||||
|
||||
@@ -997,7 +945,7 @@ function apiBindingMap(ast: PageAst, sharedHelpers: string): Map<string, NamedDa
|
||||
// legacy blocks and sectioned blocks without `error` keep failures
|
||||
// propagating exactly as before.
|
||||
...(errorSection ? { errorBody: errorSection } : {}),
|
||||
helpers: modeHelpers(ast, block.mode, sharedHelpers),
|
||||
helpers: sharedHelpers,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1664,22 +1612,7 @@ function generateInner(ast: PageAst): string {
|
||||
}
|
||||
}
|
||||
|
||||
// Server loops iterate raw SSR data. Declare a named const for every `ssr` data
|
||||
// binding a loop references, so `{#each <name> as …}` can iterate the real value.
|
||||
const loopConsts: string[] = [];
|
||||
if (loops.length > 0) {
|
||||
const lists = collectControlExprs(ast.view);
|
||||
for (const [name, binding] of apiBindings) {
|
||||
if (binding.mode !== "ssr") continue;
|
||||
if (!lists.some((expr) => new RegExp(`\\b${name}\\b`).test(expr))) continue;
|
||||
const errorBodyProp = binding.errorBody
|
||||
? `, errorBody: ${JSON.stringify(binding.errorBody)}`
|
||||
: "";
|
||||
loopConsts.push(
|
||||
` const ${name} = await __wrnexusResolveApiBinding({ path: ${JSON.stringify(binding.path)}, method: ${JSON.stringify(binding.method)}, body: ${JSON.stringify(binding.body)}, helpers: ${JSON.stringify(binding.helpers)}${errorBodyProp} }, ctx);`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const needsSsrRuntime = ssrBindings.length > 0 || loops.length > 0 || runtimeStateNames.size > 0;
|
||||
const needsRuntimeHelpers = needsSsrRuntime || hasServerApis;
|
||||
|
||||
Reference in New Issue
Block a user