release: WRNexusJS 0.2.47
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.2.46",
|
||||
"version": "0.2.47",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -996,6 +996,20 @@ function viewHasEvents(nodes: ViewNode[]): boolean {
|
||||
});
|
||||
}
|
||||
|
||||
function viewHasServerEach(nodes: ViewNode[]): boolean {
|
||||
return nodes.some((node) => {
|
||||
if (node.type === "text") return false;
|
||||
|
||||
if (node.type === "each") return true;
|
||||
|
||||
if (node.type === "if") {
|
||||
return node.branches.some((branch) => viewHasServerEach(branch.body));
|
||||
}
|
||||
|
||||
return viewHasServerEach(node.children);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a text node. Interpolations that reference state stay as client
|
||||
* mustaches (`{expr}`, hydrated by the reactive runtime); interpolations of
|
||||
@@ -1102,6 +1116,18 @@ function renderComponentEachNode(node: EachNode, ctx: CompCtx): string {
|
||||
);
|
||||
}
|
||||
|
||||
function serverLoopLocalsAttribute(ctx: CompCtx): string {
|
||||
const locals = [...(ctx.serverLocals ?? [])];
|
||||
|
||||
if (locals.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const entries = locals.map((name) => `${JSON.stringify(name)}: ${name}`).join(", ");
|
||||
|
||||
return ` data-wrn-loop-locals="\${__wrnexusEncodeLoopLocals({ ${entries} })}"`;
|
||||
}
|
||||
|
||||
/** 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);
|
||||
@@ -1167,7 +1193,14 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
? exprRefsState(a.value, elementContext.loopVars)
|
||||
: false;
|
||||
|
||||
if (!a.value.includes("{") || (!referencesState && !referencesLoopVariable)) {
|
||||
const referencesServerLocal = ctx.serverLocals
|
||||
? exprRefsState(a.value, ctx.serverLocals)
|
||||
: false;
|
||||
|
||||
if (
|
||||
!a.value.includes("{") ||
|
||||
(!referencesState && !referencesLoopVariable && !referencesServerLocal)
|
||||
) {
|
||||
return rendered;
|
||||
}
|
||||
|
||||
@@ -1195,8 +1228,17 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
|
||||
const staticClassValue = staticClasses.join(" ");
|
||||
|
||||
const classReferencesState = exprRefsState(staticClassValue, ctx.stateNames);
|
||||
const classReferencesLoopVariable = elementContext.loopVars
|
||||
? exprRefsState(staticClassValue, elementContext.loopVars)
|
||||
: false;
|
||||
const classReferencesServerLocal = ctx.serverLocals
|
||||
? exprRefsState(staticClassValue, ctx.serverLocals)
|
||||
: false;
|
||||
|
||||
const classHasReactiveExpression =
|
||||
staticClassValue.includes("{") && exprRefsState(staticClassValue, ctx.stateNames);
|
||||
staticClassValue.includes("{") &&
|
||||
(classReferencesState || classReferencesLoopVariable || classReferencesServerLocal);
|
||||
|
||||
const classAttribute =
|
||||
staticClasses.length > 0 || conditionalClasses.length > 0
|
||||
@@ -1208,9 +1250,6 @@ 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]));
|
||||
|
||||
@@ -1218,8 +1257,14 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
})
|
||||
.join("");
|
||||
|
||||
const loopLocalsAttribute = serverLoopLocalsAttribute(ctx);
|
||||
|
||||
const allAttrs =
|
||||
`${classAttribute}` + `${classReactiveBinding}` + `${classBindings}` + `${attrs}`;
|
||||
`${loopLocalsAttribute}` +
|
||||
`${classAttribute}` +
|
||||
`${classReactiveBinding}` +
|
||||
`${classBindings}` +
|
||||
`${attrs}`;
|
||||
|
||||
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
|
||||
return `<${node.tag}${allAttrs}>`;
|
||||
@@ -1232,6 +1277,11 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
|
||||
function generateComponent(ast: PageAst): string {
|
||||
const out: string[] = [];
|
||||
const hasServerEach = viewHasServerEach(ast.view);
|
||||
|
||||
if (hasServerEach) {
|
||||
out.push(`import { Buffer } from "node:buffer";`);
|
||||
}
|
||||
|
||||
const effectiveProps =
|
||||
ast.kind === "layout" && !ast.props.some((prop) => prop.name === "content")
|
||||
@@ -1420,6 +1470,12 @@ function __wireRaw(v: any): string {
|
||||
return String(v == null ? "" : v);
|
||||
}`);
|
||||
|
||||
if (hasServerEach) {
|
||||
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, any>): string {
|
||||
return Buffer.from(JSON.stringify(value), "utf8").toString("base64");
|
||||
}`);
|
||||
}
|
||||
|
||||
if (needsScope) {
|
||||
out.push(`function __wrnexusSerializeScopeValue(value: any): string {
|
||||
if (value === undefined) {
|
||||
|
||||
@@ -881,3 +881,68 @@ component Accordion {
|
||||
|
||||
expect(output).not.toContain('${(isOpen(index)) ? " open" : ""}');
|
||||
});
|
||||
test("server-rendered each locals work in reactive handlers", () => {
|
||||
const firstLocals = Buffer.from(
|
||||
JSON.stringify({
|
||||
item: {
|
||||
question: "First",
|
||||
},
|
||||
index: 0,
|
||||
}),
|
||||
"utf8",
|
||||
).toString("base64");
|
||||
|
||||
const secondLocals = Buffer.from(
|
||||
JSON.stringify({
|
||||
item: {
|
||||
question: "Second",
|
||||
},
|
||||
index: 1,
|
||||
}),
|
||||
"utf8",
|
||||
).toString("base64");
|
||||
|
||||
const dom = mountHtml(`
|
||||
<div
|
||||
data-scope="openIndex: -1"
|
||||
>
|
||||
<article
|
||||
data-wrn-loop-locals="${firstLocals}"
|
||||
>
|
||||
<button
|
||||
data-on-click="openIndex = index"
|
||||
>
|
||||
First
|
||||
</button>
|
||||
|
||||
<span
|
||||
data-wrn-class-0='["open","openIndex === index"]'
|
||||
></span>
|
||||
</article>
|
||||
|
||||
<article
|
||||
data-wrn-loop-locals="${secondLocals}"
|
||||
>
|
||||
<button
|
||||
data-on-click="openIndex = index"
|
||||
>
|
||||
Second
|
||||
</button>
|
||||
|
||||
<span
|
||||
data-wrn-class-0='["open","openIndex === index"]'
|
||||
></span>
|
||||
</article>
|
||||
</div>
|
||||
`);
|
||||
|
||||
const buttons: any = dom.querySelectorAll("button");
|
||||
|
||||
buttons[1].click();
|
||||
|
||||
const articles = dom.querySelectorAll("article");
|
||||
|
||||
expect(articles[0].querySelector("span")?.classList.contains("open")).toBe(false);
|
||||
|
||||
expect(articles[1].querySelector("span")?.classList.contains("open")).toBe(true);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user