release: WRNexusJS 0.2.36
This commit is contained in:
@@ -15,6 +15,8 @@
|
||||
* realtime {..} -> export const websocket = { evt(ws, ...args) { b } }
|
||||
*/
|
||||
|
||||
import { Buffer } from "node:buffer";
|
||||
|
||||
import { VOID_ELEMENTS, type Attr, type DataMode, type PageAst, type ViewNode } from "./parser.ts";
|
||||
|
||||
interface RenderBinding {
|
||||
@@ -804,6 +806,8 @@ interface CompCtx {
|
||||
/** `data-for` loop variables in scope — their mustaches stay literal for the
|
||||
* client's list renderer (never baked server-side, since they have no value). */
|
||||
loopVars?: Set<string>;
|
||||
/** Local identifiers introduced by server-rendered `{#each}` blocks. */
|
||||
serverLocals?: Set<string>;
|
||||
}
|
||||
|
||||
interface ComponentBehavior {
|
||||
@@ -940,9 +944,19 @@ function exprRefsState(expr: string, stateNames: Set<string>): boolean {
|
||||
}
|
||||
|
||||
function viewHasEvents(nodes: ViewNode[]): boolean {
|
||||
return nodes.some(
|
||||
(n) => n.type === "element" && (n.attrs.some((a) => a.event) || viewHasEvents(n.children)),
|
||||
);
|
||||
return nodes.some((node) => {
|
||||
if (node.type === "text") return false;
|
||||
|
||||
if (node.type === "each") {
|
||||
return viewHasEvents(node.body) || viewHasEvents(node.empty);
|
||||
}
|
||||
|
||||
if (node.type === "if") {
|
||||
return node.branches.some((branch) => viewHasEvents(branch.body));
|
||||
}
|
||||
|
||||
return node.attrs.some((attr) => attr.event) || viewHasEvents(node.children);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1004,14 +1018,65 @@ function compileAttrValue(raw: string, ctx: CompCtx): string {
|
||||
return out + escLit(attrEscape(raw.slice(last)));
|
||||
}
|
||||
|
||||
function renderComponentIfNode(node: IfNode, ctx: CompCtx): string {
|
||||
let expression = "``";
|
||||
|
||||
for (let index = node.branches.length - 1; index >= 0; index--) {
|
||||
const branch = node.branches[index]!;
|
||||
const body = branch.body.map((child) => renderComponentNode(child, ctx)).join("");
|
||||
const bodyExpression = "`" + body + "`";
|
||||
|
||||
expression =
|
||||
branch.cond === null
|
||||
? bodyExpression
|
||||
: `(${ctx.resolveExpr(branch.cond)}) ? ${bodyExpression} : ${expression}`;
|
||||
}
|
||||
|
||||
return "${" + expression + "}";
|
||||
}
|
||||
|
||||
function renderComponentEachNode(node: EachNode, ctx: CompCtx): string {
|
||||
const item = node.item;
|
||||
const index = node.index ?? "__wi";
|
||||
const list = ctx.resolveExpr(node.list);
|
||||
|
||||
const childCtx: CompCtx = {
|
||||
...ctx,
|
||||
serverLocals: new Set([...(ctx.serverLocals ?? []), item, index]),
|
||||
};
|
||||
|
||||
const body = node.body.map((child) => renderComponentNode(child, childCtx)).join("");
|
||||
const empty = node.empty.map((child) => renderComponentNode(child, ctx)).join("");
|
||||
|
||||
return (
|
||||
"${(() => { const __wl = Array.isArray(" +
|
||||
list +
|
||||
") ? (" +
|
||||
list +
|
||||
") : []; return __wl.length ? __wl.map((" +
|
||||
item +
|
||||
", " +
|
||||
index +
|
||||
") => `" +
|
||||
body +
|
||||
'`).join("") : `' +
|
||||
empty +
|
||||
"`; })()}"
|
||||
);
|
||||
}
|
||||
|
||||
/** Render a component view node into template-literal-ready source. */
|
||||
function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
if (node.type === "text") return compileText(node.value, ctx);
|
||||
if (node.type === "each" || node.type === "if") {
|
||||
throw new Error(
|
||||
"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 (node.type === "each") {
|
||||
return renderComponentEachNode(node, ctx);
|
||||
}
|
||||
|
||||
if (node.type === "if") {
|
||||
return renderComponentIfNode(node, ctx);
|
||||
}
|
||||
|
||||
if (isComponentTag(node.tag)) {
|
||||
return renderNestedComponentInvocation(node, ctx);
|
||||
}
|
||||
@@ -1080,6 +1145,9 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
: "";
|
||||
|
||||
const classBindings = conditionalClasses
|
||||
.filter(({ expression }) => {
|
||||
return !ctx.serverLocals || !exprRefsState(expression, ctx.serverLocals);
|
||||
})
|
||||
.map(({ className, expression }, index) => {
|
||||
const marker = attrEscape(JSON.stringify([className, expression]));
|
||||
|
||||
@@ -1200,6 +1268,9 @@ function __wireAttr(v: any): string {
|
||||
return String(v == null ? "" : v).replace(/[&<>"]/g, (c) =>
|
||||
c === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : """,
|
||||
);
|
||||
}
|
||||
function __wireRaw(v: any): string {
|
||||
return String(v == null ? "" : v);
|
||||
}`);
|
||||
|
||||
if (needsScope) {
|
||||
|
||||
Reference in New Issue
Block a user