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 `); i += 2; skipWs(); const close = readTagName(); if (close !== tag) return fail(`Mismatched , expected `); skipWs(); if (src[i] !== ">") return fail(`Expected '>' to close `); 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 + 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, "&").replace(/"/g, """).replace(//g, ">"); } 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) => ``); } function htmlTextEscape(value) { return value.replace(/[&<>]/g, (c) => c === "&" ? "&" : c === "<" ? "<" : ">"); } 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 `${baked}`; }); } 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(``); } 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(`
") + inner + escLit("
"); } if (VOID_ELEMENTS.has(node.tag.toLowerCase())) { return escLit(`<${node.tag}`) + attrs + escLit(">"); } return escLit(`<${node.tag}`) + attrs + escLit(">") + inner + escLit(``); } 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}`; } 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 `
${inner}
`; } 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 `
${inner}
`; } function ssrMarker(bindings, binding) { const marker = ``; 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 = { "&": "&", "<": "<", ">": ">", "\\"": """, "'": "'" }; 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 === "&" ? "&" : character === "<" ? "<" : character === ">" ? ">" : """, ); } async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise { 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 { 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 = `
${html}
`; } if (styles.length > 0) { const css = styles.map(styleEscape).join(` `); html = `${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, "&") .replace(/"/g, """) .replace(//g, ">"); 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, "&") .replace(/"/g, """) .replace(//g, ">"); 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(``); } else if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) { out += escLit(`{${expr}}`); } else if (exprRefsState(expr, ctx.stateNames)) { out += escLit(``) + `\${__wireHtml(${ctx.resolveExpr(expr)})}` + escLit(``); } 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}`; } 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(``) : ""; 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 + `
` + viewCode + "
`" : "`" + 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 === "&" ? "&" : c === "<" ? "<" : ">", ); } function __wireAttr(v: any): string { return String(v == null ? "" : v).replace( /[&<>"]/g, (c) => c === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : """, ); } 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 { 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, "&") .replace(/"/g, """) .replace(//g, ">"); }`); } out.push(`export function render(props: Record = {}): 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 === "<" ? "<" : ">"); } 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}`; }).join(""); return `<${nativeTag}${attrs}>${children}`; } 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; } }