release: WRNexusJS 0.5.0

This commit is contained in:
2026-07-29 12:51:10 +05:30
parent 76c768099d
commit 6afe32f63f
456 changed files with 40879 additions and 8850 deletions
+29 -13
View File
@@ -1013,6 +1013,8 @@ const ${name} = async (ctx: any) => {${api.body}};`);
interface CompCtx {
/** State names — text referencing any of them stays a reactive client mustache. */
stateNames: Set<string>;
/** Component functions can read state and therefore make their callers reactive. */
functionNames: Set<string>;
/** Rewrite reserved-word prop/state identifiers to their safe const names. */
resolveExpr: (expr: string) => string;
/** `data-for` loop variables in scope — their mustaches stay literal for the
@@ -1177,6 +1179,10 @@ function exprRefsState(expr: string, stateNames: Set<string>): boolean {
return false;
}
function exprRefsComponentReactiveValue(expr: string, ctx: CompCtx): boolean {
return exprRefsState(expr, ctx.stateNames) || exprRefsState(expr, ctx.functionNames);
}
function viewHasEvents(nodes: ViewNode[]): boolean {
return nodes.some((node) => {
if (node.type === "text") return false;
@@ -1244,7 +1250,7 @@ function compileText(raw: string, ctx: CompCtx): string {
// Loop variable (from data-for): leave a literal client mustache — the
// list renderer fills it per item; it has no server-side value.
out += escLit(`{${expr}}`);
} else if (exprRefsState(expr, ctx.stateNames)) {
} else if (exprRefsComponentReactiveValue(expr, ctx)) {
// State interpolation: bake the initial value AND keep it reactive via a
// data-text span, so no-JS clients see the real value and hydration
// updates it in place. `count` → `<span data-text="count">0</span>`.
@@ -1450,7 +1456,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
if (isHtmlBooleanAttribute(a.name)) {
const expression = wholeAttributeExpression(a.value);
if (expression) {
const referencesState = exprRefsState(a.value, ctx.stateNames);
const referencesState = exprRefsComponentReactiveValue(a.value, ctx);
const referencesLoopVariable = elementContext.loopVars
? exprRefsState(a.value, elementContext.loopVars)
: false;
@@ -1472,7 +1478,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
const rendered = ` ${a.name}="${compileAttrValue(a.value, elementContext)}"`;
const referencesState = exprRefsState(a.value, ctx.stateNames);
const referencesState = exprRefsComponentReactiveValue(a.value, ctx);
const referencesLoopVariable = elementContext.loopVars
? exprRefsState(a.value, elementContext.loopVars)
@@ -1513,7 +1519,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
const staticClassValue = staticClasses.join(" ");
const classReferencesState = exprRefsState(staticClassValue, ctx.stateNames);
const classReferencesState = exprRefsComponentReactiveValue(staticClassValue, ctx);
const classReferencesLoopVariable = elementContext.loopVars
? exprRefsState(staticClassValue, elementContext.loopVars)
: false;
@@ -1569,10 +1575,6 @@ function generateComponent(ast: PageAst): string {
if (ast.imports.length > 0) out.push(ast.imports.join("\n"));
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")
? [
@@ -1608,6 +1610,14 @@ function generateComponent(ast: PageAst): string {
};
const ctx: CompCtx = {
stateNames,
functionNames: new Set(
ast.functions.flatMap((body) => {
return Array.from(
body.matchAll(/(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/g),
(match) => match[1]!,
);
}),
),
resolveExpr,
eventNames: ast.events.map((event) => event.name),
};
@@ -1653,6 +1663,10 @@ function generateComponent(ast: PageAst): string {
viewHasEvents(ast.view) ||
behavior !== null);
if (hasServerEach || needsScope) {
out.push(`import { Buffer as __WrnexusBuffer } from "node:buffer";`);
}
const scopeKeys = [
...effectiveProps.map((prop) => prop.name),
...ast.states.map((state) => state.name),
@@ -1690,18 +1704,20 @@ function generateComponent(ast: PageAst): string {
const returnExpr = needsScope
? "`" +
styleTag +
`<div data-scope="\${__scope}"${behaviorAttr}${hydrationAttribute(ast)}>` +
`<div data-scope="\${__scope}" data-wrn-scope="\${__scopePayload}"${behaviorAttr}${hydrationAttribute(ast)}>` +
viewCode +
"</div>`"
: "`" + styleTag + viewCode + "`";
const scopeLine =
needsScope && scopeKeys.length > 0
? ` const __scope = __wrnexusScopeDecl({ ${scopeKeys
? ` const __scopeState = { ${scopeKeys
.map((key) => `${JSON.stringify(key)}: ${nameRefs.get(key)}`)
.join(", ")} });\n`
.join(
", ",
)} };\n const __scope = __wrnexusScopeDecl(__scopeState);\n const __scopePayload = __WrnexusBuffer.from(JSON.stringify(__scopeState), "utf8").toString("base64");\n`
: needsScope
? ` const __scope = "";\n`
? ` const __scopeState = {};\n const __scope = "";\n const __scopePayload = __WrnexusBuffer.from("{}", "utf8").toString("base64");\n`
: "";
if (ast.kind === "layout") {
@@ -1900,7 +1916,7 @@ function __wireRaw(v: any): string {
if (hasServerEach) {
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, any>): string {
return Buffer.from(JSON.stringify(value), "utf8").toString("base64");
return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
}`);
}