Files
WRNexusJS/editors/vscode/src/compiler.cjs
T
2026-07-15 13:22:20 +05:30

1944 lines
62 KiB
JavaScript

var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __hasOwnProp = Object.prototype.hasOwnProperty;
function __accessProp(key) {
return this[key];
}
var __toCommonJS = (from) => {
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
if (entry)
return entry;
entry = __defProp({}, "__esModule", { value: true });
if (from && typeof from === "object" || typeof from === "function") {
for (var key of __getOwnPropNames(from))
if (!__hasOwnProp.call(entry, key))
__defProp(entry, key, {
get: __accessProp.bind(from, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
__moduleCache.set(from, entry);
return entry;
};
var __moduleCache;
var __returnValue = (v) => v;
function __exportSetter(name, newValue) {
this[name] = __returnValue.bind(null, newValue);
}
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: __exportSetter.bind(all, name)
});
};
// ../../packages/compiler/src/index.ts
var exports_src = {};
__export(exports_src, {
parse: () => parse,
generateNative: () => generateNative,
generate: () => generate,
compileWireFile: () => compileWireFile,
compileNativeWireFile: () => compileNativeWireFile,
compile: () => compile,
ParseError: () => ParseError,
NativeCompileError: () => NativeCompileError,
Lexer: () => Lexer,
LexError: () => LexError
});
module.exports = __toCommonJS(exports_src);
// ../../packages/compiler/src/tokenizer.ts
class LexError extends Error {
}
var isWs = (c) => c === " " || c === "\t" || c === `
` || c === "\r";
var isIdentStart = (c) => /[A-Za-z_]/.test(c);
var isIdentPart = (c) => /[A-Za-z0-9_]/.test(c);
class Lexer {
src;
pos = 0;
constructor(src) {
this.src = src;
}
skipTrivia() {
const { src } = this;
while (this.pos < src.length) {
const c = src[this.pos];
if (isWs(c)) {
this.pos++;
continue;
}
if (c === "/" && src[this.pos + 1] === "/") {
while (this.pos < src.length && src[this.pos] !== `
`)
this.pos++;
continue;
}
break;
}
}
next() {
this.skipTrivia();
const { src } = this;
const pos = this.pos;
if (pos >= src.length)
return { type: "eof", value: "", pos };
const c = src[pos];
switch (c) {
case "{":
this.pos++;
return { type: "lbrace", value: c, pos };
case "}":
this.pos++;
return { type: "rbrace", value: c, pos };
case "(":
this.pos++;
return { type: "lparen", value: c, pos };
case ")":
this.pos++;
return { type: "rparen", value: c, pos };
case "@":
this.pos++;
return { type: "at", value: c, pos };
case "=":
this.pos++;
return { type: "eq", value: c, pos };
case ",":
this.pos++;
return { type: "comma", value: c, pos };
case '"':
case "'":
return this.readString(c, pos);
}
if (isIdentStart(c)) {
let v = "";
while (this.pos < src.length && isIdentPart(src[this.pos]))
v += src[this.pos++];
return { type: "ident", value: v, pos };
}
throw new LexError(`Unexpected character '${c}' at offset ${pos} (line ${this.lineAt(pos)})`);
}
peek() {
const save = this.pos;
const t = this.next();
this.pos = save;
return t;
}
readString(quote, pos) {
const { src } = this;
let v = "";
this.pos++;
while (this.pos < src.length) {
const c = src[this.pos++];
if (c === "\\") {
const n = src[this.pos++];
v += n === "n" ? `
` : n === "t" ? "\t" : n;
continue;
}
if (c === quote)
return { type: "string", value: v, pos };
v += c;
}
throw new LexError(`Unterminated string at offset ${pos}`);
}
readPath() {
this.skipTrivia();
const { src } = this;
let v = "";
while (this.pos < src.length && !isWs(src[this.pos]) && src[this.pos] !== "{") {
v += src[this.pos++];
}
if (!v)
throw new LexError(`Expected a path at offset ${this.pos}`);
return v;
}
readToLineEnd() {
const { src } = this;
let v = "";
while (this.pos < src.length && src[this.pos] !== `
`)
v += src[this.pos++];
return v.trim();
}
readBalancedBraces() {
this.skipTrivia();
const { src } = this;
if (src[this.pos] !== "{") {
throw new LexError(`Expected '{' at offset ${this.pos}`);
}
const start = this.pos + 1;
let depth = 0;
let i = this.pos;
let str = null;
for (;i < src.length; i++) {
const c = src[i];
if (str) {
if (c === "\\") {
i++;
continue;
}
if (c === str)
str = null;
continue;
}
if (c === '"' || c === "'" || c === "`") {
str = c;
continue;
}
if (c === "{")
depth++;
else if (c === "}") {
depth--;
if (depth === 0) {
this.pos = i + 1;
return src.slice(start, i);
}
}
}
throw new LexError(`Unbalanced braces starting at offset ${this.pos}`);
}
lineAt(pos) {
let line = 1;
for (let i = 0;i < pos && i < this.src.length; i++) {
if (this.src[i] === `
`)
line++;
}
return line;
}
}
// ../../packages/compiler/src/parser.ts
var VOID_ELEMENTS = new Set([
"area",
"base",
"br",
"col",
"embed",
"hr",
"img",
"input",
"link",
"meta",
"param",
"source",
"track",
"wbr"
]);
class ParseError extends Error {
}
function parseSeoBlock(body) {
const out = {};
const pair = /([A-Za-z][A-Za-z0-9_-]*)\s*=\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^\n;]+))/g;
for (const match of body.matchAll(pair)) {
const key = match[1];
const rawValue = match[2] ?? match[3] ?? match[4] ?? "";
out[key] = unescapeSeoValue(rawValue.trim());
}
return out;
}
function unescapeSeoValue(value) {
return value.replace(/\\(["'\\nrt])/g, (_match, ch) => {
if (ch === "n")
return `
`;
if (ch === "r")
return "\r";
if (ch === "t")
return "\t";
return ch;
});
}
function parse(source) {
const lx = new Lexer(source);
const expect = (type) => {
const t = lx.next();
if (t.type !== type) {
throw new ParseError(`Expected ${type} but got '${t.value || t.type}' at offset ${t.pos}`);
}
return t;
};
const expectKeyword = (kw) => {
const t = lx.next();
if (t.type !== "ident" || t.value !== kw) {
throw new ParseError(`Expected '${kw}' but got '${t.value || t.type}' at offset ${t.pos}`);
}
};
try {
const opener = lx.next();
if (opener.type !== "ident" || !["page", "component", "layout"].includes(opener.value)) {
throw new ParseError(`Expected 'page', 'component', or 'layout' but got '${opener.value || opener.type}' at offset ${opener.pos}`);
}
const kind = opener.value;
const name = expect("ident").value;
expect("lbrace");
let layout;
const props = [];
const states = [];
const seo = {};
const view = [];
const styles = [];
const functions = [];
const dataApis = [];
const modeFunctions = [];
const lifecycle = {};
const watches = [];
const apis = [];
const realtimes = [];
while (lx.peek().type !== "rbrace") {
const kw = lx.peek();
if (kw.type === "eof")
throw new ParseError(`Unexpected end of input inside ${kind}`);
if (kw.type !== "ident") {
throw new ParseError(`Expected a ${kind} member keyword at offset ${kw.pos}`);
}
switch (kw.value) {
case "layout": {
lx.next();
expect("eq");
layout = expect("string").value;
break;
}
case "props": {
lx.next();
expect("lbrace");
while (lx.peek().type !== "rbrace") {
const t = lx.peek();
if (t.type === "eof")
throw new ParseError("Unexpected end of input inside props");
if (t.type !== "ident") {
throw new ParseError(`Expected a prop name at offset ${t.pos}`);
}
const pName = expect("ident").value;
expect("eq");
props.push({ name: pName, default: lx.readToLineEnd() });
}
expect("rbrace");
break;
}
case "state": {
lx.next();
const sName = expect("ident").value;
expect("eq");
states.push({ name: sName, expr: lx.readToLineEnd() });
break;
}
case "view": {
lx.next();
expect("lbrace");
const { nodes, endPos } = parseHtmlView(lx.src, lx.pos);
view.push(...nodes);
lx.pos = endPos;
expect("rbrace");
break;
}
case "seo": {
lx.next();
Object.assign(seo, parseSeoBlock(lx.readBalancedBraces()));
break;
}
case "api": {
lx.next();
const method = expect("ident").value.toUpperCase();
const path = lx.readPath();
const body = lx.readBalancedBraces();
apis.push({ method, path, body });
break;
}
case "ssr":
case "client": {
const mode = kw.value === "ssr" ? "ssr" : "client";
lx.next();
expect("lbrace");
while (lx.peek().type !== "rbrace") {
const member = lx.peek();
if (member.type === "eof") {
throw new ParseError(`Unexpected end of input inside ${mode} block`);
}
if (member.type !== "ident") {
throw new ParseError(`Expected a ${mode} member keyword at offset ${member.pos}`);
}
switch (member.value) {
case "api": {
lx.next();
const name2 = expect("ident").value;
const method = expect("ident").value.toUpperCase();
const path = lx.readPath();
const body = lx.readBalancedBraces();
dataApis.push({ mode, name: name2, method, path, body });
break;
}
case "functions": {
lx.next();
modeFunctions.push({ mode, body: lx.readBalancedBraces() });
break;
}
default:
throw new ParseError(`Unknown ${mode} member '${member.value}' at offset ${member.pos}`);
}
}
expect("rbrace");
break;
}
case "realtime": {
lx.next();
const rName = expect("ident").value;
expect("lbrace");
const handlers = [];
while (lx.peek().type !== "rbrace") {
expectKeyword("on");
const event = expect("ident").value;
expect("lparen");
const args = [];
while (lx.peek().type !== "rparen") {
args.push(expect("ident").value);
if (lx.peek().type === "comma")
lx.next();
}
expect("rparen");
handlers.push({ event, args, body: lx.readBalancedBraces() });
}
expect("rbrace");
realtimes.push({ name: rName, handlers });
break;
}
case "style": {
lx.next();
styles.push(lx.readBalancedBraces());
break;
}
case "lifecycle": {
lx.next();
expect("lbrace");
while (lx.peek().type !== "rbrace") {
const hook = lx.peek();
if (hook.type === "eof") {
throw new ParseError("Unexpected end of input inside lifecycle block");
}
if (hook.type !== "ident") {
throw new ParseError(`Expected a lifecycle hook at offset ${hook.pos}`);
}
if (hook.value !== "mount" && hook.value !== "update" && hook.value !== "unmount") {
throw new ParseError(`Unknown lifecycle hook '${hook.value}' at offset ${hook.pos}`);
}
const hookName = hook.value;
lx.next();
if (lifecycle[hookName] !== undefined) {
throw new ParseError(`Duplicate lifecycle hook '${hookName}' at offset ${hook.pos}`);
}
lifecycle[hookName] = lx.readBalancedBraces();
}
expect("rbrace");
break;
}
case "watch": {
lx.next();
const stateName = expect("ident").value;
const body = lx.readBalancedBraces();
watches.push({
state: stateName,
body
});
break;
}
case "functions": {
lx.next();
functions.push(lx.readBalancedBraces());
break;
}
default:
throw new ParseError(`Unknown page member '${kw.value}' at offset ${kw.pos}`);
}
}
expect("rbrace");
const declaredStates = new Set(states.map((state) => state.name));
for (const watcher of watches) {
if (!declaredStates.has(watcher.state)) {
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
}
}
return {
type: "page",
kind,
name,
layout,
props,
states,
seo,
view,
styles,
functions,
dataApis,
modeFunctions,
lifecycle,
watches,
apis,
realtimes
};
} catch (err) {
if (err instanceof LexError)
throw new ParseError(err.message);
throw err;
}
}
function parseHtmlView(src, pos) {
let i = pos;
const isNameStart = (c) => /[A-Za-z_]/.test(c);
const isTagNamePart = (c) => /[A-Za-z0-9_$:.-]/.test(c);
const isWs2 = (c) => c === " " || c === "\t" || c === `
` || c === "\r";
const fail = (msg) => {
throw new ParseError(`${msg} at offset ${i}`);
};
const skipWs = () => {
while (i < src.length && isWs2(src[i]))
i++;
};
const readInterpolation = () => {
const start = i;
let depth = 0;
for (;i < src.length; i++) {
if (src[i] === "{")
depth++;
else if (src[i] === "}" && --depth === 0) {
i++;
return src.slice(start, i);
}
}
return fail("Unterminated `{` interpolation in view");
};
const readQuoted = () => {
const quote = src[i];
if (quote !== '"' && quote !== "'")
return fail("Expected a quoted attribute value");
i++;
const start = i;
while (i < src.length && src[i] !== quote)
i++;
if (i >= src.length)
return fail("Unterminated attribute value");
const value = src.slice(start, i);
i++;
return value;
};
const readTagName = () => {
if (i >= src.length || !isNameStart(src[i])) {
return fail("Expected a tag name");
}
const start = i++;
while (i < src.length && isTagNamePart(src[i])) {
i++;
}
return src.slice(start, i);
};
const readAttributeName = () => {
if (i >= src.length) {
return fail("Expected an attribute name");
}
const start = i;
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++;
}
if (i === start) {
return fail("Expected an attribute name");
}
return src.slice(start, i);
};
const parseTag = () => {
i++;
const tag = readTagName();
const attrs = [];
for (;; ) {
skipWs();
const c = src[i];
if (c === undefined)
return fail(`Unterminated <${tag}> tag`);
if (c === ">") {
i++;
break;
}
if (c === "/" && src[i + 1] === ">") {
i += 2;
return { type: "element", tag, attrs, children: [] };
}
if (c === "@") {
i++;
const name2 = readAttributeName();
skipWs();
if (src[i] !== "=")
return fail(`Expected '=' after @${name2}`);
i++;
skipWs();
attrs.push({ name: name2, value: readQuoted(), event: true });
continue;
}
const name = readAttributeName();
skipWs();
if (src[i] === "=") {
i++;
skipWs();
attrs.push({ name, value: readQuoted(), event: false });
} else {
attrs.push({ name, value: "", event: false, boolean: true });
}
}
if (VOID_ELEMENTS.has(tag.toLowerCase())) {
return { type: "element", tag, attrs, children: [] };
}
const children = parseNodeList("element");
if (src[i] !== "<" || src[i + 1] !== "/")
return fail(`Expected </${tag}>`);
i += 2;
skipWs();
const close = readTagName();
if (close !== tag)
return fail(`Mismatched </${close}>, expected </${tag}>`);
skipWs();
if (src[i] !== ">")
return fail(`Expected '>' to close </${tag}>`);
i++;
return { type: "element", tag, attrs, children };
};
const EACH_HEADER = /^\{#each\s+([\s\S]+?)\s+as\s+([A-Za-z_$][\w$]*)\s*(?:,\s*([A-Za-z_$][\w$]*)\s*)?\}$/;
function parseEach() {
const header = readInterpolation();
const m = EACH_HEADER.exec(header);
if (!m)
return fail(`Invalid {#each …} header: ${header}`);
const list = m[1].trim();
const item = m[2];
const index = m[3];
const body = parseNodeList("each");
let empty = [];
if (src.startsWith("{:empty}", i)) {
i += "{:empty}".length;
empty = parseNodeList("each");
}
if (!src.startsWith("{/each}", i))
return fail("Expected `{/each}` to close `{#each}`");
i += "{/each}".length;
return { type: "each", list, item, index, body, empty };
}
function parseIf() {
const header = readInterpolation();
const m = /^\{#if\s+([\s\S]+?)\s*\}$/.exec(header);
if (!m)
return fail(`Invalid {#if …} header: ${header}`);
const branches = [
{ cond: m[1].trim(), body: parseNodeList("if") }
];
for (;; ) {
if (src.startsWith("{:else if", i)) {
const h = readInterpolation();
const mm = /^\{:else if\s+([\s\S]+?)\s*\}$/.exec(h);
if (!mm)
return fail(`Invalid {:else if …}: ${h}`);
branches.push({ cond: mm[1].trim(), body: parseNodeList("if") });
continue;
}
if (src.startsWith("{:else}", i)) {
i += "{:else}".length;
branches.push({ cond: null, body: parseNodeList("if") });
continue;
}
break;
}
if (!src.startsWith("{/if}", i))
return fail("Expected `{/if}` to close `{#if}`");
i += "{/if}".length;
return { type: "if", branches };
}
function parseNodeList(mode) {
const nodes2 = [];
let text = "";
const flush = () => {
if (text.length > 0) {
nodes2.push({ type: "text", value: text });
text = "";
}
};
for (;; ) {
if (i >= src.length) {
return mode === "root" ? fail("Unexpected end of view (missing `}`)") : fail("Unclosed block");
}
const c = src[i];
if (c === "<") {
const next = src[i + 1];
if (next === "/") {
flush();
break;
}
if (src.startsWith("<!--", i)) {
const end = src.indexOf("-->", i + 4);
i = end === -1 ? src.length : end + 3;
continue;
}
if (next !== undefined && (isNameStart(next) || next === "!")) {
flush();
nodes2.push(parseTag());
continue;
}
text += c;
i++;
continue;
}
if (c === "{") {
if (src.startsWith("{#each", i)) {
flush();
nodes2.push(parseEach());
continue;
}
if (src.startsWith("{#if", i)) {
flush();
nodes2.push(parseIf());
continue;
}
if (mode === "each" && (src.startsWith("{:empty}", i) || src.startsWith("{/each}", i))) {
flush();
break;
}
if (mode === "if" && (src.startsWith("{:else", i) || src.startsWith("{/if}", i))) {
flush();
break;
}
text += readInterpolation();
continue;
}
if (c === "}" && mode === "root") {
flush();
break;
}
text += c;
i++;
}
return nodes2;
}
const nodes = parseNodeList("root");
return { nodes, endPos: i };
}
// ../../packages/compiler/src/codegen.ts
var import_node_buffer = require("node:buffer");
function isComponentTag(tag) {
return /^[A-Z][A-Za-z0-9_$]*$/.test(tag);
}
function attrEscape(value) {
return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function templateEscape(html) {
return html.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
}
function styleEscape(css) {
return css.replace(/<\/style/gi, "<\\/style");
}
function attrValue(attrs, name) {
return attrs.find((attr) => !attr.event && attr.name === name)?.value;
}
function renderAttr(attr) {
if (attr.event)
return ` ${eventAttribute(attr.name)}="${attrEscape(attr.value)}"`;
switch (attr.name) {
case "api":
case "ssrGet":
case "ssrText":
case "csrGet":
case "csrText":
return "";
default:
return attr.boolean ? ` ${attr.name}` : ` ${attr.name}="${attrEscape(attr.value)}"`;
}
}
function eventAttribute(name) {
if (name.startsWith("window:")) {
return `data-on-window-${name.slice("window:".length)}`;
}
if (name.startsWith("document:")) {
return `data-on-document-${name.slice("document:".length)}`;
}
if (name.startsWith("browser-")) {
return `data-on-wrnexus-browser-${name.slice(8)}`;
}
if (name.startsWith("mobile-")) {
return `data-on-wrnexus-mobile-${name.slice(7)}`;
}
return `data-on-${name}`;
}
function reactiveAttrValue(raw, reactive) {
let found = false;
const value = raw.replace(/\{([^{}]+)\}/g, (whole, inner) => {
const expr = inner.trim();
if (!exprRefsState(expr, reactive.stateNames))
return whole;
found = true;
try {
const result = new Function("with(this){return (" + expr + ");}").call(reactive.scope);
return result == null ? "" : String(result);
} catch {
return whole;
}
});
return found ? value : null;
}
function renderAttrs(attrs, csrId, reactive = null) {
let bindIndex = 0;
const rendered = attrs.map((attr) => {
const base = renderAttr(attr);
if (!reactive || attr.event || attr.boolean || !base || !attr.value.includes("{"))
return base;
const initial = reactiveAttrValue(attr.value, reactive);
if (initial === null)
return base;
const marker = JSON.stringify([attr.name, attr.value]);
return ` ${attr.name}="${attrEscape(initial)}" data-wrn-bind-${bindIndex++}="${attrEscape(marker)}"`;
}).join("");
return csrId ? `${rendered} data-wrnexus-csr="${attrEscape(csrId)}"` : rendered;
}
function substituteTMarkers(text) {
return text.replace(/\{t:([^{}]+)\}/g, (_m, key) => `<span data-t="${attrEscape(key.trim())}"></span>`);
}
function htmlTextEscape(value) {
return value.replace(/[&<>]/g, (c) => c === "&" ? "&amp;" : c === "<" ? "&lt;" : "&gt;");
}
function evalStateSeeds(states) {
const scope = {};
for (const s of states) {
try {
scope[s.name] = new Function("with(this){return (" + s.expr + ");}").call(scope);
} catch {
scope[s.name] = undefined;
}
}
return scope;
}
function substituteReactiveText(raw, reactive) {
const text = substituteTMarkers(raw);
if (!reactive || reactive.stateNames.size === 0)
return text;
return text.replace(/\{([^{}]+)\}/g, (whole, inner) => {
const expr = inner.trim();
if (expr.startsWith("t:") || !exprRefsState(expr, reactive.stateNames))
return whole;
let value;
try {
value = new Function("with(this){return (" + expr + ");}").call(reactive.scope);
} catch {
return whole;
}
const baked = htmlTextEscape(value == null ? "" : String(value));
return `<span data-text="${attrEscape(expr)}">${baked}</span>`;
});
}
function bakeLoopText(raw) {
let out = "";
let last = 0;
let m;
const re = /\{([^{}]+)\}/g;
while (m = re.exec(raw)) {
out += escLit(raw.slice(last, m.index));
const expr = m[1].trim();
if (expr.startsWith("t:")) {
out += escLit(`<span data-t="${attrEscape(expr.slice(2).trim())}"></span>`);
} else {
out += "${__wrnexusEscapeHtml(" + expr + ")}";
}
last = m.index + m[0].length;
}
return out + escLit(raw.slice(last));
}
function bakeLoopAttr(raw) {
if (!raw.includes("{"))
return escLit(attrEscape(raw));
let out = "";
let last = 0;
let m;
const re = /\{([^{}]+)\}/g;
while (m = re.exec(raw)) {
out += escLit(attrEscape(raw.slice(last, m.index)));
out += "${__wrnexusEscapeHtml(" + m[1].trim() + ")}";
last = m.index + m[0].length;
}
return out + escLit(attrEscape(raw.slice(last)));
}
function renderLoopBody(node) {
if (node.type === "text") {
return bakeLoopText(node.value);
}
if (node.type === "each") {
return compileEachExpr(node);
}
if (node.type === "if") {
return compileIfExpr(node);
}
const componentTag = isComponentTag(node.tag);
const attrs = node.attrs.filter((attr) => attr.name !== "data-component").map((attr) => {
const name = attr.event ? eventAttribute(attr.name) : attr.name;
if (attr.boolean) {
return escLit(` ${name}`);
}
return escLit(` ${name}="`) + bakeLoopAttr(attr.value) + escLit(`"`);
}).join("");
const inner = node.children.map(renderLoopBody).join("");
if (componentTag) {
return escLit(`<div data-component="${attrEscape(node.tag)}"`) + attrs + escLit(">") + inner + escLit("</div>");
}
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
return escLit(`<${node.tag}`) + attrs + escLit(">");
}
return escLit(`<${node.tag}`) + attrs + escLit(">") + inner + escLit(`</${node.tag}>`);
}
function compileEachExpr(node) {
const item = node.item;
const index = node.index ?? "__wi";
const body = node.body.map(renderLoopBody).join("");
const empty = node.empty.map(renderLoopBody).join("");
return "${(() => { const __wl = Array.isArray(" + node.list + ") ? (" + node.list + ") : []; return __wl.length ? __wl.map((" + item + ", " + index + ") => `" + body + '`).join("") : `' + empty + "`; })()}";
}
function compileIfExpr(node) {
let expr = "``";
for (let k = node.branches.length - 1;k >= 0; k--) {
const b = node.branches[k];
const bodySrc = "`" + b.body.map(renderLoopBody).join("") + "`";
expr = b.cond === null ? bodySrc : "(" + b.cond + ") ? " + bodySrc + " : " + expr;
}
return "${" + expr + "}";
}
function collectControlExprs(nodes, out = []) {
for (const node of nodes) {
if (node.type === "each") {
out.push(node.list);
collectControlExprs(node.body, out);
collectControlExprs(node.empty, out);
} else if (node.type === "if") {
for (const b of node.branches) {
if (b.cond)
out.push(b.cond);
collectControlExprs(b.body, out);
}
} else if (node.type === "element") {
collectControlExprs(node.children, out);
}
}
return out;
}
function renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive = null) {
if (node.type === "text")
return substituteReactiveText(node.value, reactive);
if (node.type === "each" || node.type === "if") {
loops.push(node.type === "each" ? compileEachExpr(node) : compileIfExpr(node));
return `\x00WRNEACH${loops.length - 1}\x00`;
}
if (isComponentTag(node.tag)) {
return renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive);
}
const apiName = attrValue(node.attrs, "api");
const apiBinding = apiName ? apiBindings.get(apiName) : undefined;
if (apiName && !apiBinding) {
throw new Error(`Unknown .wrn api binding "${apiName}"`);
}
const ssrGet = attrValue(node.attrs, "ssrGet");
const ssrText = attrValue(node.attrs, "ssrText");
const csrGet = attrValue(node.attrs, "csrGet");
const csrText = attrValue(node.attrs, "csrText");
const csrId = apiBinding?.mode === "client" ? csrMarker(csrBindings, renderBinding(apiBinding)) : csrGet && csrText ? csrMarker(csrBindings, {
method: "GET",
path: apiRoutePath(csrGet),
body: expressionBody(csrText),
helpers: ""
}) : undefined;
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive)}>`;
}
const inner = apiBinding?.mode === "ssr" ? ssrMarker(ssrBindings, renderBinding(apiBinding)) : ssrGet && ssrText ? ssrMarker(ssrBindings, {
method: "GET",
path: apiRoutePath(ssrGet),
body: expressionBody(ssrText),
helpers: ""
}) : node.children.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join("");
return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive)}>${inner}</${node.tag}>`;
}
function renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive) {
const attrs = node.attrs.filter((attr) => attr.name !== "data-component").map((attr) => renderPageComponentAttr(attr, loops)).join("");
const inner = node.children.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join("");
return `<div data-component="${attrEscape(node.tag)}"` + `${attrs}>${inner}</div>`;
}
function renderNestedComponentInvocation(node, ctx) {
let bindIndex = 0;
const attrs = node.attrs.filter((attr) => attr.name !== "data-component").map((attr) => {
if (attr.event) {
return escLit(` ${eventAttribute(attr.name)}="`) + escLit(attrEscape(attr.value)) + escLit(`"`);
}
if (attr.boolean) {
return ` ${attr.name}`;
}
const wholeExpression = wholeAttributeExpression(attr.value);
const compiledValue = wholeExpression ? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}` : compileAttrValue(attr.value, ctx);
const rendered = ` ${attr.name}="${compiledValue}"`;
if (wholeExpression || !attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames)) {
return rendered;
}
const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
return rendered + ` data-wrn-bind-${bindIndex++}="${escLit(marker)}"`;
}).join("");
const loops = loopVarsOf(node);
const childCtx = loops.length > 0 ? {
...ctx,
loopVars: new Set([...ctx.loopVars ?? [], ...loops])
} : ctx;
const inner = node.children.map((child) => renderComponentNode(child, childCtx)).join("");
return `<div data-component="${attrEscape(node.tag)}"${attrs}>${inner}</div>`;
}
function ssrMarker(bindings, binding) {
const marker = `<!--wrnexus-ssr:${bindings.length}-->`;
bindings.push({ marker, ...binding });
return marker;
}
function csrMarker(bindings, binding) {
const id = String(bindings.length);
bindings.push({ id, ...binding });
return id;
}
function renderBinding(binding) {
return {
method: binding.method,
path: binding.path,
body: binding.body,
helpers: binding.helpers
};
}
function hasClientBehavior(nodes) {
return nodes.some((node) => {
if (node.type === "text")
return /\{(?!t:)[^{}]+\}/.test(node.value);
if (node.type === "each" || node.type === "if")
return false;
return node.attrs.some((attr) => attr.event || attr.name === "csrGet" || attr.name === "csrText") || hasClientBehavior(node.children);
});
}
function apiRoutePath(path) {
const trimmed = path.trim();
if (!trimmed.startsWith("/")) {
throw new Error(`.wrn API paths must start with "/": ${path}`);
}
if (trimmed.includes("\x00") || trimmed.includes("\\") || /(^|\/)\.\.(\/|$)/.test(trimmed)) {
throw new Error(`Unsafe .wrn API path: ${path}`);
}
if (trimmed === "/api" || trimmed.startsWith("/api/"))
return trimmed;
return `/api${trimmed}`;
}
function expressionBody(expr) {
return `return (${expr});`;
}
function dataBody(source) {
const trimmed = source.trim();
if (!trimmed)
return "return undefined;";
return /\breturn\b/.test(trimmed) ? trimmed : expressionBody(trimmed);
}
function modeHelpers(ast, mode, sharedHelpers) {
return [
sharedHelpers,
...ast.modeFunctions.filter((block) => block.mode === mode).map((block) => block.body.trim()).filter(Boolean)
].filter(Boolean).join(`
`);
}
function apiBindingMap(ast, sharedHelpers) {
const bindings = new Map;
for (const block of ast.dataApis) {
if (bindings.has(block.name)) {
throw new Error(`Duplicate .wrn api binding "${block.name}"`);
}
bindings.set(block.name, {
mode: block.mode,
method: block.method,
path: apiRoutePath(block.path),
body: dataBody(block.body),
helpers: modeHelpers(ast, block.mode, sharedHelpers)
});
}
return bindings;
}
function ssrRuntimeSource() {
return `const __wrnexusHtmlEscapes = { "&": "&amp;", "<": "&lt;", ">": "&gt;", "\\"": "&quot;", "'": "&#39;" };
function __wrnexusEscapeHtml(value: unknown): string {
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch);
}
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: any): unknown {
const adapters = {
cookies: ctx.cookies,
session: ctx.session,
localStorage: ctx.localStorage,
};
return new Function("$data", "$adapters", "const cookies = $adapters.cookies;\\nconst session = $adapters.session;\\nconst localStorage = $adapters.localStorage;\\nwith ($data ?? {}) {\\n" + helpers + "\\n" + body + "\\n}")(data, adapters);
}
function __wrnexusPropAttr(
value: unknown,
): string {
const serialized =
value !== null &&
typeof value === "object"
? JSON.stringify(value)
: String(value == null ? "" : value);
return serialized.replace(
/[&<>"]/g,
(character) =>
character === "&"
? "&amp;"
: character === "<"
? "&lt;"
: character === ">"
? "&gt;"
: "&quot;",
);
}
async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise<unknown> {
if (typeof ctx.__wrnexusCallApi === "function") {
return await ctx.__wrnexusCallApi(path, method);
}
const url = new URL(path, ctx.req.url);
const res = await fetch(new Request(url, { method, headers: ctx.req.headers }));
if (!res.ok) {
throw new Error(".wrn data API request failed with status " + res.status);
}
const type = res.headers.get("content-type") || "";
return type.includes("application/json") ? await res.json() : await res.text();
}
async function __wrnexusRenderSsrBindings(html: string, ctx: any): Promise<string> {
for (const binding of __wrnexusSsrBindings) {
const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
html = html.replace(binding.marker, __wrnexusEscapeHtml(value));
}
return html;
}`;
}
function generate(ast) {
if (ast.kind === "component" || ast.kind === "layout") {
return generateComponent(ast);
}
const out = [];
const ssrBindings = [];
const csrBindings = [];
const helpers = ast.functions.map((body2) => body2.trim()).filter(Boolean).join(`
`);
const apiBindings = apiBindingMap(ast, helpers);
if (helpers) {
out.push(`// --- .wrn functions ---
${helpers}`);
}
out.push(`export const meta = ${JSON.stringify({ title: ast.name, ...ast.seo }, null, 2)};`);
if (ast.layout)
out.push(`export const layout = ${JSON.stringify(ast.layout)};`);
const reactive = ast.states.length > 0 ? { stateNames: new Set(ast.states.map((s) => s.name)), scope: evalStateSeeds(ast.states) } : null;
const loops = [];
let html = ast.view.map((node) => renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive)).join("");
const styles = ast.styles.map((body2) => body2.trim()).filter(Boolean);
const needsClientRuntime = ast.states.length > 0 || hasClientBehavior(ast.view);
if (needsClientRuntime) {
const scopePlaceholder = "__WRNEXUS_DYNAMIC_SCOPE__";
html = `<div data-scope="${scopePlaceholder}">${html}</div>`;
}
if (styles.length > 0) {
const css = styles.map(styleEscape).join(`
`);
html = `<style data-wrnexus-style="${attrEscape(ast.name)}">
${css}
</style>${html}`;
}
if (csrBindings.length > 0) {
out.push(`export const __wrnexusCsr = ${JSON.stringify(csrBindings, null, 2)};`);
}
let body = templateEscape(html);
const dynamicStateScope = ast.states.map((state) => `${JSON.stringify(state.name)}: (() => { try { return (${state.expr}); } catch { return undefined; } })()`).join(", ");
loops.forEach((code, idx) => {
body = body.replace(`\x00WRNEACH${idx}\x00`, () => code);
});
const loopConsts = [];
if (loops.length > 0) {
const lists = collectControlExprs(ast.view);
for (const [name, binding] of apiBindings) {
if (binding.mode !== "ssr")
continue;
if (!lists.some((expr) => new RegExp(`\\b${name}\\b`).test(expr)))
continue;
loopConsts.push(` const ${name} = __wrnexusEvalData(await __wrnexusCallApi(${JSON.stringify(binding.path)}, ${JSON.stringify(binding.method)}, ctx), ${JSON.stringify(binding.body)}, ${JSON.stringify(binding.helpers)}, ctx);`);
}
}
const needsSsrRuntime = ssrBindings.length > 0 || loops.length > 0 || ast.states.some((state) => /\bctx\b/.test(state.expr));
if (needsSsrRuntime) {
out.push(ssrRuntimeSource());
out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`);
const decls = loopConsts.length > 0 ? loopConsts.join(`
`) + `
` : "";
out.push(`export default async function ${ast.name}(ctx: any) {
${decls}
const __state = { ${dynamicStateScope} };
const __scopeValue = Object.entries(__state)
.map(([key, value]) => {
const encoded =
typeof value === "number" || typeof value === "boolean"
? String(value)
: JSON.stringify(value == null ? "" : String(value));
return key + ": " + encoded;
})
.join(", ")
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const html = \`${body}\`.replace(
"__WRNEXUS_DYNAMIC_SCOPE__",
__scopeValue,
);
return await __wrnexusRenderSsrBindings(html, ctx);
}`);
} else {
out.push(`export default function ${ast.name}(ctx: any) {
const __state = { ${dynamicStateScope} };
const __scopeValue = Object.entries(__state)
.map(([key, value]) => {
const encoded =
typeof value === "number" || typeof value === "boolean"
? String(value)
: JSON.stringify(value == null ? "" : String(value));
return key + ": " + encoded;
})
.join(", ")
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
return \`${body}\`.replace(
"__WRNEXUS_DYNAMIC_SCOPE__",
__scopeValue,
);
}`);
}
if (ast.apis.length > 0) {
ast.apis.forEach((api, index) => {
const name = `__wrnexusApi_${api.method}_${index}`;
out.push(`// ${api.method} ${apiRoutePath(api.path)}
const ${name} = async (ctx: any) => {${api.body}};`);
});
const entries = ast.apis.map((api, index) => ` ${JSON.stringify(`${api.method} ${apiRoutePath(api.path)}`)}: __wrnexusApi_${api.method}_${index},`);
out.push(`export const __wrnexusApi = {
${entries.join(`
`)}
};`);
const exported = new Set;
ast.apis.forEach((api, index) => {
if (exported.has(api.method))
return;
exported.add(api.method);
out.push(`export const ${api.method} = __wrnexusApi_${api.method}_${index};`);
});
}
if (ast.realtimes.length > 0) {
const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => {
const params = ["ws", ...h.args].join(", ");
return ` ${h.event}(${params}: any) {${h.body}},`;
}));
out.push(`export const websocket = {
${handlers.join(`
`)}
};`);
}
return out.join(`
`) + `
`;
}
function parseForExpr(value) {
const m = /^\s*([A-Za-z_$][\w$]*)\s*(?:,\s*([A-Za-z_$][\w$]*)\s*)?\s+in\s+([\s\S]+?)\s*$/.exec(value);
if (!m)
return null;
return { item: m[1], index: m[2], list: m[3] };
}
function loopVarsOf(node) {
if (node.type !== "element")
return [];
const attr = node.attrs.find((a) => !a.event && a.name === "data-for");
if (!attr)
return [];
const parsed = parseForExpr(attr.value);
return parsed ? [parsed.item, ...parsed.index ? [parsed.index] : []] : [];
}
var JS_RESERVED = new Set([
"class",
"for",
"default",
"function",
"return",
"if",
"else",
"new",
"delete",
"typeof",
"in",
"instanceof",
"void",
"do",
"while",
"switch",
"case",
"break",
"continue",
"this",
"super",
"import",
"export",
"extends",
"var",
"let",
"const",
"null",
"true",
"false",
"try",
"catch",
"finally",
"throw",
"yield",
"await",
"enum",
"with",
"debugger"
]);
function safeRef(name) {
return JS_RESERVED.has(name) ? `__p_${name}` : name;
}
function escLit(s) {
return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
}
function componentBehavior(ast) {
const functions = ast.functions.map((body) => body.trim()).filter(Boolean).join(`
`);
const lifecycle = {
...ast.lifecycle.mount?.trim() ? { mount: ast.lifecycle.mount.trim() } : {},
...ast.lifecycle.update?.trim() ? { update: ast.lifecycle.update.trim() } : {},
...ast.lifecycle.unmount?.trim() ? { unmount: ast.lifecycle.unmount.trim() } : {}
};
const watches = ast.watches.map((watch) => ({
state: watch.state,
body: watch.body.trim()
}));
if (!functions && Object.keys(lifecycle).length === 0 && watches.length === 0) {
return null;
}
return {
functions,
lifecycle,
watches
};
}
function behaviorAttribute(behavior) {
if (!behavior) {
return "";
}
const encoded = import_node_buffer.Buffer.from(JSON.stringify(behavior), "utf8").toString("base64");
return ` data-wrn-behavior="${encoded}"`;
}
var INTERP_RE = /\{([^{}]+)\}/g;
function exprRefsState(expr, stateNames) {
for (const name of stateNames) {
if (new RegExp(`\\b${name}\\b`).test(expr))
return true;
}
return false;
}
function viewHasEvents(nodes) {
return nodes.some((node) => {
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);
});
}
function compileText(raw, ctx) {
let out = "";
let last = 0;
let m;
INTERP_RE.lastIndex = 0;
while (m = INTERP_RE.exec(raw)) {
out += escLit(raw.slice(last, m.index));
const expr = m[1].trim();
if (expr.startsWith("t:")) {
out += escLit(`<span data-t="${attrEscape(expr.slice(2).trim())}"></span>`);
} else if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) {
out += escLit(`{${expr}}`);
} else if (exprRefsState(expr, ctx.stateNames)) {
out += escLit(`<span data-text="${attrEscape(expr)}">`) + `\${__wireHtml(${ctx.resolveExpr(expr)})}` + escLit(`</span>`);
} else if (expr === "content") {
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
} else {
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
}
last = m.index + m[0].length;
}
return out + escLit(raw.slice(last));
}
function compileAttrValue(raw, ctx) {
if (!raw.includes("{"))
return escLit(attrEscape(raw));
let out = "";
let last = 0;
let m;
INTERP_RE.lastIndex = 0;
while (m = INTERP_RE.exec(raw)) {
out += escLit(attrEscape(raw.slice(last, m.index)));
const expr = m[1].trim();
if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) {
out += escLit(`{${expr}}`);
} else {
out += `\${__wireAttr(${ctx.resolveExpr(expr)})}`;
}
last = m.index + m[0].length;
}
return out + escLit(attrEscape(raw.slice(last)));
}
function renderComponentIfNode(node, ctx) {
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, ctx) {
const item = node.item;
const index = node.index ?? "__wi";
const list = ctx.resolveExpr(node.list);
const childCtx = {
...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 + "`; })()}";
}
function renderComponentNode(node, ctx) {
if (node.type === "text")
return compileText(node.value, ctx);
if (node.type === "each") {
return renderComponentEachNode(node, ctx);
}
if (node.type === "if") {
return renderComponentIfNode(node, ctx);
}
if (isComponentTag(node.tag)) {
return renderNestedComponentInvocation(node, ctx);
}
let bindIndex = 0;
const staticClasses = [];
const conditionalClasses = [];
for (const attr of node.attrs) {
if (!attr.event && attr.name === "class") {
staticClasses.push(attr.value);
}
if (!attr.event && attr.name.startsWith("class:")) {
conditionalClasses.push({
className: attr.name.slice("class:".length),
expression: attr.value
});
}
}
const attrs = node.attrs.filter((a) => a.name !== "class" && !a.name.startsWith("class:")).map((a) => {
if (a.event) {
return ` ${eventAttribute(a.name)}="${escLit(attrEscape(a.value))}"`;
}
if (a.boolean) {
return ` ${a.name}`;
}
const rendered = ` ${a.name}="${compileAttrValue(a.value, ctx)}"`;
if (!a.value.includes("{") || !exprRefsState(a.value, ctx.stateNames)) {
return rendered;
}
const marker = attrEscape(JSON.stringify([a.name, a.value]));
return `${rendered} data-wrn-bind-${bindIndex++}="${escLit(marker)}"`;
}).join("");
const initialConditionalClasses = conditionalClasses.map(({ className, expression }) => {
return `\${(${ctx.resolveExpr(expression)}) ? ${JSON.stringify(` ${className}`)} : ""}`;
}).join("");
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.filter(({ expression }) => {
return !ctx.serverLocals || !exprRefsState(expression, ctx.serverLocals);
}).map(({ className, expression }, index) => {
const marker = attrEscape(JSON.stringify([className, expression]));
return ` data-wrn-class-${index}="${escLit(marker)}"`;
}).join("");
const loops = loopVarsOf(node);
const childCtx = loops.length > 0 ? { ...ctx, loopVars: new Set([...ctx.loopVars ?? [], ...loops]) } : ctx;
const allAttrs = `${classAttribute}` + `${classReactiveBinding}` + `${classBindings}` + `${attrs}`;
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
return `<${node.tag}${allAttrs}>`;
}
const inner = node.children.map((child) => renderComponentNode(child, childCtx)).join("");
return `<${node.tag}${allAttrs}>${inner}</${node.tag}>`;
}
function generateComponent(ast) {
const out = [];
const effectiveProps = ast.kind === "layout" && !ast.props.some((prop) => prop.name === "content") ? [
{
name: "content",
default: '""'
},
...ast.props
] : ast.props;
const stateNames = new Set(ast.states.map((s) => s.name));
const nameRefs = new Map;
for (const p of effectiveProps) {
nameRefs.set(p.name, safeRef(p.name));
}
for (const s of ast.states)
nameRefs.set(s.name, safeRef(s.name));
const resolveExpr = (expr) => {
let result = expr;
for (const [name, ref] of nameRefs) {
if (name !== ref)
result = result.replace(new RegExp(`\\b${name}\\b`, "g"), ref);
}
return result;
};
const ctx = { stateNames, resolveExpr };
const viewCode = ast.view.map((node) => renderComponentNode(node, ctx)).join("");
const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
const styleTag = styles.length > 0 ? escLit(`<style data-wrnexus-style="${attrEscape(ast.name)}">
${styles.map(styleEscape).join(`
`)}
</style>`) : "";
const behavior = componentBehavior(ast);
const needsScope = ast.states.length > 0 || viewHasEvents(ast.view) || behavior !== null;
const scopeKeys = [
...effectiveProps.map((prop) => prop.name),
...ast.states.map((state) => state.name)
];
const behaviorAttr = behaviorAttribute(behavior);
const decls = [];
for (const prop of effectiveProps) {
decls.push(` const ${nameRefs.get(prop.name)} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}));`);
}
for (const state of ast.states) {
decls.push(` const ${nameRefs.get(state.name)} = (${resolveExpr(state.expr)});`);
}
const returnExpr = needsScope ? "`" + styleTag + `<div data-scope="\${__scope}"${behaviorAttr}>` + viewCode + "</div>`" : "`" + styleTag + viewCode + "`";
const scopeLine = needsScope && scopeKeys.length > 0 ? ` const __scope = __wrnexusScopeDecl({ ${scopeKeys.map((k) => `${JSON.stringify(k)}: ${nameRefs.get(k)}`).join(", ")} });
` : needsScope ? ` const __scope = "";
` : "";
if (ast.kind === "layout") {
out.push(`export const __wrnexusLayout = ${JSON.stringify(ast.name)};`);
} else {
out.push(`export const __wrnexusComponent = ${JSON.stringify(ast.name)};`);
}
if (behavior) {
out.push(`export const __wrnexusBehavior = ${JSON.stringify(behavior, null, 2)};`);
}
out.push(`function __coerce(v: any, def: any): any {
if (v === undefined || v === null) {
return def;
}
if (typeof def === "number") {
return Number(v);
}
if (typeof def === "boolean") {
return v === true || v === "" || v === "true";
}
if (Array.isArray(def)) {
if (Array.isArray(v)) {
return v;
}
if (typeof v === "string") {
try {
const parsed = JSON.parse(v);
return Array.isArray(parsed) ? parsed : def;
} catch {
return def;
}
}
return def;
}
if (def !== null && typeof def === "object") {
if (
v !== null &&
typeof v === "object" &&
!Array.isArray(v)
) {
return v;
}
if (typeof v === "string") {
try {
const parsed = JSON.parse(v);
return (
parsed !== null &&
typeof parsed === "object" &&
!Array.isArray(parsed)
)
? parsed
: def;
} catch {
return def;
}
}
return def;
}
return String(v);
}
function __wireHtml(v: any): string {
return String(v == null ? "" : v).replace(
/[&<>]/g,
(c) =>
c === "&"
? "&amp;"
: c === "<"
? "&lt;"
: "&gt;",
);
}
function __wireAttr(v: any): string {
return String(v == null ? "" : v).replace(
/[&<>"]/g,
(c) =>
c === "&"
? "&amp;"
: c === "<"
? "&lt;"
: c === ">"
? "&gt;"
: "&quot;",
);
}
function __wireProp(v: any): string {
const value =
v !== null && typeof v === "object"
? JSON.stringify(v)
: String(v == null ? "" : v);
return __wireAttr(value);
}
function __wireRaw(v: any): string {
return String(v == null ? "" : v);
}`);
if (needsScope) {
out.push(`function __wrnexusScopeDecl(obj: Record<string, any>): string {
const lit = (v: any) =>
typeof v === "number" || typeof v === "boolean"
? String(v)
: "'" + String(v).replace(/\\\\/g, "\\\\\\\\").replace(/'/g, "\\\\'").replace(/\\n/g, "\\\\n") + "'";
return Object.keys(obj)
.map((k) => k + ": " + lit(obj[k]))
.join(", ")
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}`);
}
out.push(`export function render(props: Record<string, any> = {}): string {
` + ` const __p = props || {};
` + (decls.length > 0 ? decls.join(`
`) + `
` : "") + scopeLine + ` return ${returnExpr};
` + `}`);
return out.join(`
`) + `
`;
}
function wholeAttributeExpression(value) {
const match = /^\s*\{([\s\S]+)\}\s*$/.exec(value);
return match?.[1]?.trim() || null;
}
function renderPageComponentAttr(attr, dynamicExpressions) {
if (attr.event) {
return ` ${eventAttribute(attr.name)}="${attrEscape(attr.value)}"`;
}
if (attr.boolean) {
return ` ${attr.name}`;
}
const expression = wholeAttributeExpression(attr.value);
if (!expression) {
return ` ${attr.name}="${attrEscape(attr.value)}"`;
}
dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`);
const marker = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
return ` ${attr.name}="${marker}"`;
}
// ../../packages/compiler/src/native-codegen.ts
class NativeCompileError extends Error {
constructor(message) {
super(message);
this.name = "NativeCompileError";
}
}
var tagMap = {
div: "View",
main: "View",
section: "View",
article: "View",
nav: "View",
header: "View",
footer: "View",
aside: "View",
form: "View",
ul: "View",
ol: "View",
li: "View",
p: "Text",
span: "Text",
strong: "Text",
em: "Text",
small: "Text",
label: "Text",
h1: "Text",
h2: "Text",
h3: "Text",
h4: "Text",
h5: "Text",
h6: "Text",
button: "Pressable",
a: "Pressable",
input: "TextInput",
textarea: "TextInput",
img: "Image",
view: "View",
text: "Text",
pressable: "Pressable",
textinput: "TextInput",
image: "Image",
scrollview: "ScrollView",
safeareaview: "SafeAreaView",
flatlist: "FlatList",
activityindicator: "ActivityIndicator"
};
var attrMap = {
class: "style",
className: "style",
src: "source",
alt: "accessibilityLabel",
placeholder: "placeholder",
disabled: "disabled",
value: "value",
href: "__href",
"aria-label": "accessibilityLabel"
};
function expression(value) {
const exact = /^\{([\s\S]+)\}$/.exec(value.trim());
return exact?.[1]?.trim() ?? null;
}
function textJsx(value) {
const pieces = [];
let last = 0;
for (const match of value.matchAll(/\{([^{}]+)\}/g)) {
if (match.index > last)
pieces.push(value.slice(last, match.index));
const expr = match[1].trim();
pieces.push(expr.startsWith("t:") ? `{${JSON.stringify(expr.slice(2).trim())}}` : `{${expr}}`);
last = match.index + match[0].length;
}
pieces.push(value.slice(last));
return pieces.join("").replace(/([<>])/g, (char) => char === "<" ? "&lt;" : "&gt;");
}
function eventBody(value, states) {
let body = expression(value) ?? value;
for (const state of states) {
const cap = state[0].toUpperCase() + state.slice(1);
body = body.replace(new RegExp(`\\b${state}\\+\\+`, "g"), `set${cap}(value => value + 1)`).replace(new RegExp(`\\b${state}--`, "g"), `set${cap}(value => value - 1)`).replace(new RegExp(`\\b${state}\\s*=\\s*([^;]+)`, "g"), `set${cap}($1)`);
}
return `() => { ${body} }`;
}
function renderAttrs2(attrs, states) {
return attrs.map((attr) => {
if (attr.event) {
if (attr.name.startsWith("browser-"))
return "";
const eventName = attr.name.startsWith("mobile-") ? attr.name.slice(7) : attr.name;
const event = eventName === "click" || eventName === "press" ? "onPress" : eventName === "input" || eventName === "change" ? "onChangeText" : `on${eventName[0].toUpperCase()}${eventName.slice(1)}`;
return ` ${event}={${eventBody(attr.value, states)}}`;
}
if (attr.name === "data-native-browser" || attr.name.startsWith("data-native-on-browser-"))
return "";
if (attr.name === "data-native-options" || attr.name === "data-native-only" || attr.name === "data-native-requires" || attr.name === "data-native-unsupported")
return "";
if (attr.name === "data-native-mobile") {
throw new NativeCompileError(`Declarative native capability "${attr.value}" currently targets browser/Capacitor pages. In Expo output, call the installed Expo package from an @mobile-event handler.`);
}
const name = attrMap[attr.name] ?? attr.name;
if (name === "__href")
return ` onPress={() => router.push(${JSON.stringify(attr.value)})}`;
if (name === "source") {
const expr2 = expression(attr.value);
return ` source={${expr2 ? `{ uri: ${expr2} }` : `{ uri: ${JSON.stringify(attr.value)} }`}}`;
}
if (name === "style" && attr.name !== "style") {
return ` style={[${attr.value.split(/\s+/).filter(Boolean).map((value) => `styles[${JSON.stringify(value)}]`).join(", ")} ]}`;
}
if (name === "style") {
const inlineExpression = expression(attr.value);
if (inlineExpression)
return ` style={${inlineExpression}}`;
throw new NativeCompileError('Inline CSS strings are not portable to native; use class="name" and a page style block');
}
if (attr.boolean)
return ` ${name}`;
const expr = expression(attr.value);
return expr ? ` ${name}={${expr}}` : ` ${name}=${JSON.stringify(attr.value)}`;
}).join("");
}
function renderNode2(node, states, key) {
if (node.type === "text")
return textJsx(node.value);
if (node.type === "each") {
const params = node.index ? `${node.item}, ${node.index}` : `${node.item}, __index`;
const body = node.body.map((child, index) => renderNode2(child, states, index === 0 ? node.index ?? "__index" : undefined)).join("");
const empty = node.empty.map((child) => renderNode2(child, states)).join("");
return `{(${node.list})?.length ? (${node.list}).map((${params}) => <>${body}</>) : <>${empty}</>}`;
}
if (node.type === "if") {
const result = node.branches.reduceRight((fallback, branch) => branch.cond === null ? `<>${branch.body.map((child) => renderNode2(child, states)).join("")}</>` : `(${branch.cond}) ? <>${branch.body.map((child) => renderNode2(child, states)).join("")}</> : ${fallback}`, "null");
return `{${result}}`;
}
const nativeOnly = node.attrs.find((attr) => !attr.event && attr.name === "data-native-only")?.value;
if (nativeOnly === "browser" || nativeOnly === "web")
return "";
const nativeTag = tagMap[node.tag.toLowerCase()] ?? (/^[A-Z]/.test(node.tag) ? node.tag : undefined);
if (!nativeTag)
throw new NativeCompileError(`HTML element <${node.tag}> has no native equivalent`);
const attrs = renderAttrs2(node.attrs, states) + (key ? ` key={${key}}` : "");
if (nativeTag === "TextInput" || nativeTag === "Image" || nativeTag === "ActivityIndicator")
return `<${nativeTag}${attrs} />`;
const children = node.children.map((child) => {
if (child.type !== "text")
return renderNode2(child, states);
if (!child.value.trim())
return "";
const text = textJsx(child.value);
return nativeTag === "Text" ? text : `<Text>${text}</Text>`;
}).join("");
return `<${nativeTag}${attrs}>${children}</${nativeTag}>`;
}
function nativeStyles(blocks) {
const entries = [];
for (const block of blocks) {
for (const match of block.matchAll(/\.([A-Za-z_][\w-]*)\s*\{([^}]*)\}/g)) {
const props = [];
for (const declaration of match[2].split(";")) {
const colon = declaration.indexOf(":");
if (colon < 0)
continue;
const name = declaration.slice(0, colon).trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase());
let value = declaration.slice(colon + 1).trim();
if (/^-?\d+(?:\.\d+)?px$/.test(value))
value = Number(value.slice(0, -2));
props.push(`${JSON.stringify(name)}: ${typeof value === "number" ? value : JSON.stringify(value)}`);
}
entries.push(`${JSON.stringify(match[1])}: { ${props.join(", ")} }`);
}
}
return `const styles = StyleSheet.create({ ${entries.join(`,
`)} });`;
}
function generateNative(ast) {
if (ast.kind !== "page")
throw new NativeCompileError("Native route compilation currently accepts page files only");
if (ast.dataApis.length)
throw new NativeCompileError("Data API blocks are not yet portable to native screens; fetch through the generated native backend helper");
const states = new Set(ast.states.map((state) => state.name));
const hooks = ast.states.map((state) => {
const cap = state.name[0].toUpperCase() + state.name.slice(1);
return ` const [${state.name}, set${cap}] = useState(${state.expr});`;
}).join(`
`);
const body = ast.view.map((node) => renderNode2(node, states)).join("");
return `// generated from .wrn for Expo/React Native
import React, { useState } from "react";
import { ActivityIndicator, FlatList, Image, Pressable, SafeAreaView, ScrollView, StyleSheet, Text, TextInput, View } from "react-native";
import { useRouter } from "expo-router";
export default function ${ast.name}() {
const router = useRouter();
${hooks}
return <>${body}</>;
}
${nativeStyles(ast.styles)}
`;
}
// ../../packages/compiler/src/index.ts
function compileNativeWireFile(source) {
return generateNative(parse(source));
}
function compileWireFile(source) {
const ast = parse(source);
return `// compiled from .wrn
${generate(ast)}`;
}
function compile(source) {
const diagnostics = [];
try {
const ast = parse(source);
return { code: `// compiled from .wrn
${generate(ast)}`, ast, diagnostics };
} catch (err) {
if (err instanceof ParseError)
diagnostics.push(err.message);
throw err;
}
}