From 8a8328a308478ccebfb0b429107e7e50a1c1bd07 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Wed, 15 Jul 2026 19:41:42 +0530 Subject: [PATCH] release: WRNexusJS 0.2.44 --- .publish/ai/package.json | 2 +- .publish/authz/package.json | 2 +- .publish/cli/package.json | 22 +- .publish/compiler/package.json | 2 +- .publish/core/package.json | 2 +- .publish/csr/package.json | 4 +- .publish/db/package.json | 2 +- .publish/dev-server/package.json | 26 +- .publish/encryption/package.json | 2 +- .publish/helpers/package.json | 4 +- .publish/i18n/package.json | 4 +- .publish/jwt/package.json | 2 +- .publish/mobile/package.json | 4 +- .publish/native/package.json | 2 +- .publish/oauth/package.json | 2 +- .publish/pubsub/package.json | 2 +- .publish/queue/package.json | 2 +- .publish/reactive/package.json | 2 +- .publish/router/package.json | 6 +- .publish/ssr/package.json | 4 +- .publish/styles/package.json | 4 +- .publish/test/package.json | 2 +- .publish/tracking/package.json | 2 +- .publish/ui/package.json | 4 +- .publish/uploader/package.json | 4 +- .publish/validation/package.json | 2 +- packages/ai/package.json | 2 +- packages/authz/package.json | 2 +- packages/cli/package.json | 2 +- packages/cli/src/update.ts | 9 + packages/compiler/package.json | 2 +- packages/compiler/src/codegen.ts | 47 ++- packages/compiler/test/compiler.test.ts | 34 ++ packages/core/package.json | 2 +- packages/csr/package.json | 2 +- packages/csr/src/reactive-runtime.ts | 531 +++++++++++++++++++++--- packages/db/package.json | 2 +- packages/dev-server/package.json | 2 +- packages/encryption/package.json | 2 +- packages/helpers/package.json | 2 +- packages/i18n/package.json | 2 +- packages/jwt/package.json | 2 +- packages/mobile/package.json | 2 +- packages/native/package.json | 2 +- packages/oauth/package.json | 2 +- packages/pubsub/package.json | 2 +- packages/queue/package.json | 2 +- packages/reactive/package.json | 2 +- packages/router/package.json | 2 +- packages/ssr/package.json | 2 +- packages/styles/package.json | 2 +- packages/test/package.json | 2 +- packages/tracking/package.json | 2 +- packages/ui/package.json | 2 +- packages/uploader/package.json | 2 +- packages/validation/package.json | 2 +- update-package-versions.mjs | 2 +- 57 files changed, 642 insertions(+), 149 deletions(-) diff --git a/.publish/ai/package.json b/.publish/ai/package.json index 06b78053..30d53aa0 100644 --- a/.publish/ai/package.json +++ b/.publish/ai/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ai", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "license": "MIT", diff --git a/.publish/authz/package.json b/.publish/authz/package.json index f2434602..a5be7595 100644 --- a/.publish/authz/package.json +++ b/.publish/authz/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/authz", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/authz — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/cli/package.json b/.publish/cli/package.json index a7d990dc..e38d70e8 100644 --- a/.publish/cli/package.json +++ b/.publish/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cli", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/cli — part of the WrNexus framework.", "license": "MIT", @@ -28,16 +28,16 @@ "wrnexus": "./dist/index.js" }, "dependencies": { - "@wrnexus/core": "^0.2.44", - "@wrnexus/router": "^0.2.44", - "@wrnexus/csr": "^0.2.44", - "@wrnexus/compiler": "^0.2.44", - "@wrnexus/styles": "^0.2.44", - "@wrnexus/dev-server": "^0.2.44", - "@wrnexus/ui": "^0.2.44", - "@wrnexus/validation": "^0.2.44", - "@wrnexus/i18n": "^0.2.44", - "@wrnexus/db": "^0.2.44" + "@wrnexus/core": "^0.2.45", + "@wrnexus/router": "^0.2.45", + "@wrnexus/csr": "^0.2.45", + "@wrnexus/compiler": "^0.2.45", + "@wrnexus/styles": "^0.2.45", + "@wrnexus/dev-server": "^0.2.45", + "@wrnexus/ui": "^0.2.45", + "@wrnexus/validation": "^0.2.45", + "@wrnexus/i18n": "^0.2.45", + "@wrnexus/db": "^0.2.45" }, "files": [ "dist" diff --git a/.publish/compiler/package.json b/.publish/compiler/package.json index cd7cfc57..ceb4a6dc 100644 --- a/.publish/compiler/package.json +++ b/.publish/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/compiler", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/compiler — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/core/package.json b/.publish/core/package.json index 07068c9c..da8cb0dc 100644 --- a/.publish/core/package.json +++ b/.publish/core/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/core", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/core — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/csr/package.json b/.publish/csr/package.json index afd3da02..a425af47 100644 --- a/.publish/csr/package.json +++ b/.publish/csr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/csr", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/csr — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.44" + "@wrnexus/core": "^0.2.45" }, "files": [ "dist" diff --git a/.publish/db/package.json b/.publish/db/package.json index 7890767a..c5b2be21 100644 --- a/.publish/db/package.json +++ b/.publish/db/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/db", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/db — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/dev-server/package.json b/.publish/dev-server/package.json index 39094898..88ab3dca 100644 --- a/.publish/dev-server/package.json +++ b/.publish/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-server", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/dev-server — part of the WrNexus framework.", "license": "MIT", @@ -25,18 +25,18 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.44", - "@wrnexus/router": "^0.2.44", - "@wrnexus/ssr": "^0.2.44", - "@wrnexus/csr": "^0.2.44", - "@wrnexus/compiler": "^0.2.44", - "@wrnexus/styles": "^0.2.44", - "@wrnexus/ui": "^0.2.44", - "@wrnexus/validation": "^0.2.44", - "@wrnexus/i18n": "^0.2.44", - "@wrnexus/db": "^0.2.44", - "@wrnexus/pubsub": "^0.2.44", - "@wrnexus/uploader": "^0.2.44" + "@wrnexus/core": "^0.2.45", + "@wrnexus/router": "^0.2.45", + "@wrnexus/ssr": "^0.2.45", + "@wrnexus/csr": "^0.2.45", + "@wrnexus/compiler": "^0.2.45", + "@wrnexus/styles": "^0.2.45", + "@wrnexus/ui": "^0.2.45", + "@wrnexus/validation": "^0.2.45", + "@wrnexus/i18n": "^0.2.45", + "@wrnexus/db": "^0.2.45", + "@wrnexus/pubsub": "^0.2.45", + "@wrnexus/uploader": "^0.2.45" }, "files": [ "dist" diff --git a/.publish/encryption/package.json b/.publish/encryption/package.json index 8af727a2..da0a8133 100644 --- a/.publish/encryption/package.json +++ b/.publish/encryption/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/encryption", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/encryption — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/helpers/package.json b/.publish/helpers/package.json index b3f84f70..da9da07f 100644 --- a/.publish/helpers/package.json +++ b/.publish/helpers/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/helpers", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.44" + "@wrnexus/core": "^0.2.45" }, "files": [ "dist" diff --git a/.publish/i18n/package.json b/.publish/i18n/package.json index 1c20950e..65785b3a 100644 --- a/.publish/i18n/package.json +++ b/.publish/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/i18n", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/i18n — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.44" + "@wrnexus/core": "^0.2.45" }, "files": [ "dist" diff --git a/.publish/jwt/package.json b/.publish/jwt/package.json index e992a33d..ce9aa3cc 100644 --- a/.publish/jwt/package.json +++ b/.publish/jwt/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/jwt", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/jwt — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/mobile/package.json b/.publish/mobile/package.json index 4d5f4036..c12e8ddf 100644 --- a/.publish/mobile/package.json +++ b/.publish/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/mobile", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/mobile — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/native": "^0.2.44" + "@wrnexus/native": "^0.2.45" }, "files": [ "dist" diff --git a/.publish/native/package.json b/.publish/native/package.json index cdaca091..896ed00c 100644 --- a/.publish/native/package.json +++ b/.publish/native/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/native", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/native — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/oauth/package.json b/.publish/oauth/package.json index 9d21778a..c0496ec4 100644 --- a/.publish/oauth/package.json +++ b/.publish/oauth/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/oauth", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/oauth — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/pubsub/package.json b/.publish/pubsub/package.json index 158ff274..7269163d 100644 --- a/.publish/pubsub/package.json +++ b/.publish/pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/pubsub", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/pubsub — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/queue/package.json b/.publish/queue/package.json index 02edb973..7cc6bf49 100644 --- a/.publish/queue/package.json +++ b/.publish/queue/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/queue", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/queue — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/reactive/package.json b/.publish/reactive/package.json index 741374c3..2d78c021 100644 --- a/.publish/reactive/package.json +++ b/.publish/reactive/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/reactive", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/reactive — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/router/package.json b/.publish/router/package.json index 1fdca5b0..4f52efd0 100644 --- a/.publish/router/package.json +++ b/.publish/router/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/router", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/router — part of the WrNexus framework.", "license": "MIT", @@ -21,8 +21,8 @@ } }, "dependencies": { - "@wrnexus/compiler": "^0.2.44", - "@wrnexus/core": "^0.2.44" + "@wrnexus/compiler": "^0.2.45", + "@wrnexus/core": "^0.2.45" }, "files": [ "dist" diff --git a/.publish/ssr/package.json b/.publish/ssr/package.json index 3f1f33c8..2b5d23d4 100644 --- a/.publish/ssr/package.json +++ b/.publish/ssr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ssr", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/ssr — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.44" + "@wrnexus/core": "^0.2.45" }, "files": [ "dist" diff --git a/.publish/styles/package.json b/.publish/styles/package.json index 8835aec5..272b3498 100644 --- a/.publish/styles/package.json +++ b/.publish/styles/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/styles", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/styles — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/uploader": "^0.2.44" + "@wrnexus/uploader": "^0.2.45" }, "files": [ "dist" diff --git a/.publish/test/package.json b/.publish/test/package.json index c917f03e..7858e971 100644 --- a/.publish/test/package.json +++ b/.publish/test/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/test", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/test — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/tracking/package.json b/.publish/tracking/package.json index b3bbcba3..b4d472e7 100644 --- a/.publish/tracking/package.json +++ b/.publish/tracking/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/tracking", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/tracking — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/ui/package.json b/.publish/ui/package.json index a90b761f..5d36a943 100644 --- a/.publish/ui/package.json +++ b/.publish/ui/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ui", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/ui — part of the WrNexus framework.", "license": "MIT", @@ -22,7 +22,7 @@ "./ui.css": "./ui.css" }, "dependencies": { - "@wrnexus/core": "^0.2.44" + "@wrnexus/core": "^0.2.45" }, "files": [ "dist", diff --git a/.publish/uploader/package.json b/.publish/uploader/package.json index cdf37689..adeba5c4 100644 --- a/.publish/uploader/package.json +++ b/.publish/uploader/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/uploader", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/uploader — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.2.44" + "@wrnexus/core": "^0.2.45" }, "files": [ "dist" diff --git a/.publish/validation/package.json b/.publish/validation/package.json index ceda1201..ca2918a2 100644 --- a/.publish/validation/package.json +++ b/.publish/validation/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/validation", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "description": "@wrnexus/validation — part of the WrNexus framework.", "license": "MIT", diff --git a/packages/ai/package.json b/packages/ai/package.json index a16c2019..c4905d3c 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ai", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", diff --git a/packages/authz/package.json b/packages/authz/package.json index 16f1b330..7e0920cc 100644 --- a/packages/authz/package.json +++ b/packages/authz/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/authz", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/cli/package.json b/packages/cli/package.json index 4551fea1..3ca6f19f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cli", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/cli/src/update.ts b/packages/cli/src/update.ts index 73fa01a4..ce15cbf1 100644 --- a/packages/cli/src/update.ts +++ b/packages/cli/src/update.ts @@ -474,6 +474,15 @@ const MIGRATIONS: Migration[] = [ // No generated application files require automatic migration. }, }, + { + version: "0.2.45", + id: "conditions-bug", + description: + "Adds server-rendered component each/if blocks and fixes dynamic component rendering.", + apply() { + // No generated application files require automatic migration. + }, + }, ]; /** Release tooling uses this to require an explicit migration entry per version. */ diff --git a/packages/compiler/package.json b/packages/compiler/package.json index f0f88cb0..52ba1e9c 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/compiler", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/compiler/src/codegen.ts b/packages/compiler/src/codegen.ts index f29e7725..e78119e3 100644 --- a/packages/compiler/src/codegen.ts +++ b/packages/compiler/src/codegen.ts @@ -524,7 +524,13 @@ function hasClientBehavior(nodes: ViewNode[]): boolean { // `{t:key}` is i18n sugar resolved server-side — not client reactivity. if (node.type === "text") return /\{(?!t:)[^{}]+\}/.test(node.value); // Server control blocks render on the server; they don't add client reactivity. - if (node.type === "each" || node.type === "if") return false; + if (node.type === "each") { + return hasClientBehavior(node.body) || hasClientBehavior(node.empty); + } + + if (node.type === "if") { + return node.branches.some((branch) => hasClientBehavior(branch.body)); + } return ( node.attrs.some((attr) => attr.event || attr.name === "csrGet" || attr.name === "csrText") || hasClientBehavior(node.children) @@ -1112,6 +1118,16 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string { return renderNestedComponentInvocation(node, ctx); } + const loopVariables = loopVarsOf(node); + + const elementContext = + loopVariables.length > 0 + ? { + ...ctx, + loopVars: new Set([...(ctx.loopVars ?? []), ...loopVariables]), + } + : ctx; + let bindIndex = 0; const staticClasses: string[] = []; const conditionalClasses: Array<{ @@ -1143,9 +1159,15 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string { return ` ${a.name}`; } - const rendered = ` ${a.name}="${compileAttrValue(a.value, ctx)}"`; + const rendered = ` ${a.name}="${compileAttrValue(a.value, elementContext)}"`; - if (!a.value.includes("{") || !exprRefsState(a.value, ctx.stateNames)) { + const referencesState = exprRefsState(a.value, ctx.stateNames); + + const referencesLoopVariable = elementContext.loopVars + ? exprRefsState(a.value, elementContext.loopVars) + : false; + + if (!a.value.includes("{") || (!referencesState && !referencesLoopVariable)) { return rendered; } @@ -1157,6 +1179,16 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string { const initialConditionalClasses = conditionalClasses .map(({ className, expression }) => { + const referencesLoopVariable = elementContext.loopVars + ? exprRefsState(expression, elementContext.loopVars) + : false; + + // data-for variables do not exist during + // initial server rendering. + if (referencesLoopVariable) { + return ""; + } + return `\${(${ctx.resolveExpr(expression)}) ? ${JSON.stringify(` ${className}`)} : ""}`; }) .join(""); @@ -1168,7 +1200,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string { const classAttribute = staticClasses.length > 0 || conditionalClasses.length > 0 - ? ` class="${compileAttrValue(staticClassValue, ctx)}${initialConditionalClasses}"` + ? ` class="${compileAttrValue(staticClassValue, elementContext)}${initialConditionalClasses}"` : ""; const classReactiveBinding = classHasReactiveExpression @@ -1186,11 +1218,6 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string { }) .join(""); - // A `data-for` element introduces loop variables for its subtree. - const loops = loopVarsOf(node); - const childCtx = - loops.length > 0 ? { ...ctx, loopVars: new Set([...(ctx.loopVars ?? []), ...loops]) } : ctx; - const allAttrs = `${classAttribute}` + `${classReactiveBinding}` + `${classBindings}` + `${attrs}`; @@ -1198,7 +1225,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string { return `<${node.tag}${allAttrs}>`; } - const inner = node.children.map((child) => renderComponentNode(child, childCtx)).join(""); + const inner = node.children.map((child) => renderComponentNode(child, elementContext)).join(""); return `<${node.tag}${allAttrs}>${inner}`; } diff --git a/packages/compiler/test/compiler.test.ts b/packages/compiler/test/compiler.test.ts index 8e459e9c..1d3b7f4e 100644 --- a/packages/compiler/test/compiler.test.ts +++ b/packages/compiler/test/compiler.test.ts @@ -839,3 +839,37 @@ component FAQAccordion { expect(output).toContain("isOpen(index)"); }); +test("data-for conditional classes do not evaluate loop variables during SSR", () => { + const output = generate( + parse(` +component Accordion { + props { + items = [] + } + + state openIndexes = [] + + functions { + function isOpen(index) { + return openIndexes.includes(index) + } + } + + view { +
+ {item.title} +
+ } +} +`), + ); + + expect(output).toContain('data-for="item, index in items"'); + + expect(output).toContain("data-wrn-class-0"); + + expect(output).not.toContain('${(isOpen(index)) ? " open" : ""}'); +}); diff --git a/packages/core/package.json b/packages/core/package.json index 86828062..2f86541d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/core", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/csr/package.json b/packages/csr/package.json index e56626bb..dc6ad0c5 100644 --- a/packages/csr/package.json +++ b/packages/csr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/csr", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/csr/src/reactive-runtime.ts b/packages/csr/src/reactive-runtime.ts index 17dc2733..eb8f22b7 100644 --- a/packages/csr/src/reactive-runtime.ts +++ b/packages/csr/src/reactive-runtime.ts @@ -335,6 +335,59 @@ export const REACTIVE_RUNTIME = String.raw` return run; } function renderAll() { renderers.forEach(function (f) { f(); }); } + function decodeLoopLocals(node) { + var owner = + node && + node.closest && + node.closest( + "[data-wrn-loop-locals]", + ); + + if (!owner) { + return {}; + } + + var encoded = + owner.getAttribute( + "data-wrn-loop-locals", + ); + + if (!encoded) { + return {}; + } + + try { + var binary = + window.atob(encoded); + + var bytes = + new Uint8Array( + binary.length, + ); + + for ( + var index = 0; + index < binary.length; + index++ + ) { + bytes[index] = + binary.charCodeAt(index); + } + + return JSON.parse( + new TextDecoder( + "utf-8", + ).decode(bytes), + ); + } catch (error) { + console.error( + "[wrnexus] failed to decode loop locals", + error, + ); + + return {}; + } + } var behaviorFunctions = {}; var stateWatchers = {}; @@ -537,44 +590,338 @@ export const REACTIVE_RUNTIME = String.raw` try { return String(itemEval(e)); } catch (err) { return ""; } }); } - function hydrateItem(root, locals) { + function hydrateItem( + root, + locals, + ) { function localRead(name) { - return Object.prototype.hasOwnProperty.call(locals, name) ? locals[name] : peekScope(name); - } - function itemEval(expr) { return evaluateExpression(expr, localRead); } + if ( + Object.prototype.hasOwnProperty.call( + locals, + name, + ) + ) { + return locals[name]; + } - var els = [root]; - if (root.querySelectorAll) Array.prototype.push.apply(els, root.querySelectorAll("*")); - els.forEach(function (n) { - if (n.nodeType !== 1) return; - Array.prototype.slice.call(n.attributes).forEach(function (attr) { - if (attr.name === "data-text") { - try { n.textContent = String(itemEval(attr.value)); } catch (e) { /* ignore */ } - } else if (attr.name.indexOf("data-on-") === 0) { - var evt = attr.name.slice("data-on-".length); - var stmt = attr.value; - n.addEventListener(evt, function () { + return readScope(name); + } + + function itemEval(expression) { + return evaluateExpression( + expression, + localRead, + ); + } + + function applyItemTemplate( + template, + ) { + return String(template).replace( + /\{\{\s*([^}]+?)\s*\}\}|\{([^{}]+)\}/g, + function ( + _, + doubleExpression, + singleExpression, + ) { + var expression = ( + doubleExpression || + singleExpression + ).trim(); + + try { + var value = + itemEval(expression); + + return value == null + ? "" + : String(value); + } catch (_) { + return ""; + } + }, + ); + } + + var elements = [root]; + + if (root.querySelectorAll) { + Array.prototype.push.apply( + elements, + root.querySelectorAll("*"), + ); + } + + elements.forEach(function (node) { + if ( + !node || + node.nodeType !== 1 + ) { + return; + } + + var attributes = + Array.prototype.slice.call( + node.attributes, + ); + + attributes.forEach(function ( + attribute, + ) { + /* + * data-text must be handled using itemEval(), + * because item/index only exist in loop locals. + */ + if ( + attribute.name === "data-text" + ) { + try { + var textValue = + itemEval(attribute.value); + + node.textContent = + textValue == null + ? "" + : String(textValue); + } catch (error) { + console.error( + "[wrnexus] data-for text binding failed for '" + + attribute.value + + "'", + error, + ); + + node.textContent = ""; + } + + return; + } + + /* + * Conditional classes: + * data-wrn-class-*='["class","expression"]' + */ + if ( + attribute.name.indexOf( + "data-wrn-class-", + ) === 0 + ) { + var classBinding; + + try { + classBinding = JSON.parse( + attribute.value, + ); + } catch (_) { + return; + } + + if ( + !classBinding || + classBinding.length !== 2 + ) { + return; + } + + var className = + classBinding[0]; + + var classExpression = + classBinding[1]; + + var classEnabled = false; + + try { + classEnabled = !!itemEval( + classExpression, + ); + } catch (_) { + classEnabled = false; + } + + node.classList.toggle( + className, + classEnabled, + ); + + return; + } + + /* + * Reactive ordinary attributes: + * data-wrn-bind-*='["aria-expanded","{isOpen(index)}"]' + */ + if ( + attribute.name.indexOf( + "data-wrn-bind-", + ) === 0 + ) { + var binding; + + try { + binding = JSON.parse( + attribute.value, + ); + } catch (_) { + return; + } + + if ( + !binding || + binding.length !== 2 + ) { + return; + } + + var attributeName = + binding[0]; + + var attributeTemplate = + binding[1]; + + var exact = + /^\{([^{}]+)\}$/.exec( + attributeTemplate, + ); + + var rawValue; + + if (exact) { try { - splitTopLevel(stmt, ";").forEach(function (part) { - runStatement(part, itemEval, localRead, function (name, value) { - if (Object.prototype.hasOwnProperty.call(locals, name)) locals[name] = value; - else if (!signals[name]) { signals[name] = signal(value); renderAll(); } - else signals[name].set(value); - }); + rawValue = itemEval( + exact[1].trim(), + ); + } catch (_) { + return; + } + } else { + rawValue = + attributeTemplate.replace( + /\{([^{}]+)\}/g, + function ( + _, + expression, + ) { + try { + var value = + itemEval( + expression.trim(), + ); + + return value == null + ? "" + : String(value); + } catch (_) { + return ""; + } + }, + ); + } + + applyReactiveAttribute( + node, + attributeName, + rawValue, + ); + + return; + } + + /* + * Event handlers inside data-for. + */ + if ( + attribute.name.indexOf( + "data-on-", + ) === 0 + ) { + var eventName = + attribute.name.slice( + "data-on-".length, + ); + + var statement = + attribute.value; + + node.addEventListener( + eventName, + function (event) { + var eventLocals = {}; + + Object.keys( + locals, + ).forEach(function (key) { + eventLocals[key] = + locals[key]; }); - } catch (e) { console.error("[wrnexus] data-for handler error", e); } - }); - } else if (attr.value.indexOf("{") !== -1) { - attr.value = fillMustache(attr.value, itemEval); + + eventLocals.event = event; + eventLocals.$event = event; + + try { + runStmt( + statement, + eventLocals, + ); + } catch (error) { + console.error( + "[wrnexus] data-for handler error in '" + + statement + + "'", + error, + ); + } + }, + ); + + return; + } + + /* + * Static attributes containing interpolation. + */ + if ( + attribute.value && + attribute.value.indexOf("{") !== -1 + ) { + node.setAttribute( + attribute.name, + applyItemTemplate( + attribute.value, + ), + ); } }); }); - var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null); - var tn; - while ((tn = walker.nextNode())) { - if (tn.nodeValue.indexOf("{") === -1) continue; - tn.nodeValue = fillMustache(tn.nodeValue, itemEval); + /* + * Text interpolation inside loop content: + * + * {item.title} + */ + var walker = + document.createTreeWalker( + root, + NodeFilter.SHOW_TEXT, + null, + ); + + var textNode; + + while ( + (textNode = walker.nextNode()) + ) { + var template = + textNode.nodeValue || ""; + + if ( + template.indexOf("{") === -1 + ) { + continue; + } + + textNode.nodeValue = + applyItemTemplate(template); } } @@ -658,7 +1005,10 @@ export const REACTIVE_RUNTIME = String.raw` var enabled = false; try { - enabled = !!evalExpr(expression); + enabled = !!evalExpr( + expression, + decodeLoopLocals(node), + ); } catch (e) { enabled = false; } @@ -671,32 +1021,97 @@ export const REACTIVE_RUNTIME = String.raw` // Reactive ordinary attributes emitted by the compiler. Each marker stores // [attributeName, originalTemplate], preserving an SSR value while allowing // state changes to update type, aria-*, class, href, and other attributes. - var bindNodes = [el].concat(Array.prototype.slice.call(el.querySelectorAll("*"))); + var bindNodes = [el].concat( + Array.prototype.slice.call( + el.querySelectorAll("*"), + ), + ); + bindNodes.forEach(function (node) { if (!owns(node)) return; - Array.prototype.slice.call(node.attributes).forEach(function (marker) { - if (marker.name.indexOf("data-wrn-bind-") !== 0) return; - var binding; - try { binding = JSON.parse(marker.value); } catch (e) { return; } - if (!binding || binding.length !== 2) return; - var name = binding[0]; - var template = binding[1]; - reactive(function () { - var exact = /^\{([^{}]+)\}$/.exec(template); - var raw; - if (exact) { - try { raw = evalExpr(exact[1].trim()); } catch (e) { return; } - } else { - raw = template.replace(/\{([^{}]+)\}/g, function (_, expr) { - try { - var value = evalExpr(expr.trim()); - return value == null ? "" : String(value); - } catch (e) { return ""; } - }); + + Array.prototype.slice + .call(node.attributes) + .forEach(function (marker) { + if ( + marker.name.indexOf( + "data-wrn-bind-", + ) !== 0 + ) { + return; } - applyReactiveAttribute(node, name, raw); + + var binding; + + try { + binding = JSON.parse( + marker.value, + ); + } catch (error) { + return; + } + + if ( + !binding || + binding.length !== 2 + ) { + return; + } + + var name = binding[0]; + var template = binding[1]; + + reactive(function () { + var locals = + decodeLoopLocals(node); + + var exact = + /^\{([^{}]+)\}$/.exec( + template, + ); + + var raw; + + if (exact) { + try { + raw = evalExpr( + exact[1].trim(), + locals, + ); + } catch (error) { + return; + } + } else { + raw = template.replace( + /\{([^{}]+)\}/g, + function ( + _, + expression, + ) { + try { + var value = + evalExpr( + expression.trim(), + locals, + ); + + return value == null + ? "" + : String(value); + } catch (error) { + return ""; + } + }, + ); + } + + applyReactiveAttribute( + node, + name, + raw, + ); + }); }); - }); }); // {{expr}} / {expr} interpolation in text nodes @@ -737,7 +1152,15 @@ export const REACTIVE_RUNTIME = String.raw` var stmt = attr.value; var listener = function (event) { - try { runStmt(stmt, { event: event, $event: event }); } catch (error) { + try { + var locals = + decodeLoopLocals(node); + + locals.event = event; + locals.$event = event; + + runStmt(stmt, locals); + } catch (error) { console.error("[wrnexus] handler error in '" + stmt + "'", error); } }; diff --git a/packages/db/package.json b/packages/db/package.json index 0d95a900..80fdfb8c 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/db", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index 7c1c5770..b7d98be4 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-server", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/encryption/package.json b/packages/encryption/package.json index 7ad20d0c..9ccfb3a5 100644 --- a/packages/encryption/package.json +++ b/packages/encryption/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/encryption", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/helpers/package.json b/packages/helpers/package.json index 4aad4c0d..5e1d7407 100644 --- a/packages/helpers/package.json +++ b/packages/helpers/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/helpers", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 72cc214c..40c93bc8 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/i18n", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/jwt/package.json b/packages/jwt/package.json index e277c15e..1ef2e274 100644 --- a/packages/jwt/package.json +++ b/packages/jwt/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/jwt", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 844a312a..5f0a36ba 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/mobile", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/native/package.json b/packages/native/package.json index e95bad55..b366b8d7 100644 --- a/packages/native/package.json +++ b/packages/native/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/native", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/oauth/package.json b/packages/oauth/package.json index 7ec38f05..49e5f0b1 100644 --- a/packages/oauth/package.json +++ b/packages/oauth/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/oauth", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json index 4e6197a1..0fc033ee 100644 --- a/packages/pubsub/package.json +++ b/packages/pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/pubsub", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/queue/package.json b/packages/queue/package.json index 900849ea..f90066c1 100644 --- a/packages/queue/package.json +++ b/packages/queue/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/queue", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/reactive/package.json b/packages/reactive/package.json index 18b20292..0e004123 100644 --- a/packages/reactive/package.json +++ b/packages/reactive/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/reactive", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/router/package.json b/packages/router/package.json index 14656bc4..e196a498 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/router", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/ssr/package.json b/packages/ssr/package.json index c88ca85b..19ce1104 100644 --- a/packages/ssr/package.json +++ b/packages/ssr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ssr", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/styles/package.json b/packages/styles/package.json index 289c147e..b7f532ff 100644 --- a/packages/styles/package.json +++ b/packages/styles/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/styles", - "version": "0.2.44", + "version": "0.2.45", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/test/package.json b/packages/test/package.json index 8b9f7b3c..8be81288 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/test", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/tracking/package.json b/packages/tracking/package.json index 735cbef4..720f4fe1 100644 --- a/packages/tracking/package.json +++ b/packages/tracking/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/tracking", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/ui/package.json b/packages/ui/package.json index 58a48e6a..ab220cab 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ui", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/uploader/package.json b/packages/uploader/package.json index 871a1de7..3c277faa 100644 --- a/packages/uploader/package.json +++ b/packages/uploader/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/uploader", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/validation/package.json b/packages/validation/package.json index 0b53c28f..e1c75aaa 100644 --- a/packages/validation/package.json +++ b/packages/validation/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/validation", - "version": "0.2.44", + "version": "0.2.45", "private": true, "type": "module", "main": "src/index.ts", diff --git a/update-package-versions.mjs b/update-package-versions.mjs index 16463980..315a7290 100644 --- a/update-package-versions.mjs +++ b/update-package-versions.mjs @@ -1,7 +1,7 @@ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; -const version = "0.2.44"; +const version = "0.2.45"; const dependencyGroups = [ "dependencies",