release: WRNexusJS 0.2.27

This commit is contained in:
2026-07-14 14:40:13 +05:30
parent fef4218a07
commit d3094010af
64 changed files with 763 additions and 244 deletions
+122 -9
View File
@@ -36,6 +36,10 @@ interface NamedDataBinding extends RenderBinding {
mode: DataMode;
}
function isComponentTag(tag: string): boolean {
return /^[A-Z][A-Za-z0-9_$]*$/.test(tag);
}
/** Escape a value placed inside a double-quoted HTML attribute. */
function attrEscape(value: string): string {
return value
@@ -217,19 +221,49 @@ function bakeLoopAttr(raw: string): string {
/** Render one loop-body node to template-literal source (nested loops inline). */
function renderLoopBody(node: ViewNode): string {
if (node.type === "text") return bakeLoopText(node.value);
if (node.type === "each") return compileEachExpr(node);
if (node.type === "if") return compileIfExpr(node);
if (node.type === "text") {
return bakeLoopText(node.value);
}
if (node.type === "each") {
return compileEachExpr(node);
}
if (node.type === "if") {
return compileIfExpr(node);
}
const componentTag = isComponentTag(node.tag);
const attrs = node.attrs
.map((a) => {
const name = a.event ? eventAttribute(a.name) : a.name;
if (a.boolean) return escLit(` ${name}`);
return escLit(` ${name}="`) + bakeLoopAttr(a.value) + escLit(`"`);
.filter((attr) => attr.name !== "data-component")
.map((attr) => {
const name = attr.event ? eventAttribute(attr.name) : attr.name;
if (attr.boolean) {
return escLit(` ${name}`);
}
return escLit(` ${name}="`) + bakeLoopAttr(attr.value) + escLit(`"`);
})
.join("");
if (VOID_ELEMENTS.has(node.tag.toLowerCase()))
return escLit(`<${node.tag}`) + attrs + escLit(">");
const inner = node.children.map(renderLoopBody).join("");
if (componentTag) {
return (
escLit(`<div data-component="${attrEscape(node.tag)}"`) +
attrs +
escLit(">") +
inner +
escLit("</div>")
);
}
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
return escLit(`<${node.tag}`) + attrs + escLit(">");
}
return escLit(`<${node.tag}`) + attrs + escLit(">") + inner + escLit(`</${node.tag}>`);
}
@@ -314,6 +348,17 @@ function renderNode(
return `\x00WRNEACH${loops.length - 1}\x00`;
}
if (isComponentTag(node.tag)) {
return renderPageComponentInvocation(
node,
ssrBindings,
csrBindings,
apiBindings,
loops,
reactive,
);
}
const apiName = attrValue(node.attrs, "api");
const apiBinding = apiName ? apiBindings.get(apiName) : undefined;
if (apiName && !apiBinding) {
@@ -361,6 +406,71 @@ function renderNode(
return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive)}>${inner}</${node.tag}>`;
}
function renderPageComponentInvocation(
node: Extract<ViewNode, { type: "element" }>,
ssrBindings: SsrBinding[],
csrBindings: CsrBinding[],
apiBindings: Map<string, NamedDataBinding>,
loops: string[],
reactive: PageReactive | null,
): string {
const attrs = node.attrs.filter((attr) => attr.name !== "data-component");
const inner = node.children
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
.join("");
return `<div data-component="${attrEscape(node.tag)}"${renderAttrs(
attrs,
undefined,
reactive,
)}>${inner}</div>`;
}
function renderNestedComponentInvocation(
node: Extract<ViewNode, { type: "element" }>,
ctx: CompCtx,
): string {
let bindIndex = 0;
const attrs = node.attrs
.filter((attr) => attr.name !== "data-component")
.map((attr) => {
if (attr.event) {
return ` ${eventAttribute(attr.name)}="${compileAttrValue(attr.value, ctx)}"`;
}
if (attr.boolean) {
return ` ${attr.name}`;
}
const rendered = ` ${attr.name}="` + `${compileAttrValue(attr.value, ctx)}"`;
if (!attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames)) {
return rendered;
}
const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
return rendered + ` data-wrn-bind-${bindIndex++}="${escLit(marker)}"`;
})
.join("");
const loops = loopVarsOf(node);
const childCtx =
loops.length > 0
? {
...ctx,
loopVars: new Set([...(ctx.loopVars ?? []), ...loops]),
}
: ctx;
const inner = node.children.map((child) => renderComponentNode(child, childCtx)).join("");
return `<div data-component="${attrEscape(node.tag)}"${attrs}>${inner}</div>`;
}
function ssrMarker(bindings: SsrBinding[], binding: RenderBinding): string {
const marker = `<!--wrnexus-ssr:${bindings.length}-->`;
bindings.push({ marker, ...binding });
@@ -829,6 +939,9 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
"Server `{#each}` / `{#if}` blocks are supported in pages, not components. Move them into a page (or use data-for / data-show on the client).",
);
}
if (isComponentTag(node.tag)) {
return renderNestedComponentInvocation(node, ctx);
}
let bindIndex = 0;
const staticClasses: string[] = [];