fix(compiler): preserve server-only loader text and control blocks
This commit is contained in:
1 parent
aec1dc6056
commit
bfa72d5679
4 files changed
+36
-6
No files matched your search
@@ -320,7 +320,7 @@
|
|||||||
},
|
},
|
||||||
"packages/compiler": {
|
"packages/compiler": {
|
||||||
"name": "@wrnexus/compiler",
|
"name": "@wrnexus/compiler",
|
||||||
"version": "0.8.23",
|
"version": "0.8.24",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/csr": "workspace:*",
|
"@wrnexus/csr": "workspace:*",
|
||||||
"@wrnexus/store": "workspace:*",
|
"@wrnexus/store": "workspace:*",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/compiler",
|
"name": "@wrnexus/compiler",
|
||||||
"version": "0.8.23",
|
"version": "0.8.24",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -417,15 +417,17 @@ function substituteReactiveText(
|
|||||||
dynamicExpressions?: string[],
|
dynamicExpressions?: string[],
|
||||||
): string {
|
): string {
|
||||||
const text = substituteTMarkers(raw);
|
const text = substituteTMarkers(raw);
|
||||||
if (!reactive || reactive.stateNames.size === 0) return text;
|
if (!reactive) return text;
|
||||||
return text.replace(/\{([^{}]+)\}/g, (whole, inner: string) => {
|
return text.replace(/\{([^{}]+)\}/g, (whole, inner: string) => {
|
||||||
const expr = inner.trim();
|
const expr = inner.trim();
|
||||||
if (expr.startsWith("t:") || !exprRefsState(expr, reactive.stateNames)) return whole;
|
const clientReactive = exprRefsState(expr, reactive.stateNames);
|
||||||
|
if (expr.startsWith("t:")) return whole;
|
||||||
if (exprRefsState(expr, reactive.runtimeStateNames) && dynamicExpressions) {
|
if (exprRefsState(expr, reactive.runtimeStateNames) && dynamicExpressions) {
|
||||||
dynamicExpressions.push(`\${__wrnexusEscapeHtml(${expr})}`);
|
dynamicExpressions.push(`\${__wrnexusEscapeHtml(${expr})}`);
|
||||||
const sentinel = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
|
const sentinel = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
|
||||||
return `<span data-text="${attrEscape(expr)}">${sentinel}</span>`;
|
return clientReactive ? `<span data-text="${attrEscape(expr)}">${sentinel}</span>` : sentinel;
|
||||||
}
|
}
|
||||||
|
if (!clientReactive) return whole;
|
||||||
let value: unknown;
|
let value: unknown;
|
||||||
try {
|
try {
|
||||||
value = new Function("with(this){return (" + expr + ");}").call(reactive.scope);
|
value = new Function("with(this){return (" + expr + ");}").call(reactive.scope);
|
||||||
@@ -660,6 +662,19 @@ function renderNode(
|
|||||||
// templateEscape, swapped for its real `${…}` code after escaping.
|
// templateEscape, swapped for its real `${…}` code after escaping.
|
||||||
if (node.type === "each" || node.type === "if") {
|
if (node.type === "each" || node.type === "if") {
|
||||||
loops.push(node.type === "each" ? compileEachExpr(node) : compileIfExpr(node));
|
loops.push(node.type === "each" ? compileEachExpr(node) : compileIfExpr(node));
|
||||||
|
const conditions =
|
||||||
|
node.type === "each"
|
||||||
|
? [node.list]
|
||||||
|
: node.branches.flatMap((branch) => (branch.cond ? [branch.cond] : []));
|
||||||
|
// Server-only loader values are not serialized into browser state. Their
|
||||||
|
// controls must not be re-evaluated by a surrounding component's hydration.
|
||||||
|
if (
|
||||||
|
reactive &&
|
||||||
|
conditions.some((condition) => exprRefsState(condition, reactive.runtimeStateNames)) &&
|
||||||
|
!conditions.some((condition) => exprRefsState(condition, reactive.stateNames))
|
||||||
|
) {
|
||||||
|
return `\x00WRNEACH${loops.length - 1}\x00`;
|
||||||
|
}
|
||||||
const definition =
|
const definition =
|
||||||
node.type === "each"
|
node.type === "each"
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ test("server-only loader data renders native input attributes without client sta
|
|||||||
parse(`page Profile {
|
parse(`page Profile {
|
||||||
load server account { return {} }
|
load server account { return {} }
|
||||||
view {
|
view {
|
||||||
|
<h1>{account.name}</h1>
|
||||||
|
{#if account.readonly}<p>Read only</p>{:else}<p>Editable</p>{/if}
|
||||||
|
{#each account.items as item}<strong>{item}</strong>{/each}
|
||||||
<input value="{account.name}" readonly="{account.readonly}" />
|
<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>
|
<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>
|
<div data-component="ProfileShell" identity="{account.identity}"></div>
|
||||||
@@ -17,7 +20,13 @@ test("server-only loader data renders native input attributes without client sta
|
|||||||
`data:text/javascript;base64,${Buffer.from(javascript).toString("base64")}`
|
`data:text/javascript;base64,${Buffer.from(javascript).toString("base64")}`
|
||||||
);
|
);
|
||||||
const html = await page.default({
|
const html = await page.default({
|
||||||
account: { name: 'A "<User>"', readonly: false, theme: "dark", identity: { name: "Alice" } },
|
account: {
|
||||||
|
items: ["Example record"],
|
||||||
|
name: 'A "<User>"',
|
||||||
|
readonly: false,
|
||||||
|
theme: "dark",
|
||||||
|
identity: { name: "Alice" },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(html).toContain('value="A "<User>""');
|
expect(html).toContain('value="A "<User>""');
|
||||||
expect(html).not.toContain(" readonly");
|
expect(html).not.toContain(" readonly");
|
||||||
@@ -26,4 +35,10 @@ test("server-only loader data renders native input attributes without client sta
|
|||||||
expect(html).toContain('identity="{"name":"Alice"}"');
|
expect(html).toContain('identity="{"name":"Alice"}"');
|
||||||
expect(html).not.toContain("data-wrn-bind");
|
expect(html).not.toContain("data-wrn-bind");
|
||||||
expect(html).not.toContain("{account.");
|
expect(html).not.toContain("{account.");
|
||||||
|
expect(html).toContain("<h1>A "<User>"</h1>");
|
||||||
|
expect(html).toContain("<p>Editable</p>");
|
||||||
|
expect(html).toContain("<strong>Example record</strong>");
|
||||||
|
expect(html).not.toContain("data-wrn-if");
|
||||||
|
expect(html).not.toContain("data-wrn-each");
|
||||||
|
expect(html).not.toContain("data-text");
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user