release: WRNexusJS 0.2.36

This commit is contained in:
2026-07-15 10:04:14 +05:30
parent afb14c2fbf
commit 17a535d4dc
65 changed files with 615 additions and 111 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/authz — part of the WrNexus framework.", "description": "@wrnexus/authz — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+11 -11
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/cli — part of the WrNexus framework.", "description": "@wrnexus/cli — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -28,16 +28,16 @@
"wrnexus": "./dist/index.js" "wrnexus": "./dist/index.js"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.36", "@wrnexus/core": "^0.2.37",
"@wrnexus/router": "^0.2.36", "@wrnexus/router": "^0.2.37",
"@wrnexus/csr": "^0.2.36", "@wrnexus/csr": "^0.2.37",
"@wrnexus/compiler": "^0.2.36", "@wrnexus/compiler": "^0.2.37",
"@wrnexus/styles": "^0.2.36", "@wrnexus/styles": "^0.2.37",
"@wrnexus/dev-server": "^0.2.36", "@wrnexus/dev-server": "^0.2.37",
"@wrnexus/ui": "^0.2.36", "@wrnexus/ui": "^0.2.37",
"@wrnexus/validation": "^0.2.36", "@wrnexus/validation": "^0.2.37",
"@wrnexus/i18n": "^0.2.36", "@wrnexus/i18n": "^0.2.37",
"@wrnexus/db": "^0.2.36" "@wrnexus/db": "^0.2.37"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/compiler — part of the WrNexus framework.", "description": "@wrnexus/compiler — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/core — part of the WrNexus framework.", "description": "@wrnexus/core — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/csr — part of the WrNexus framework.", "description": "@wrnexus/csr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.36" "@wrnexus/core": "^0.2.37"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/db — part of the WrNexus framework.", "description": "@wrnexus/db — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+13 -13
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/dev-server — part of the WrNexus framework.", "description": "@wrnexus/dev-server — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -25,18 +25,18 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.36", "@wrnexus/core": "^0.2.37",
"@wrnexus/router": "^0.2.36", "@wrnexus/router": "^0.2.37",
"@wrnexus/ssr": "^0.2.36", "@wrnexus/ssr": "^0.2.37",
"@wrnexus/csr": "^0.2.36", "@wrnexus/csr": "^0.2.37",
"@wrnexus/compiler": "^0.2.36", "@wrnexus/compiler": "^0.2.37",
"@wrnexus/styles": "^0.2.36", "@wrnexus/styles": "^0.2.37",
"@wrnexus/ui": "^0.2.36", "@wrnexus/ui": "^0.2.37",
"@wrnexus/validation": "^0.2.36", "@wrnexus/validation": "^0.2.37",
"@wrnexus/i18n": "^0.2.36", "@wrnexus/i18n": "^0.2.37",
"@wrnexus/db": "^0.2.36", "@wrnexus/db": "^0.2.37",
"@wrnexus/pubsub": "^0.2.36", "@wrnexus/pubsub": "^0.2.37",
"@wrnexus/uploader": "^0.2.36" "@wrnexus/uploader": "^0.2.37"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/encryption — part of the WrNexus framework.", "description": "@wrnexus/encryption — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.36" "@wrnexus/core": "^0.2.37"
}, },
"files": [ "files": [
"dist" "dist"
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/i18n — part of the WrNexus framework.", "description": "@wrnexus/i18n — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.36" "@wrnexus/core": "^0.2.37"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/jwt — part of the WrNexus framework.", "description": "@wrnexus/jwt — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/mobile — part of the WrNexus framework.", "description": "@wrnexus/mobile — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/native": "^0.2.36" "@wrnexus/native": "^0.2.37"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/native — part of the WrNexus framework.", "description": "@wrnexus/native — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/oauth — part of the WrNexus framework.", "description": "@wrnexus/oauth — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/pubsub — part of the WrNexus framework.", "description": "@wrnexus/pubsub — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/queue — part of the WrNexus framework.", "description": "@wrnexus/queue — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/reactive — part of the WrNexus framework.", "description": "@wrnexus/reactive — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/router", "name": "@wrnexus/router",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/router — part of the WrNexus framework.", "description": "@wrnexus/router — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,8 +21,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/compiler": "^0.2.36", "@wrnexus/compiler": "^0.2.37",
"@wrnexus/core": "^0.2.36" "@wrnexus/core": "^0.2.37"
}, },
"files": [ "files": [
"dist" "dist"
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/ssr — part of the WrNexus framework.", "description": "@wrnexus/ssr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.36" "@wrnexus/core": "^0.2.37"
}, },
"files": [ "files": [
"dist" "dist"
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/styles — part of the WrNexus framework.", "description": "@wrnexus/styles — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/uploader": "^0.2.36" "@wrnexus/uploader": "^0.2.37"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/test — part of the WrNexus framework.", "description": "@wrnexus/test — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/tracking", "name": "@wrnexus/tracking",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/tracking — part of the WrNexus framework.", "description": "@wrnexus/tracking — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ui", "name": "@wrnexus/ui",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/ui — part of the WrNexus framework.", "description": "@wrnexus/ui — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -22,7 +22,7 @@
"./ui.css": "./ui.css" "./ui.css": "./ui.css"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.36" "@wrnexus/core": "^0.2.37"
}, },
"files": [ "files": [
"dist", "dist",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/uploader", "name": "@wrnexus/uploader",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/uploader — part of the WrNexus framework.", "description": "@wrnexus/uploader — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.36" "@wrnexus/core": "^0.2.37"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/validation", "name": "@wrnexus/validation",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"description": "@wrnexus/validation — part of the WrNexus framework.", "description": "@wrnexus/validation — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+17 -1
View File
@@ -42,5 +42,21 @@
"editors/vscode/src/compiler.cjs": true "editors/vscode/src/compiler.cjs": true
}, },
"wrnexus.diagnostics.enable": true "wrnexus.diagnostics.enable": true,
"editor.semanticHighlighting.enabled": true,
"editor.semanticTokenColorCustomizations": {
"rules": {
"variable:wrn": {
"foreground": "#8B5CF6"
},
"variable.declaration:wrn": {
"foreground": "#A78BFA",
"fontStyle": "bold"
},
"variable.modification:wrn": {
"foreground": "#F59E0B"
}
}
}
} }
+25 -2
View File
@@ -2,7 +2,7 @@
"name": "wrnexus", "name": "wrnexus",
"displayName": "WRNexus Language Support", "displayName": "WRNexus Language Support",
"description": "Complete language support for WRNexus .wrn files, including highlighting, formatting, diagnostics, snippets, lifecycle hooks, state watchers, component functions, completions, and definition navigation.", "description": "Complete language support for WRNexus .wrn files, including highlighting, formatting, diagnostics, snippets, lifecycle hooks, state watchers, component functions, completions, and definition navigation.",
"version": "0.2.7", "version": "0.2.9",
"publisher": "wrnexus", "publisher": "wrnexus",
"private": true, "private": true,
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
@@ -225,7 +225,30 @@
} }
] ]
} }
} },
"semanticTokenScopes": [
{
"language": "wrn",
"scopes": {
"variable.declaration": [
"variable.other.readwrite.wrn",
"variable.other.readwrite.declaration.wrn"
],
"variable": [
"variable.other.readwrite.wrn"
],
"parameter": [
"variable.parameter.wrn"
],
"function": [
"entity.name.function.wrn"
],
"property": [
"variable.other.property.wrn"
]
}
}
]
}, },
"scripts": { "scripts": {
"build:compiler": "bun build ../../packages/compiler/src/index.ts --target=node --format=cjs --outfile=src/compiler.cjs", "build:compiler": "bun build ../../packages/compiler/src/index.ts --target=node --format=cjs --outfile=src/compiler.cjs",
+19 -7
View File
@@ -546,14 +546,22 @@ function parseHtmlView(src, pos) {
return src.slice(start, i); return src.slice(start, i);
}; };
const readAttributeName = () => { const readAttributeName = () => {
const first = src[i]; if (i >= src.length) {
if (i >= src.length || !isNameStart(first) && first !== ":" && first !== "$") {
return fail("Expected an attribute name"); return fail("Expected an attribute name");
} }
const start = i++; const start = i;
while (i < src.length && isAttributeNamePart(src[i], src[i + 1])) { while (i < src.length) {
const char = src[i];
const next = src[i + 1];
if (char === "=" || char === ">" || char === '"' || char === "'" || char === " " || char === "\t" || char === `
` || char === "\r" || char === "/" && next === ">") {
break;
}
i++; i++;
} }
if (i === start) {
return fail("Expected an attribute name");
}
return src.slice(start, i); return src.slice(start, i);
}; };
const parseTag = () => { const parseTag = () => {
@@ -1340,7 +1348,8 @@ function behaviorAttribute(behavior) {
if (!behavior) { if (!behavior) {
return ""; return "";
} }
return ` data-wrn-behavior="${attrEscape(JSON.stringify(behavior))}"`; const encoded = Buffer.from(JSON.stringify(behavior), "utf8").toString("base64");
return ` data-wrn-behavior="${encoded}"`;
} }
var INTERP_RE = /\{([^{}]+)\}/g; var INTERP_RE = /\{([^{}]+)\}/g;
function exprRefsState(expr, stateNames) { function exprRefsState(expr, stateNames) {
@@ -1435,14 +1444,17 @@ function renderComponentNode(node, ctx) {
const initialConditionalClasses = conditionalClasses.map(({ className, expression }) => { const initialConditionalClasses = conditionalClasses.map(({ className, expression }) => {
return `\${(${ctx.resolveExpr(expression)}) ? ${JSON.stringify(` ${className}`)} : ""}`; return `\${(${ctx.resolveExpr(expression)}) ? ${JSON.stringify(` ${className}`)} : ""}`;
}).join(""); }).join("");
const classAttribute = staticClasses.length > 0 || conditionalClasses.length > 0 ? ` class="${compileAttrValue(staticClasses.join(" "), ctx)}${initialConditionalClasses}"` : ""; const staticClassValue = staticClasses.join(" ");
const classHasReactiveExpression = staticClassValue.includes("{") && exprRefsState(staticClassValue, ctx.stateNames);
const classAttribute = staticClasses.length > 0 || conditionalClasses.length > 0 ? ` class="${compileAttrValue(staticClassValue, ctx)}${initialConditionalClasses}"` : "";
const classReactiveBinding = classHasReactiveExpression ? ` data-wrn-bind-class="${escLit(attrEscape(JSON.stringify(["class", staticClassValue])))}"` : "";
const classBindings = conditionalClasses.map(({ className, expression }, index) => { const classBindings = conditionalClasses.map(({ className, expression }, index) => {
const marker = attrEscape(JSON.stringify([className, expression])); const marker = attrEscape(JSON.stringify([className, expression]));
return ` data-wrn-class-${index}="${escLit(marker)}"`; return ` data-wrn-class-${index}="${escLit(marker)}"`;
}).join(""); }).join("");
const loops = loopVarsOf(node); const loops = loopVarsOf(node);
const childCtx = loops.length > 0 ? { ...ctx, loopVars: new Set([...ctx.loopVars ?? [], ...loops]) } : ctx; const childCtx = loops.length > 0 ? { ...ctx, loopVars: new Set([...ctx.loopVars ?? [], ...loops]) } : ctx;
const allAttrs = `${classAttribute}${classBindings}${attrs}`; const allAttrs = `${classAttribute}` + `${classReactiveBinding}` + `${classBindings}` + `${attrs}`;
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) { if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
return `<${node.tag}${allAttrs}>`; return `<${node.tag}${allAttrs}>`;
} }
+217
View File
@@ -271,10 +271,227 @@ function activate(context) {
registerCompletionProvider(context); registerCompletionProvider(context);
registerDefinitionProvider(context); registerDefinitionProvider(context);
registerFormatter(context); registerFormatter(context);
context.subscriptions.push(
vscode.languages.registerDocumentSemanticTokensProvider(
{
language: "wrn",
},
wrnSemanticTokensProvider,
semanticTokenLegend,
),
);
} }
function deactivate() {} function deactivate() {}
const semanticTokenLegend = new vscode.SemanticTokensLegend(
["variable"],
["declaration", "modification"],
);
/**
* @param {string} value
* @returns {string}
*/
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
/**
* @param {string} text
* @returns {Map<string, Set<number>>}
*/
function collectStateVariables(text) {
const states = new Map();
const statePattern = /\bstate\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*(?==|;|\r?$)/gm;
let match;
while ((match = statePattern.exec(text)) !== null) {
const name = match[1];
if (!name) {
continue;
}
const offset = match.index + match[0].lastIndexOf(name);
const declarations = states.get(name) || new Set();
declarations.add(offset);
states.set(name, declarations);
}
return states;
}
/**
* Comments are excluded, but quoted attribute expressions are deliberately
* included because WRN state references commonly appear in values such as
* class:flex="centered" and @click="enabled = !enabled".
*
* @param {string} text
* @returns {Array<{start: number, end: number}>}
*/
function collectCommentRanges(text) {
const ranges = [];
let index = 0;
let quote = null;
let escaped = false;
while (index < text.length) {
const current = text[index];
const next = text[index + 1];
if (quote) {
if (escaped) {
escaped = false;
index++;
continue;
}
if (current === "\\") {
escaped = true;
index++;
continue;
}
if (current === quote) {
quote = null;
}
index++;
continue;
}
if (current === '"' || current === "'" || current === "`") {
quote = current;
index++;
continue;
}
if (current === "/" && next === "/") {
const start = index;
index += 2;
while (index < text.length && text[index] !== "\n") {
index++;
}
ranges.push({ start, end: index });
continue;
}
if (current === "/" && next === "*") {
const start = index;
index += 2;
while (index < text.length && !(text[index] === "*" && text[index + 1] === "/")) {
index++;
}
index = Math.min(text.length, index + 2);
ranges.push({ start, end: index });
continue;
}
index++;
}
return ranges;
}
/**
* @param {number} offset
* @param {Array<{start: number, end: number}>} ranges
* @returns {boolean}
*/
function isInsideComment(offset, ranges) {
let low = 0;
let high = ranges.length - 1;
while (low <= high) {
const middle = Math.floor((low + high) / 2);
const range = ranges[middle];
if (!range) {
return false;
}
if (offset < range.start) {
high = middle - 1;
} else if (offset >= range.end) {
low = middle + 1;
} else {
return true;
}
}
return false;
}
/**
* @param {string} text
* @param {number} offset
* @param {number} length
* @returns {boolean}
*/
function isStateModification(text, offset, length) {
const after = text.slice(offset + length).match(/^\s*(=|\+=|-=|\*=|\/=|%=|\+\+|--)/);
if (after) {
return true;
}
const before = text.slice(Math.max(0, offset - 8), offset).match(/(\+\+|--)\s*$/);
return Boolean(before);
}
const wrnSemanticTokensProvider = {
/**
* @param {vscode.TextDocument} document
* @param {vscode.CancellationToken} token
*/
provideDocumentSemanticTokens(document, token) {
const builder = new vscode.SemanticTokensBuilder(semanticTokenLegend);
const text = document.getText();
const states = collectStateVariables(text);
const commentRanges = collectCommentRanges(text);
for (const [name, declarationOffsets] of states) {
if (token.isCancellationRequested) {
return builder.build();
}
const pattern = new RegExp(`(?<![A-Za-z0-9_$])${escapeRegExp(name)}(?![A-Za-z0-9_$])`, "g");
let match;
while ((match = pattern.exec(text)) !== null) {
const offset = match.index;
if (isInsideComment(offset, commentRanges)) {
continue;
}
const position = document.positionAt(offset);
let modifiers = [];
if (declarationOffsets.has(offset)) {
modifiers = ["declaration"];
} else if (isStateModification(text, offset, name.length)) {
modifiers = ["modification"];
}
builder.push(position.line, position.character, name.length, "variable", modifiers);
}
}
return builder.build();
},
};
module.exports = { module.exports = {
activate, activate,
deactivate, deactivate,
+55 -3
View File
@@ -534,6 +534,9 @@
{ {
"include": "#conditional-class" "include": "#conditional-class"
}, },
{
"include": "#conditional-class-single"
},
{ {
"include": "text.html.basic" "include": "text.html.basic"
} }
@@ -560,8 +563,9 @@
} }
}, },
"conditional-class": { "conditional-class": {
"match": "\\b(class)(:)([^\\s=]+)(\\s*=)", "name": "meta.attribute.class-directive.wrn",
"captures": { "begin": "(?<=\\s)(class)(:)((?:\\[[^\\]\\r\\n]*\\]|[^\\s=])+)(\\s*=\\s*)(\")",
"beginCaptures": {
"1": { "1": {
"name": "entity.other.attribute-name.class.wrn" "name": "entity.other.attribute-name.class.wrn"
}, },
@@ -573,8 +577,56 @@
}, },
"4": { "4": {
"name": "keyword.operator.assignment.wrn" "name": "keyword.operator.assignment.wrn"
},
"5": {
"name": "punctuation.definition.string.begin.wrn"
} }
} },
"conditional-class-single": {
"name": "meta.attribute.class-directive.wrn",
"begin": "(?<=\\s)(class)(:)((?:\\[[^\\]\\r\\n]*\\]|[^\\s=])+)(\\s*=\\s*)(')",
"beginCaptures": {
"1": {
"name": "entity.other.attribute-name.class.wrn"
},
"2": {
"name": "punctuation.separator.key-value.wrn"
},
"3": {
"name": "string.unquoted.class-name.wrn"
},
"4": {
"name": "keyword.operator.assignment.wrn"
},
"5": {
"name": "punctuation.definition.string.begin.wrn"
}
},
"end": "'",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.wrn"
}
},
"contentName": "meta.embedded.expression.wrn",
"patterns": [
{
"include": "source.ts"
}
]
},
"end": "\"",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.wrn"
}
},
"contentName": "meta.embedded.expression.wrn",
"patterns": [
{
"include": "source.ts"
}
]
}, },
"interpolation": { "interpolation": {
"patterns": [ "patterns": [
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+9
View File
@@ -402,6 +402,15 @@ const MIGRATIONS: Migration[] = [
// No generated application files require automatic migration. // No generated application files require automatic migration.
}, },
}, },
{
version: "0.2.37",
id: "loop-condition-fix",
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. */ /** Release tooling uses this to require an explicit migration entry per version. */
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+78 -7
View File
@@ -15,6 +15,8 @@
* realtime {..} -> export const websocket = { evt(ws, ...args) { b } } * realtime {..} -> export const websocket = { evt(ws, ...args) { b } }
*/ */
import { Buffer } from "node:buffer";
import { VOID_ELEMENTS, type Attr, type DataMode, type PageAst, type ViewNode } from "./parser.ts"; import { VOID_ELEMENTS, type Attr, type DataMode, type PageAst, type ViewNode } from "./parser.ts";
interface RenderBinding { interface RenderBinding {
@@ -804,6 +806,8 @@ interface CompCtx {
/** `data-for` loop variables in scope — their mustaches stay literal for the /** `data-for` loop variables in scope — their mustaches stay literal for the
* client's list renderer (never baked server-side, since they have no value). */ * client's list renderer (never baked server-side, since they have no value). */
loopVars?: Set<string>; loopVars?: Set<string>;
/** Local identifiers introduced by server-rendered `{#each}` blocks. */
serverLocals?: Set<string>;
} }
interface ComponentBehavior { interface ComponentBehavior {
@@ -940,9 +944,19 @@ function exprRefsState(expr: string, stateNames: Set<string>): boolean {
} }
function viewHasEvents(nodes: ViewNode[]): boolean { function viewHasEvents(nodes: ViewNode[]): boolean {
return nodes.some( return nodes.some((node) => {
(n) => n.type === "element" && (n.attrs.some((a) => a.event) || viewHasEvents(n.children)), if (node.type === "text") return false;
);
if (node.type === "each") {
return viewHasEvents(node.body) || viewHasEvents(node.empty);
}
if (node.type === "if") {
return node.branches.some((branch) => viewHasEvents(branch.body));
}
return node.attrs.some((attr) => attr.event) || viewHasEvents(node.children);
});
} }
/** /**
@@ -1004,14 +1018,65 @@ function compileAttrValue(raw: string, ctx: CompCtx): string {
return out + escLit(attrEscape(raw.slice(last))); return out + escLit(attrEscape(raw.slice(last)));
} }
function renderComponentIfNode(node: IfNode, ctx: CompCtx): string {
let expression = "``";
for (let index = node.branches.length - 1; index >= 0; index--) {
const branch = node.branches[index]!;
const body = branch.body.map((child) => renderComponentNode(child, ctx)).join("");
const bodyExpression = "`" + body + "`";
expression =
branch.cond === null
? bodyExpression
: `(${ctx.resolveExpr(branch.cond)}) ? ${bodyExpression} : ${expression}`;
}
return "${" + expression + "}";
}
function renderComponentEachNode(node: EachNode, ctx: CompCtx): string {
const item = node.item;
const index = node.index ?? "__wi";
const list = ctx.resolveExpr(node.list);
const childCtx: CompCtx = {
...ctx,
serverLocals: new Set([...(ctx.serverLocals ?? []), item, index]),
};
const body = node.body.map((child) => renderComponentNode(child, childCtx)).join("");
const empty = node.empty.map((child) => renderComponentNode(child, ctx)).join("");
return (
"${(() => { const __wl = Array.isArray(" +
list +
") ? (" +
list +
") : []; return __wl.length ? __wl.map((" +
item +
", " +
index +
") => `" +
body +
'`).join("") : `' +
empty +
"`; })()}"
);
}
/** Render a component view node into template-literal-ready source. */ /** Render a component view node into template-literal-ready source. */
function renderComponentNode(node: ViewNode, ctx: CompCtx): string { function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
if (node.type === "text") return compileText(node.value, ctx); if (node.type === "text") return compileText(node.value, ctx);
if (node.type === "each" || node.type === "if") {
throw new Error( if (node.type === "each") {
"Server `{#each}` / `{#if}` blocks are supported in pages, not components. Move them into a page (or use data-for / data-show on the client).", return renderComponentEachNode(node, ctx);
);
} }
if (node.type === "if") {
return renderComponentIfNode(node, ctx);
}
if (isComponentTag(node.tag)) { if (isComponentTag(node.tag)) {
return renderNestedComponentInvocation(node, ctx); return renderNestedComponentInvocation(node, ctx);
} }
@@ -1080,6 +1145,9 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
: ""; : "";
const classBindings = conditionalClasses const classBindings = conditionalClasses
.filter(({ expression }) => {
return !ctx.serverLocals || !exprRefsState(expression, ctx.serverLocals);
})
.map(({ className, expression }, index) => { .map(({ className, expression }, index) => {
const marker = attrEscape(JSON.stringify([className, expression])); const marker = attrEscape(JSON.stringify([className, expression]));
@@ -1200,6 +1268,9 @@ function __wireAttr(v: any): string {
return String(v == null ? "" : v).replace(/[&<>"]/g, (c) => return String(v == null ? "" : v).replace(/[&<>"]/g, (c) =>
c === "&" ? "&amp;" : c === "<" ? "&lt;" : c === ">" ? "&gt;" : "&quot;", c === "&" ? "&amp;" : c === "<" ? "&lt;" : c === ">" ? "&gt;" : "&quot;",
); );
}
function __wireRaw(v: any): string {
return String(v == null ? "" : v);
}`); }`);
if (needsScope) { if (needsScope) {
-7
View File
@@ -469,13 +469,6 @@ export function parseHtmlView(src: string, pos: number): { nodes: ViewNode[]; en
const isTagNamePart = (c: string): boolean => /[A-Za-z0-9_$:.-]/.test(c); const isTagNamePart = (c: string): boolean => /[A-Za-z0-9_$:.-]/.test(c);
const isAttributeNamePart = (c: string, next: string | undefined): boolean => {
if (c === "/") {
return next !== ">";
}
return /[A-Za-z0-9_$:.[\]%-]/.test(c);
};
const isWs = (c: string): boolean => c === " " || c === "\t" || c === "\n" || c === "\r"; const isWs = (c: string): boolean => c === " " || c === "\t" || c === "\n" || c === "\r";
const fail = (msg: string): never => { const fail = (msg: string): never => {
+48
View File
@@ -689,3 +689,51 @@ component PageHeader {
expect(output).toContain("dark:bg-white/10"); expect(output).toContain("dark:bg-white/10");
}); });
test("components support each blocks", () => {
const output = generate(
parse(`
component PageHeader {
props {
highlights = []
}
view {
<div>
{#each highlights as highlight}
<span>{highlight.title}</span>
{/each}
</div>
}
}
`),
);
expect(output).toContain("highlights");
expect(output).toContain("highlight.title");
expect(output).not.toContain("supported in pages, not components");
});
test("components support if blocks", () => {
const output = generate(
parse(`
component Banner {
props {
visible = true
}
view {
{#if visible}
<div>Visible</div>
{:else}
<div>Hidden</div>
{/if}
}
}
`),
);
expect(output).toContain("visible");
expect(output).toContain("Visible");
expect(output).toContain("Hidden");
});
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/router", "name": "@wrnexus/router",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.2.36", "version": "0.2.37",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/tracking", "name": "@wrnexus/tracking",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ui", "name": "@wrnexus/ui",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/uploader", "name": "@wrnexus/uploader",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/validation", "name": "@wrnexus/validation",
"version": "0.2.36", "version": "0.2.37",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+63
View File
@@ -0,0 +1,63 @@
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
const version = "0.2.37";
const dependencyGroups = [
"dependencies",
"devDependencies",
"peerDependencies",
"optionalDependencies",
];
for (const entry of readdirSync("packages", {
withFileTypes: true,
})) {
if (!entry.isDirectory()) {
continue;
}
const packageFile = join("packages", entry.name, "package.json");
if (!existsSync(packageFile)) {
continue;
}
const packageJson = JSON.parse(readFileSync(packageFile, "utf8"));
packageJson.version = version;
for (const group of dependencyGroups) {
const dependencies = packageJson[group];
if (!dependencies || typeof dependencies !== "object") {
continue;
}
for (const dependencyName of Object.keys(dependencies)) {
if (!dependencyName.startsWith("@wrnexus/")) {
continue;
}
const currentVersion = dependencies[dependencyName];
if (typeof currentVersion !== "string") {
continue;
}
if (currentVersion.startsWith("workspace:")) {
continue;
}
if (currentVersion.startsWith("^")) {
dependencies[dependencyName] = `^${version}`;
} else if (currentVersion.startsWith("~")) {
dependencies[dependencyName] = `~${version}`;
} else {
dependencies[dependencyName] = version;
}
}
}
writeFileSync(packageFile, `${JSON.stringify(packageJson, null, 2)}\n`);
}