fix(compiler): render server loader attributes without client state

This commit is contained in:
Clintchiz committed 2026-08-26 12:19:07 +05:30
1 parent 6acf78a72c
commit aec1dc6056
4 files changed
+40 -5

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/compiler",
"version": "0.8.22",
"version": "0.8.23",
"type": "module",
"main": "src/index.ts",
"exports": {
+9 -3
View File
@@ -344,10 +344,16 @@ function renderAttrs(
exprRefsState(expression, reactive.runtimeStateNames) &&
dynamicExpressions
) {
const marker = exprRefsState(expression, reactive.stateNames)
? ` data-wrn-bind-${bindIndex++}="${attrEscape(JSON.stringify([attr.name, attr.value]))}"`
: "";
if (isHtmlBooleanAttribute(attr.name)) {
dynamicExpressions.push(`\${(${expression}) ? ${JSON.stringify(` ${attr.name}`)} : ""}`);
return `\x00WRNEACH${dynamicExpressions.length - 1}\x00${marker}`;
}
dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`);
const sentinel = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
const marker = JSON.stringify([attr.name, attr.value]);
return ` ${attr.name}="${sentinel}" data-wrn-bind-${bindIndex++}="${attrEscape(marker)}"`;
return ` ${attr.name}="${sentinel}"${marker}`;
}
const initial = reactiveAttrValue(attr.value, reactive);
if (initial === null) return base;
@@ -1603,7 +1609,7 @@ function generateInner(ast: PageAst): string {
];
const runtimeStateNames = runtimeComputedNames(orderedStates, orderedComputed, runtimeRoots);
const reactive: PageReactive | null =
reactiveNames.length > 0
reactiveNames.length > 0 || runtimeStateNames.size > 0
? { stateNames: new Set(reactiveNames), runtimeStateNames, scope: seedScope }
: null;
const loops: string[] = [];
@@ -0,0 +1,29 @@
import { expect, test } from "bun:test";
import { generate, parse } from "../src/index.ts";
test("server-only loader data renders native input attributes without client state", async () => {
const code = generate(
parse(`page Profile {
load server account { return {} }
view {
<input value="{account.name}" readonly="{account.readonly}" />
<select><option value="light" selected="{account.theme === 'light'}">Light</option><option value="dark" selected="{account.theme === 'dark'}">Dark</option></select>
<div data-component="ProfileShell" identity="{account.identity}"></div>
}
}`),
);
const javascript = new Bun.Transpiler({ loader: "ts" }).transformSync(code);
const page = await import(
`data:text/javascript;base64,${Buffer.from(javascript).toString("base64")}`
);
const html = await page.default({
account: { name: 'A "<User>"', readonly: false, theme: "dark", identity: { name: "Alice" } },
});
expect(html).toContain('value="A &quot;&lt;User&gt;&quot;"');
expect(html).not.toContain(" readonly");
expect(html).toContain('<option value="light">Light</option>');
expect(html).toContain('<option value="dark" selected>Dark</option>');
expect(html).toContain('identity="{&quot;name&quot;:&quot;Alice&quot;}"');
expect(html).not.toContain("data-wrn-bind");
expect(html).not.toContain("{account.");
});