release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-02 23:18:51 +05:30
parent 87507edf59
commit 586a6db8ff
625 changed files with 243608 additions and 11210 deletions
+35
View File
@@ -1,9 +1,44 @@
# @wrnexus/compiler
## Partial-static rendering
Pages can select `render = "partial-static"` and divide their view with `<Static>` and
`<Dynamic>` boundaries. The compiler emits a build-only shell renderer that never evaluates
dynamic-boundary children. `wrnexus build` expands static component mounts into
`dist/partial-shells.json`, records byte/region evidence in `build-report.json`, and embeds
the shell in the production route manifest. At request time the production runtime retains
request-aware layouts, locale/theme metadata and security nonces while streaming dynamic
regions into stable placeholders.
> Compiler for the `.wrn` language — tokenizes, parses, and lowers `.wrn` page and component files to TypeScript.
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
Production adapters use `analyzeRuntimeImports` before bundling. Edge, worker,
service-worker, and browser targets reject Node filesystem, TCP, and process
modules with `WRN-RUNTIME-CAPABILITY`. Package manifests can declare supported
`wrnexus.runtimes` and required `wrnexus.requires` capabilities; discovery fails
when the selected deployment cannot satisfy them.
## Server actions
```wrn
action createUser using CreateUserSchema {
const user = await users.create(input)
invalidate("users")
return user
}
view {
<form @submit="createUser">...</form>
}
```
The compiler produces a schema-aware server registry, a fully inferred action
client, and progressively enhanced form metadata. The shared runtime performs
validation, authentication/permission checks, CSRF verification, serialization,
invalidation reporting, and browser lifecycle events.
## Overview
`@wrnexus/compiler` turns `.wrn` source into TypeScript that targets the framework's runtime primitives. A `.wrn` file declares either a `page` (a route) or a `component` (a reusable, prop-driven fragment) with blocks for `state`, `view` (plain HTML), `seo`, `style`, `functions`, `api`, `ssr`/`client` data bindings, and `realtime` websocket handlers. The pipeline is `source → Lexer → parse() → PageAst → generate() → TypeScript`. It is a build/server-side library — the WrNexus dev loader calls it to compile `.wrn` files on the fly, surfacing `ParseError` as a readable error page.
+4 -2
View File
@@ -1,13 +1,15 @@
{
"name": "@wrnexus/compiler",
"version": "0.7.0",
"version": "0.8.0",
"type": "module",
"main": "src/index.ts",
"exports": {
".": "./src/index.ts"
},
"dependencies": {
"@wrnexus/csr": "workspace:*",
"@wrnexus/syntax": "workspace:*",
"@wrnexus/store": "workspace:*"
"@wrnexus/store": "workspace:*",
"@wrnexus/validation": "workspace:*"
}
}
+212 -3
View File
@@ -15,6 +15,185 @@ export interface RuntimeRequirements {
needsServerRuntime: boolean;
hydrationStrategy: string | null;
reasons: string[];
optimization: OptimizationReport;
cachePolicy: Record<string, string>;
requiredPermission: string | null;
}
export interface OptimizationReport {
staticNodes: number;
reactiveRegions: number;
eliminatedBranches: number;
unusedState: string[];
unusedHandlers: string[];
constantProps: string[];
unusedLocalCssClasses: string[];
batchableStateUpdates: number;
memoizableComponents: string[];
preloadDependencies: string[];
serverOnlyModules: string[];
}
function identifiers(value: string): Set<string> {
return new Set(value.match(/[A-Za-z_$][\w$]*/g) ?? []);
}
function literalBoolean(expression: string | null): boolean | undefined {
if (expression === null) return true;
const value = expression.trim();
if (value === "true") return true;
if (
value === "false" ||
value === "null" ||
value === "undefined" ||
value === "0" ||
value === "''" ||
value === '""'
)
return false;
if (/^-?(?:[1-9]\d*|0?\.\d+)$/.test(value) || /^(['"]).+\1$/.test(value)) return true;
return undefined;
}
function optimizeNodes(nodes: ViewNode[], report: { eliminated: number }): ViewNode[] {
const output: ViewNode[] = [];
for (const node of nodes) {
if (node.type === "element")
output.push({
...node,
attrs: node.attrs.map((attribute) => ({ ...attribute })),
children: optimizeNodes(node.children, report),
});
else if (node.type === "each")
output.push({
...node,
body: optimizeNodes(node.body, report),
empty: optimizeNodes(node.empty, report),
});
else if (node.type === "if") {
let selected: ViewNode[] | undefined;
let dynamic = false;
for (const branch of node.branches) {
const value = literalBoolean(branch.cond);
if (value === undefined) {
dynamic = true;
break;
}
report.eliminated++;
if (value) {
selected = branch.body;
break;
}
}
if (dynamic)
output.push({
...node,
branches: node.branches.map((branch) => ({
...branch,
body: optimizeNodes(branch.body, report),
})),
});
else if (selected) output.push(...optimizeNodes(selected, report));
} else output.push({ ...node });
}
return output;
}
/** Safe compile-time folding for literal conditional branches. */
export function optimizeAst(ast: PageAst): { ast: PageAst; eliminatedBranches: number } {
const report = { eliminated: 0 };
return {
ast: { ...ast, view: optimizeNodes(ast.view, report) },
eliminatedBranches: report.eliminated,
};
}
export function analyzeOptimizations(ast: PageAst): OptimizationReport {
const used = new Set<string>();
let staticNodes = 0;
let reactiveRegions = 0;
const componentNames = new Set<string>();
const staticClasses = new Set<string>();
const visit = (nodes: ViewNode[]) => {
for (const node of nodes) {
if (node.type === "text") {
const refs = identifiers(node.value);
refs.forEach((name) => used.add(name));
if (node.value.includes("{")) reactiveRegions++;
else staticNodes++;
} else if (node.type === "element") {
if (/^[A-Z]/.test(node.tag)) componentNames.add(node.tag);
let reactive = false;
for (const attribute of node.attrs) {
identifiers(attribute.value).forEach((name) => used.add(name));
reactive ||= attribute.event || attribute.value.includes("{");
if (attribute.name === "class" && !attribute.value.includes("{"))
for (const name of attribute.value.split(/\s+/)) if (name) staticClasses.add(name);
}
if (reactive) reactiveRegions++;
else staticNodes++;
visit(node.children);
} else if (node.type === "each") {
identifiers(`${node.list} ${node.key ?? ""}`).forEach((name) => used.add(name));
reactiveRegions++;
visit(node.body);
visit(node.empty);
} else {
for (const branch of node.branches) {
identifiers(branch.cond ?? "").forEach((name) => used.add(name));
visit(branch.body);
}
reactiveRegions++;
}
}
};
visit(ast.view);
const handlerReferences = new Set(used);
const executable = [
...ast.runtimeFunctions.map((fn) => fn.body),
...ast.functions,
...ast.effects.map((effect) => effect.body),
...ast.watches.map((watch) => watch.body),
...ast.actions.map((action) => action.body),
].join("\n");
identifiers(executable).forEach((name) => used.add(name));
const localCss = new Set(
ast.styles.flatMap((style) =>
[...style.matchAll(/\.([_a-zA-Z][\w-]*)/g)].map((match) => match[1]!),
),
);
const optimized = optimizeAst(ast);
const assignmentCounts = ast.runtimeFunctions.map(
(fn) =>
ast.states.filter((state) =>
new RegExp(`\\b${state.name}\\s*(?:[+*/-]?=|\\+\\+|--)`).test(fn.body),
).length,
);
return {
staticNodes,
reactiveRegions,
eliminatedBranches: optimized.eliminatedBranches,
unusedState: ast.states.filter((state) => !used.has(state.name)).map((state) => state.name),
unusedHandlers: ast.runtimeFunctions
.filter((fn) => fn.runtime !== "server" && !handlerReferences.has(fn.name))
.map((fn) => fn.name),
constantProps: ast.props
.filter((prop) =>
/^(?:-?\d+(?:\.\d+)?|true|false|null|(['"]).*\1)$/.test(prop.default.trim()),
)
.map((prop) => prop.name),
unusedLocalCssClasses: [...localCss].filter((name) => !staticClasses.has(name)).sort(),
batchableStateUpdates: assignmentCounts
.filter((count) => count > 1)
.reduce((sum, count) => sum + count - 1, 0),
memoizableComponents: [...componentNames].sort(),
preloadDependencies: ast.structuredImports
.filter((entry) => !entry.typeOnly && !entry.source.startsWith("node:"))
.map((entry) => entry.source),
serverOnlyModules: ast.structuredImports
.filter((entry) => entry.source.startsWith("node:") || ast.runtime === "server")
.map((entry) => entry.source),
};
}
function hasEvent(nodes: ViewNode[]): boolean {
@@ -67,12 +246,42 @@ export function analyzeRuntimeRequirements(ast: PageAst): RuntimeRequirements {
else if (interactive) kind = "static-interactive";
else kind = "static";
if (ast.renderMode === "static") {
kind = "static";
reasons.push("explicit static rendering");
} else if (ast.renderMode === "server") {
kind = requestData ? "request-ssr" : "static";
reasons.push("explicit server rendering");
} else if (ast.renderMode === "client") {
kind = "static-interactive";
reasons.push("explicit client rendering");
} else if (ast.renderMode === "partial-static") {
kind = "streaming-ssr";
reasons.push("partial-static shell with streamed dynamic regions");
}
const clientDisabled = ast.renderMode === "static" || ast.renderMode === "server";
const serverDisabled = ast.renderMode === "client";
return {
kind,
canPrerender: kind === "static" || kind === "static-interactive",
needsClientRuntime: interactive && ast.hydrate !== "none" && ast.runtime !== "server",
needsServerRuntime: requestData || authenticated || streaming || ast.runtime === "server",
hydrationStrategy: interactive ? (ast.hydrate ?? "load") : null,
needsClientRuntime:
!clientDisabled &&
(interactive || ast.renderMode === "client") &&
ast.hydrate !== "none" &&
ast.runtime !== "server",
needsServerRuntime:
!serverDisabled &&
(requestData ||
authenticated ||
streaming ||
ast.renderMode === "server" ||
["server", "edge", "worker", "service-worker"].includes(ast.runtime ?? "")),
hydrationStrategy: clientDisabled ? null : interactive ? (ast.hydrate ?? "load") : null,
reasons,
optimization: analyzeOptimizations(ast),
cachePolicy: { ...(ast.cache ?? {}) },
requiredPermission: ast.security.permission ?? null,
};
}
+371 -19
View File
@@ -21,6 +21,7 @@ import { VOID_ELEMENTS, type Attr, type DataMode, type PageAst, type ViewNode }
import { eraseFunctionTypes, runtimeTypeOf } from "./types.ts";
import { stripRuntimeFunctionModifiers } from "@wrnexus/syntax";
import { generateStoreModule } from "./store-codegen.ts";
import { optimizeAst } from "./analysis.ts";
interface RenderBinding {
method: string;
@@ -352,6 +353,55 @@ function renderLoopBody(node: ViewNode): string {
const inner = node.children.map(renderLoopBody).join("");
if (node.tag === "Static") return inner;
if (node.tag === "Dynamic")
return (
escLit('<wrn-dynamic-region data-wrn-dynamic="true">') +
inner +
escLit("</wrn-dynamic-region>")
);
if (node.tag === "KeepAlive") {
const key = node.attrs.find((attribute) => attribute.name === "key")?.value ?? "default";
return (
escLit('<div data-wrn-keepalive="') +
bakeLoopAttr(key) +
escLit(`">`) +
inner +
escLit("</div>")
);
}
if (node.tag === "Portal") {
const target = node.attrs.find((attribute) => attribute.name === "to")?.value ?? "body";
return (
escLit('<div data-wrn-portal="') +
bakeLoopAttr(target) +
escLit('">') +
inner +
escLit("</div>")
);
}
if (node.tag === "Transition") {
const name =
node.attrs.find((attribute) => attribute.name === "name")?.value ?? "wrn-transition";
return (
escLit('<div data-wrn-transition="') +
bakeLoopAttr(name) +
escLit('">') +
inner +
escLit("</div>")
);
}
if (node.tag === "Component") {
const selected = node.attrs.find((attribute) => attribute.name === "is")?.value ?? "";
return (
escLit('<div data-wrn-dynamic-component="') +
bakeLoopAttr(selected) +
escLit('">') +
inner +
escLit("</div>")
);
}
if (componentTag) {
return (
escLit(`<div data-component="${attrEscape(node.tag)}"`) +
@@ -417,6 +467,7 @@ function compileIfExpr(node: IfNode): string {
*/
function collectControlExprs(nodes: ViewNode[], out: string[] = []): string[] {
for (const node of nodes) {
if (node.type === "text") continue;
if (node.type === "each") {
out.push(node.list);
collectControlExprs(node.body, out);
@@ -450,6 +501,73 @@ function renderNode(
return `\x00WRNEACH${loops.length - 1}\x00`;
}
if (node.tag === "Static" || node.tag === "Dynamic") {
const inner = node.children
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
.join("");
return node.tag === "Static"
? inner
: `<wrn-dynamic-region data-wrn-dynamic="true">${inner}</wrn-dynamic-region>`;
}
if (node.tag === "Portal" || node.tag === "Transition" || node.tag === "Component") {
const inner = node.children
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
.join("");
const attribute =
node.tag === "Portal"
? "data-wrn-portal"
: node.tag === "Transition"
? "data-wrn-transition"
: "data-wrn-dynamic-component";
const source = node.tag === "Portal" ? "to" : node.tag === "Transition" ? "name" : "is";
const fallback =
node.tag === "Portal" ? "body" : node.tag === "Transition" ? "wrn-transition" : "";
const original = node.attrs.find((item) => item.name === source);
const rendered = original
? renderAttrs([{ ...original, name: attribute }], undefined, reactive, loops)
: ` ${attribute}="${attrEscape(fallback)}"`;
return `<div${rendered}>${inner}</div>`;
}
if (node.tag === "Async") {
const source = attrValue(node.attrs, "source") ?? "data";
const retries = attrValue(node.attrs, "retries") ?? "2";
const serverResolved = attrValue(node.attrs, "data-wrn-async-server") === "true";
const asyncIndex = serverResolved ? loops.push("") - 1 : -1;
const branch = (name: string) => {
const element = node.children.find(
(child): child is Extract<ViewNode, { type: "element" }> =>
child.type === "element" && child.tag === name,
);
return (element?.children ?? [])
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
.join("");
};
const loading = branch("Loading");
const success = branch("Success");
const error = branch("Error");
let initial = loading;
if (serverResolved) {
const nested = (value: string) => value.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
const sourcePattern = source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const serverSuccess = success.replace(
new RegExp(`\\{\\s*(${sourcePattern}(?:\\.[A-Za-z_$][\\w$]*)*)\\s*\\}`, "g"),
(_whole, expression: string) => `\${__wrnexusEscapeHtml(${expression})}`,
);
loops[asyncIndex] =
`\${ctx[${JSON.stringify(source)}] !== undefined ? \`${nested(serverSuccess)}\` : \`${nested(loading)}\`}`;
initial = `\x00WRNEACH${asyncIndex}\x00`;
}
return `<section data-wrn-async="${attrEscape(source)}" data-wrn-async-retries="${attrEscape(retries)}"${serverResolved ? ' data-wrn-async-resolved="true"' : ""} aria-busy="${serverResolved ? "false" : "true"}"><div data-wrn-async-content>${initial}</div><template data-wrn-async-loading>${loading}</template><template data-wrn-async-success>${success}</template><template data-wrn-async-error>${error}</template></section>`;
}
if (node.tag === "KeepAlive") {
const key = attrValue(node.attrs, "key") ?? "default";
const inner = node.children
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
.join("");
return `<div data-wrn-keepalive="${attrEscape(key)}">${inner}</div>`;
}
if (isComponentTag(node.tag)) {
return renderPageComponentInvocation(
node,
@@ -848,7 +966,9 @@ function generateSsrStateAliases(stateNames: string[]): string {
}
function hydrationAttribute(ast: PageAst): string {
const strategy = ast.hydrate ?? "load";
const strategy = ["static", "server"].includes(ast.renderMode ?? "")
? "none"
: (ast.hydrate ?? "load");
const hasBrowserModule = ast.runtimeFunctions.some((fn) =>
["legacy", "client", "shared"].includes(fn.runtime),
);
@@ -879,13 +999,88 @@ function publicOutputNames(ast: PageAst): string[] {
];
}
function prepareActionForms(nodes: ViewNode[], actions: ReadonlySet<string>): void {
for (const node of nodes) {
if (node.type === "text") continue;
if (node.type === "each") {
prepareActionForms(node.body, actions);
prepareActionForms(node.empty, actions);
continue;
}
if (node.type === "if") {
node.branches.forEach((branch) => prepareActionForms(branch.body, actions));
continue;
}
prepareActionForms(node.children, actions);
if (node.tag.toLowerCase() !== "form") continue;
const submit = node.attrs.find((attr) => attr.event && attr.name === "submit");
if (!submit || !actions.has(submit.value.trim())) continue;
const name = submit.value.trim();
node.attrs = node.attrs.filter((attr) => attr !== submit);
if (!node.attrs.some((attr) => !attr.event && attr.name === "method")) {
node.attrs.push({ name: "method", value: "post", event: false });
}
node.attrs.push({ name: "data-wrn-action", value: name, event: false });
node.children.unshift({
type: "element",
tag: "input",
attrs: [
{ name: "type", value: "hidden", event: false },
{ name: "name", value: "_wrnexus_action", event: false },
{ name: "value", value: name, event: false },
],
children: [],
});
}
}
function markServerAsyncBoundaries(nodes: ViewNode[], serverLoads: ReadonlySet<string>): void {
for (const node of nodes) {
if (node.type === "text") continue;
if (node.type === "each") {
markServerAsyncBoundaries(node.body, serverLoads);
markServerAsyncBoundaries(node.empty, serverLoads);
continue;
}
if (node.type === "if") {
node.branches.forEach((branch) => markServerAsyncBoundaries(branch.body, serverLoads));
continue;
}
if (node.tag === "Async") {
const source = attrValue(node.attrs, "source") ?? "data";
if (
serverLoads.has(source) &&
!node.attrs.some((attribute) => attribute.name === "data-wrn-async-server")
) {
node.attrs.push({ name: "data-wrn-async-server", value: "true", event: false });
}
}
markServerAsyncBoundaries(node.children, serverLoads);
}
}
export function generate(ast: PageAst): string {
ast = optimizeAst(ast).ast;
if (ast.kind === "global-store" || ast.kind === "page-store") return generateStoreModule(ast);
if (ast.kind === "component" || ast.kind === "layout") {
return generateComponent(ast);
}
const out: string[] = [];
prepareActionForms(ast.view, new Set(ast.actions.map((action) => action.name)));
markServerAsyncBoundaries(
ast.view,
new Set(
ast.loads
.filter((load) => load.mode === "server" && !load.deferred && load.name)
.map((load) => load.name!),
),
);
if (ast.actions.length > 0) {
out.push(
`import { createActionClient } from "@wrnexus/csr";\nimport type { InferSchema } from "@wrnexus/validation";`,
);
}
if (ast.imports.length > 0) out.push(generatedImports(ast).join("\n"));
const ssrBindings: SsrBinding[] = [];
const csrBindings: CsrBinding[] = [];
@@ -909,11 +1104,19 @@ export function generate(ast: PageAst): string {
`export const layout = ${ast.layoutIsSymbol ? ast.layout : JSON.stringify(ast.layout)};`,
);
out.push(`export const __wrnexusRuntime = ${JSON.stringify(ast.runtime ?? "universal")};`);
out.push(`export const __wrnexusHydrate = ${JSON.stringify(ast.hydrate ?? "load")};`);
out.push(`export const __wrnexusRender = ${JSON.stringify(ast.renderMode ?? "hybrid")};`);
out.push(
`export const __wrnexusHydrate = ${JSON.stringify(["static", "server"].includes(ast.renderMode ?? "") ? "none" : (ast.hydrate ?? "load"))};`,
);
out.push(`export const __wrnexusHydrationId = ${JSON.stringify(hydrationId(ast))};`);
if (Object.keys(ast.cache ?? {}).length > 0)
out.push(`export const __wrnexusCache = ${JSON.stringify(ast.cache, null, 2)};`);
if (Object.keys(ast.security).length > 0) {
out.push(`export const __wrnexusSecurity = ${JSON.stringify(ast.security, null, 2)};`);
}
if (Object.keys(ast.navigation).length > 0) {
out.push(`export const __wrnexusNavigation = ${JSON.stringify(ast.navigation, null, 2)};`);
}
// --- View -> default page component ---
const browserStates = ast.states.filter((state) => state.runtime !== "server");
@@ -960,6 +1163,10 @@ export function generate(ast: PageAst): string {
if (pageStyleTag) {
html = `${pageStyleTag}${html}`;
}
if (ast.renderMode === "client") {
const clientRoot = hydrationId(ast);
html = `<div data-wrn-client-root="${clientRoot}" aria-busy="true"></div><template data-wrn-client-template="${clientRoot}">${html}</template>`;
}
const pageStyleExport = localStyleExport(ast, styles);
if (pageStyleExport) out.push(pageStyleExport);
if (csrBindings.length > 0) {
@@ -972,6 +1179,14 @@ export function generate(ast: PageAst): string {
// Escape the static HTML for the template literal, then swap loop sentinels for
// their real `${…}` code (which must NOT be escaped).
let body = templateEscape(html);
let staticShellBody: string | undefined;
if (ast.renderMode === "partial-static") {
const shellHtml = html.replace(
/<wrn-dynamic-region\b[^>]*>[\s\S]*?<\/wrn-dynamic-region>/gi,
'<wrn-dynamic-region data-wrn-dynamic="true"></wrn-dynamic-region>',
);
staticShellBody = templateEscape(shellHtml);
}
const dynamicStateScope = ast.states
.map(
(state) =>
@@ -994,9 +1209,16 @@ export function generate(ast: PageAst): string {
(entry) => ` const ${entry.local} = await ctx.__wrnexusUseStore(${entry.internal});`,
)
.join("\n");
const serverLoadAliases = ast.loads
.filter((load) => load.mode === "server" && !load.deferred && load.name)
.map((load) => ` const ${load.name} = ctx[${JSON.stringify(load.name)}];`)
.join("\n");
loops.forEach((code, idx) => {
body = body.replace(`\x00WRNEACH${idx}\x00`, () => code);
if (staticShellBody?.includes(`\x00WRNEACH${idx}\x00`)) {
staticShellBody = staticShellBody.replace(`\x00WRNEACH${idx}\x00`, () => code);
}
});
// Server loops iterate raw SSR data. Declare a named const for every `ssr` data
@@ -1024,6 +1246,7 @@ export function generate(ast: PageAst): string {
out.push(
`export default async function ${ast.name}(ctx: any) {
${storeDeclarations}
${serverLoadAliases}
${decls}
const __state: ${stateType} = { ${dynamicStateScope} };
${ssrStateAliases}
@@ -1055,6 +1278,7 @@ export function generate(ast: PageAst): string {
out.push(
`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: any) {
${storeDeclarations}
${serverLoadAliases}
const __state: ${stateType} = { ${dynamicStateScope} };
${ssrStateAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
@@ -1081,30 +1305,122 @@ export function generate(ast: PageAst): string {
);
}
if (staticShellBody !== undefined) {
out.push(
`export async function __wrnexusBuildStaticShell(ctx: any = {}) {
${storeDeclarations}
${serverLoadAliases}
${loopConsts.length > 0 ? loopConsts.join("\n") : ""}
const __state: ${stateType} = { ${dynamicStateScope} };
${ssrStateAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
const __scopeValue = Object.entries(__hydrationState)
.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 \`${staticShellBody}\`.replace("__WRNEXUS_DYNAMIC_SCOPE__", __scopeValue);
}`,
);
}
if (ast.loads.length > 0) {
const serverLoads = ast.loads.filter((entry) => entry.mode === "server");
const clientLoads = ast.loads.filter((entry) => entry.mode === "client");
if (serverLoads.length > 0) {
out.push(
`export async function __wrnexusLoad(ctx: any) {\n${serverLoads.map((entry) => entry.body).join("\n")}\n}`,
);
}
if (clientLoads.length > 0) {
out.push(
`export async function __wrnexusClientLoad(ctx: any) {
${clientLoads.map((entry) => entry.body).join("\n")}
}`,
);
}
const serverLoads = ast.loads.filter((entry) => entry.mode === "server" && !entry.deferred);
const publicClientLoads = ast.loads.filter(
(entry) => entry.mode === "client" || entry.deferred,
);
const namedByName = new Map(
ast.loads.filter((entry) => entry.name).map((entry) => [entry.name!, entry]),
);
const clientNames = new Set(
publicClientLoads.flatMap((entry) => (entry.name ? [entry.name] : [])),
);
const includeDependencies = (name: string): void => {
for (const dependency of namedByName.get(name)?.dependsOn ?? []) {
if (clientNames.has(dependency)) continue;
clientNames.add(dependency);
includeDependencies(dependency);
}
};
for (const name of [...clientNames]) includeDependencies(name);
const clientLoads = ast.loads.filter((entry) => !entry.name || clientNames.has(entry.name));
const renderLoads = (
exportName: string,
execution: typeof ast.loads,
exposed: typeof ast.loads,
): string => {
const declarations = execution
.filter((entry) => entry.name)
.map((entry) => {
const dependencies = (entry.dependsOn ?? [])
.map((dependency) => `const ${dependency} = await __load_${dependency}();`)
.join("\n");
return ` let __promise_${entry.name}: Promise<unknown> | undefined;
const __load_${entry.name} = () => (__promise_${entry.name} ??= (async () => {
${dependencies}
${entry.body}
})());`;
})
.join("\n");
const visible = exposed.filter((entry) => entry.name);
return `export async function ${exportName}(ctx: any) {
${exposed
.filter((entry) => !entry.name)
.map((entry) => entry.body)
.join("\n")}
${declarations}
${
visible.length
? ` const __values = await Promise.all([${visible.map((entry) => `__load_${entry.name}()`).join(", ")}]);
return { ${visible.map((entry, index) => `${JSON.stringify(entry.name)}: __values[${index}]`).join(", ")} };`
: ""
}
}`;
};
if (serverLoads.length > 0) out.push(renderLoads("__wrnexusLoad", serverLoads, serverLoads));
if (publicClientLoads.length > 0)
out.push(renderLoads("__wrnexusClientLoad", clientLoads, publicClientLoads));
}
if (ast.actions.length > 0) {
for (const action of ast.actions) {
out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`);
if (!action.schema) {
out.push(
`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`,
);
continue;
}
out.push(`export async function ${action.name}(input: any, ctx: any) {
const invalidate = (...tags: string[]) => {
const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []);
bucket.push(...tags.flat());
};
${action.body}
}`);
}
out.push(
`export const __wrnexusActions = { ${ast.actions.map((action) => action.name).join(", ")} };`,
`export const __wrnexusActions = { ${ast.actions
.map(
(action) =>
`${action.name}: { run: ${action.name}, schema: ${action.schema ?? "undefined"} }`,
)
.join(", ")} };`,
);
out.push(`export const __wrnexusActionClients = {
${ast.actions
.map(
(action) =>
` ${action.name}: createActionClient<${action.schema ? `InferSchema<typeof ${action.schema}>` : "Record<string, unknown>"}, Awaited<ReturnType<typeof ${action.name}>>>("", ${JSON.stringify(action.name)}),`,
)
.join("\n")}
};`);
}
// --- API blocks -> method handlers ---
@@ -1545,6 +1861,34 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
return renderComponentIfNode(node, ctx);
}
if (node.tag === "Static" || node.tag === "Dynamic") {
const inner = node.children.map((child) => renderComponentNode(child, ctx)).join("");
return node.tag === "Static"
? inner
: `<wrn-dynamic-region data-wrn-dynamic="true">${inner}</wrn-dynamic-region>`;
}
if (node.tag === "KeepAlive") {
const key = node.attrs.find((attribute) => attribute.name === "key")?.value ?? "default";
const inner = node.children.map((child) => renderComponentNode(child, ctx)).join("");
return `<div data-wrn-keepalive="${compileAttrValue(key, ctx)}">${inner}</div>`;
}
if (node.tag === "Portal" || node.tag === "Transition" || node.tag === "Component") {
const inner = node.children.map((child) => renderComponentNode(child, ctx)).join("");
const attribute =
node.tag === "Portal"
? "data-wrn-portal"
: node.tag === "Transition"
? "data-wrn-transition"
: "data-wrn-dynamic-component";
const source = node.tag === "Portal" ? "to" : node.tag === "Transition" ? "name" : "is";
const fallback =
node.tag === "Portal" ? "body" : node.tag === "Transition" ? "wrn-transition" : "";
const raw = node.attrs.find((item) => item.name === source)?.value ?? fallback;
return `<div ${attribute}="${compileAttrValue(raw, ctx)}">${inner}</div>`;
}
if (isComponentTag(node.tag)) {
return renderNestedComponentInvocation(node, ctx);
}
@@ -1865,11 +2209,19 @@ function generateComponent(ast: PageAst): string {
out.push(`export const __wrnexusComponent = ${JSON.stringify(ast.name)};`);
}
out.push(`export const __wrnexusRuntime = ${JSON.stringify(ast.runtime ?? "universal")};`);
out.push(`export const __wrnexusHydrate = ${JSON.stringify(ast.hydrate ?? "load")};`);
out.push(`export const __wrnexusRender = ${JSON.stringify(ast.renderMode ?? "hybrid")};`);
out.push(
`export const __wrnexusHydrate = ${JSON.stringify(["static", "server"].includes(ast.renderMode ?? "") ? "none" : (ast.hydrate ?? "load"))};`,
);
out.push(`export const __wrnexusHydrationId = ${JSON.stringify(hydrationId(ast))};`);
if (Object.keys(ast.cache ?? {}).length > 0)
out.push(`export const __wrnexusCache = ${JSON.stringify(ast.cache, null, 2)};`);
if (Object.keys(ast.security).length > 0) {
out.push(`export const __wrnexusSecurity = ${JSON.stringify(ast.security, null, 2)};`);
}
if (Object.keys(ast.navigation).length > 0) {
out.push(`export const __wrnexusNavigation = ${JSON.stringify(ast.navigation, null, 2)};`);
}
const componentStyleExport = localStyleExport(ast, styles);
if (componentStyleExport) out.push(componentStyleExport);
+10 -2
View File
@@ -15,6 +15,8 @@ import {
type PageAst,
type WrnDiagnostic,
} from "@wrnexus/syntax";
export { formatWrn } from "@wrnexus/syntax";
export type { FormatWrnOptions } from "@wrnexus/syntax";
import { generate } from "./codegen.ts";
import { generateNative } from "./native-codegen.ts";
@@ -35,8 +37,14 @@ export { generateStoreBrowserModule, generateStoreModule } from "./store-codegen
export { createComponentContract } from "./component-contract.ts";
export { resolveWrnImport, resolveWrnImports } from "./import-resolver.ts";
export { createWrnSourceMap } from "./source-map.ts";
export { analyzeRuntimeRequirements } from "./analysis.ts";
export type { RouteExecutionKind, RuntimeRequirements } from "./analysis.ts";
export { analyzeOptimizations, analyzeRuntimeRequirements, optimizeAst } from "./analysis.ts";
export type { OptimizationReport, RouteExecutionKind, RuntimeRequirements } from "./analysis.ts";
export { analyzeRuntimeImports, runtimeCapabilities } from "./runtime-capabilities.ts";
export type {
DeploymentRuntime,
RuntimeCapability,
RuntimeCapabilityDiagnostic,
} from "./runtime-capabilities.ts";
export { generateNative, NativeCompileError } from "./native-codegen.ts";
export { Lexer, LexError } from "@wrnexus/syntax";
export { eraseFunctionTypes, inferredRuntimeType, runtimeTypeOf } from "@wrnexus/syntax";
@@ -0,0 +1,69 @@
export type DeploymentRuntime = "bun" | "node" | "edge" | "worker" | "service-worker" | "browser";
export type RuntimeCapability =
"filesystem" | "tcp" | "process" | "websocket" | "crypto" | "streams" | "background-tasks";
const CAPABILITIES: Record<DeploymentRuntime, ReadonlySet<RuntimeCapability>> = {
bun: new Set([
"filesystem",
"tcp",
"process",
"websocket",
"crypto",
"streams",
"background-tasks",
]),
node: new Set([
"filesystem",
"tcp",
"process",
"websocket",
"crypto",
"streams",
"background-tasks",
]),
edge: new Set(["websocket", "crypto", "streams", "background-tasks"]),
worker: new Set(["websocket", "crypto", "streams", "background-tasks"]),
"service-worker": new Set(["crypto", "streams", "background-tasks"]),
browser: new Set(["websocket", "crypto", "streams"]),
};
const MODULE_CAPABILITIES: Array<[RegExp, RuntimeCapability]> = [
[/^(?:node:)?(?:fs|path|os)(?:\/|$)/, "filesystem"],
[/^(?:node:)?(?:net|tls|dgram|http2)(?:\/|$)/, "tcp"],
[/^(?:node:)?(?:child_process|cluster|worker_threads)(?:\/|$)/, "process"],
];
export interface RuntimeCapabilityDiagnostic {
code: "WRN-RUNTIME-CAPABILITY";
runtime: DeploymentRuntime;
module: string;
capability: RuntimeCapability;
message: string;
}
export function runtimeCapabilities(runtime: DeploymentRuntime): ReadonlySet<RuntimeCapability> {
return CAPABILITIES[runtime];
}
export function analyzeRuntimeImports(
source: string,
runtime: DeploymentRuntime,
): RuntimeCapabilityDiagnostic[] {
const modules = [
...source.matchAll(/\b(?:import\s+(?:[\s\S]*?\s+from\s+)?|require\s*\()\s*["']([^"']+)["']/g),
].map((match) => match[1]!);
const available = runtimeCapabilities(runtime);
return modules.flatMap((module) => {
const requirement = MODULE_CAPABILITIES.find(([pattern]) => pattern.test(module));
if (!requirement || available.has(requirement[1])) return [];
return [
{
code: "WRN-RUNTIME-CAPABILITY" as const,
runtime,
module,
capability: requirement[1],
message: `Module '${module}' requires ${requirement[1]}, which is unavailable in the ${runtime} runtime.`,
},
];
});
}
@@ -0,0 +1,278 @@
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
exports[`compiler output remains snapshot-compatible for the canonical component contract 1`] = `
{
"code":
"// compiled from .wrn
import Button from "@wrnexus/ui/components/Button.wrn";
import { Buffer as __WrnexusBuffer } from "node:buffer";
export const __wrnexusComponent = "Counter";
export const __wrnexusRuntime = "universal";
export const __wrnexusRender = "hybrid";
export const __wrnexusHydrate = "load";
export const __wrnexusHydrationId = "Counter:1skggk6";
export const __wrnexusBehavior = {
"functions": "function increment(){\\n count = count + 1\\n output.change(count)\\n }",
"outputs": [
{
"name": "change",
"payload": {
"name": "value",
"valueType": "number",
"optional": false
}
}
],
"computed": [],
"effects": [],
"lifecycle": {},
"watches": []
};
export interface CounterProps {
[attribute: string]: unknown;
"label"?: string;
}
export interface CounterOutputs {
"change"(value: number): void;
}
function __coerce(v: any, def: any, declared: string = "unknown"): any {
if (v === undefined || v === null) {
return def;
}
if (declared === "number" || typeof def === "number") {
const parsed = Number(v);
if (!Number.isFinite(parsed)) throw new TypeError("Expected a finite number prop");
return parsed;
}
if (declared === "boolean" || typeof def === "boolean") {
if (v === true || v === "" || v === "true" || v === 1 || v === "1") return true;
if (v === false || v === "false" || v === 0 || v === "0") return false;
throw new TypeError("Expected a boolean prop");
}
if (declared === "array" || 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 {
if (declared === "array") throw new TypeError("Expected an array prop");
return def;
}
}
return def;
}
if (declared === "object" || (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 {
if (declared === "object") throw new TypeError("Expected an object prop");
return def;
}
}
return def;
}
if (declared === "bigint") return BigInt(v);
if (declared === "function" && typeof v !== "function") {
throw new TypeError("Expected a function prop");
}
return declared === "unknown" && def === undefined ? v : String(v);
}
function __restProps(
props: Record<string, any>,
declared: Set<string>,
): Record<string, any> {
return Object.fromEntries(
Object.entries(props).filter(([name]) => !declared.has(name)),
);
}
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 __wireBooleanAttr(name: string, value: any): string {
return value === true ||
value === "true" ||
value === "" ||
value === 1 ||
value === "1" ||
value === name
? " " + name
: "";
}
function __wireSpreadAttrs(value: any): string {
if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
const booleanAttributes = new Set(["allowfullscreen","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","itemscope","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"]);
const attributes: string[] = [];
for (const [name, raw] of Object.entries(value)) {
const lowerName = name.toLowerCase();
if (
!/^[A-Za-z_:][A-Za-z0-9_.:-]*$/.test(name) ||
lowerName.startsWith("on") ||
lowerName === "style" ||
lowerName === "slot" ||
lowerName === "data-component" ||
lowerName.startsWith("data-wrn")
) {
continue;
}
if (booleanAttributes.has(lowerName)) {
attributes.push(__wireBooleanAttr(name, raw));
continue;
}
if (raw === false || raw === null || raw === undefined) continue;
attributes.push(" " + name + '="' + __wireAttr(raw) + '"');
}
return attributes.join("");
}
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);
}
function __wrnexusSerializeScopeValue(value: any): string {
if (value === undefined) {
return "undefined";
}
if (value === null) {
return "null";
}
if (typeof value === "number") {
return Number.isFinite(value)
? String(value)
: "null";
}
if (typeof value === "boolean") {
return value ? "true" : "false";
}
if (typeof value === "string") {
return JSON.stringify(value);
}
try {
const serialized = JSON.stringify(value);
return serialized === undefined
? "undefined"
: serialized;
} catch {
return "null";
}
}
function __wrnexusScopeDecl(obj: Record<string, any>): string {
return Object.keys(obj)
.map(
(key) =>
key +
": " +
__wrnexusSerializeScopeValue(
obj[key],
),
)
.join(", ")
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
export function render(props: CounterProps = {} as CounterProps): string {
const __p = props || {};
const label: string = __coerce(__p["label"], ("Count"), "string");
const __attrs = __restProps(__p, new Set(["label"]));
let count = (0);
const __scopeState = { "label": label, "count": count };
const __scope = __wrnexusScopeDecl(__scopeState);
const __scopePayload = __WrnexusBuffer.from(JSON.stringify(__scopeState), "utf8").toString("base64");
return \`<div data-scope="\${__scope}" data-wrn-scope="\${__scopePayload}" data-wrn-behavior="eyJmdW5jdGlvbnMiOiJmdW5jdGlvbiBpbmNyZW1lbnQoKXtcbiAgICAgIGNvdW50ID0gY291bnQgKyAxXG4gICAgICBvdXRwdXQuY2hhbmdlKGNvdW50KVxuICAgIH0iLCJvdXRwdXRzIjpbeyJuYW1lIjoiY2hhbmdlIiwicGF5bG9hZCI6eyJuYW1lIjoidmFsdWUiLCJ2YWx1ZVR5cGUiOiJudW1iZXIiLCJvcHRpb25hbCI6ZmFsc2V9fV0sImNvbXB1dGVkIjpbXSwiZWZmZWN0cyI6W10sImxpZmVjeWNsZSI6e30sIndhdGNoZXMiOltdfQ==" data-wrn-hydration="Counter:1skggk6" data-wrn-hydrate="load" data-wrn-runtime="universal" data-wrn-client-module="__WRNEXUS_CLIENT_MODULE__">
<div data-component="Button"\${__wireSpreadAttrs(__attrs)} on:click="\${__wireProp(increment)}">\${__wireHtml(label)}: <span data-text="count">\${__wireHtml(count)}</span></div>
</div>\`;
}
export default { name: "Counter", kind: "component", render };
"
,
"diagnostics": [],
}
`;
+21
View File
@@ -0,0 +1,21 @@
import { expect, test } from "bun:test";
import { compile } from "../src/index.ts";
test("compiles schema-backed actions and progressively enhanced forms", () => {
const output = compile(
`import { CreateUserSchema } from "./schema";
page Users {
action createUser using CreateUserSchema { invalidate("users"); return { id: input.name } }
view { <form @submit='createUser'><input name='name' /></form> }
}`,
"Users.wrn",
).code;
expect(output).toContain('data-wrn-action="createUser"');
expect(output).toContain('name="_wrnexus_action" value="createUser"');
expect(output).toContain("schema: CreateUserSchema");
expect(output).toContain("__wrnexusInvalidatedTags");
expect(output).toContain(
"createActionClient<InferSchema<typeof CreateUserSchema>, Awaited<ReturnType<typeof createUser>>>",
);
expect(output).not.toContain("data-on-submit");
});
@@ -0,0 +1,70 @@
import { expect, test } from "bun:test";
import { generate, parse } from "../src/index.ts";
test("Async syntax compiles loading, success and error branches into inert templates", () => {
const code = generate(
parse(`page Users {
load client users { return [{ name: "Ada" }] }
view {
<Async source="users" retries="3">
<Loading><p>Loading users</p></Loading>
<Success data="users"><p>{users.name}</p></Success>
<Error error="error"><p>{error.message}</p></Error>
</Async>
}
}`),
);
expect(code).toContain('data-wrn-async="users"');
expect(code).toContain('data-wrn-async-retries="3"');
expect(code).toContain("data-wrn-async-loading");
expect(code).toContain("data-wrn-async-success");
expect(code).toContain("data-wrn-async-error");
expect(code).toContain("__wrnexusClientLoad");
});
test("server named loads render Async success content during SSR", () => {
const code = generate(
parse(`page Users {
load server users { return { name: "Ada" } }
view {
<Async source="users">
<Loading><p>Loading</p></Loading>
<Success><p>{users.name}</p></Success>
<Error><p>Failed</p></Error>
</Async>
}
}`),
);
expect(code).toContain('const users = ctx["users"]');
expect(code).toContain('data-wrn-async-resolved="true"');
expect(code).toContain('ctx["users"] !== undefined');
});
test("named loads support memoized dependencies and deferred execution", () => {
const code = generate(
parse(`page Data {
load server account { return { id: 7 } }
load server projects after account { return [account.id] }
load server audit after projects defer { return { project: projects[0] } }
view { <Async source="audit"><Loading>Wait</Loading><Success>Ready</Success></Async> }
}`),
);
expect(code).toContain("const account = await __load_account()");
expect(code).toContain("const projects = await __load_projects()");
expect(code).toContain("__promise_projects ??=");
expect(code).toContain("export async function __wrnexusClientLoad");
expect(code).toContain('return { "audit": __values[0] }');
});
test("load dependency cycles and cross-phase server dependencies fail compilation", () => {
expect(() =>
parse(
`page Cycle { load server first after second { return 1 } load server second after first { return 2 } view { <p>x</p> } }`,
),
).toThrow("cycle");
expect(() =>
parse(
`page Phase { load client browser { return 1 } load server invalid after browser { return 2 } view { <p>x</p> } }`,
),
).toThrow("cannot depend");
});
@@ -0,0 +1,11 @@
import { expect, test } from "bun:test";
import { generate, parse } from "../src/index.ts";
test("client-rendered pages emit an inert template and browser mount anchor", () => {
const code = generate(
parse('page ClientOnly { render = "client" view { <main><h1>Browser only</h1></main> } }'),
);
expect(code).toContain('data-wrn-client-root="');
expect(code).toContain('data-wrn-client-template="');
expect(code.indexOf("Browser only")).toBeGreaterThan(code.indexOf("<template"));
});
+21
View File
@@ -7,6 +7,27 @@ import { generate, parse } from "../src/index.ts";
import { compileWireFile } from "../src/index.ts";
import { mountHtml } from "@wrnexus/test";
test("explicit static rendering disables hydration metadata", () => {
const output = compileWireFile(`page StaticPage {
render = "static"
state count = 0
view { <button @click="count++">{count}</button> }
}`);
expect(output).toContain('export const __wrnexusRender = "static"');
expect(output).toContain('data-wrn-hydrate="none"');
expect(output).toContain('export const __wrnexusHydrate = "none"');
});
test("named data loads compile as parallel typed data entries", () => {
const output = compileWireFile(`page Users {
load users { return ["Ada"] }
load server teams { return ["Core"] }
view { <p>Users</p> }
}`);
expect(output).toContain("await Promise.all");
expect(output).toContain('return { "users": __values[0], "teams": __values[1] }');
});
let seq = 0;
/** Compile a `.wrn` source and import the resulting module. */
async function compileAndImport(src: string): Promise<Record<string, unknown>> {
@@ -0,0 +1,15 @@
import { expect, test } from "bun:test";
import { compile } from "../src/index.ts";
test("compiles declarative portals, transitions and dynamic component cases", () => {
const result = compile(`page Ui {
view {
<Portal to="#modal"><p>Modal</p></Portal>
<Transition name="fade"><p>Animated</p></Transition>
<Component is="Admin"><section data-component-case="Admin">Admin</section><section data-component-case="Guest">Guest</section></Component>
}
}`);
expect(result.code).toContain('data-wrn-portal="#modal"');
expect(result.code).toContain('data-wrn-transition="fade"');
expect(result.code).toContain('data-wrn-dynamic-component="Admin"');
});
+12
View File
@@ -0,0 +1,12 @@
import { expect, test } from "bun:test";
import { generate, parse } from "../src/index.ts";
test("KeepAlive compiles to a keyed live-instance preservation boundary", () => {
const output = generate(
parse(
`page Dashboard { navigation { preserve = ["component"] } view { <KeepAlive key="filters"><DashboardFilters /></KeepAlive> } }`,
),
);
expect(output).toContain('data-wrn-keepalive="filters"');
expect(output).not.toContain('data-component="KeepAlive"');
});
@@ -0,0 +1,23 @@
import { expect, test } from "bun:test";
import { analyzeOptimizations, generate, optimizeAst, parse } from "../src/index.ts";
test("compiler folds literal branches and reports optimization opportunities", () => {
const ast = parse(`component Optimized {
props { title: string = "Hello" }
state count = 0
state unused = 1
functions {
client function increment(): void { count++ }
client function orphan(): void { unused++ }
}
style { .used { color: red } .unused-css { color: blue } }
view { <section class="used"><h1>{title}</h1>{#if false}<p>dead</p>{:else}<button @click="increment">{count}</button>{/if}</section> }
}`);
const report = analyzeOptimizations(ast);
expect(report.eliminatedBranches).toBeGreaterThan(0);
expect(report.unusedHandlers).toContain("orphan");
expect(report.unusedLocalCssClasses).toContain("unused-css");
expect(report.constantProps).toContain("title");
expect(optimizeAst(ast).ast.view).not.toEqual(ast.view);
expect(generate(ast)).not.toContain("dead");
});
@@ -0,0 +1,15 @@
import { expect, test } from "bun:test";
import { analyzeRuntimeRequirements, generate, parse } from "../src/index.ts";
test("partial-static pages compile transparent static and streamed dynamic boundaries", () => {
const ast = parse(
`page Dashboard { render = "partial-static" view { <Static><header>Docs</header></Static><Dynamic><p>User</p></Dynamic> } }`,
);
expect(ast.renderMode).toBe("partial-static");
expect(analyzeRuntimeRequirements(ast).kind).toBe("streaming-ssr");
const output = generate(ast);
expect(output).toContain("wrn-dynamic-region");
expect(output).toContain("__wrnexusBuildStaticShell");
expect(output).toContain('<wrn-dynamic-region data-wrn-dynamic="true"></wrn-dynamic-region>');
expect(output).not.toContain('data-component="Static"');
});
+44
View File
@@ -0,0 +1,44 @@
import { expect, test } from "bun:test";
import { compile, diagnose } from "../src/index.ts";
test("compiler output remains snapshot-compatible for the canonical component contract", () => {
const source = `import Button from "@wrnexus/ui/components/Button.wrn";
component Counter {
props {
label: string = "Count"
}
state {
count = 0
}
outputs {
change(value: number)
}
functions {
client function increment(): void {
count = count + 1
output.change(count)
}
}
view {
<Button on:click={increment}>{label}: {count}</Button>
}
}
`;
const result = compile(source, "Counter.wrn");
expect({ code: result.code, diagnostics: result.richDiagnostics }).toMatchSnapshot();
});
test("diagnostics tolerate deterministic malformed-source fuzz cases", () => {
let state = 0x8f3a21;
const alphabet = "{}[]()<>=:/@#$'\"` abcdefghijklmnopqrstuvwxyz0123456789\n\t";
for (let sample = 0; sample < 500; sample++) {
let source = "";
const length = 1 + (state % 180);
for (let index = 0; index < length; index++) {
state = (Math.imul(state, 1664525) + 1013904223) >>> 0;
source += alphabet[state % alphabet.length];
}
expect(() => diagnose(source, { file: `fuzz-${sample}.wrn` })).not.toThrow();
}
});
@@ -0,0 +1,12 @@
import { expect, test } from "bun:test";
import { analyzeRuntimeImports, runtimeCapabilities } from "../src/index.ts";
test("edge and workers reject Node capabilities with stable diagnostics", () => {
const source = `import fs from "node:fs";\nimport { connect } from "node:net";`;
expect(analyzeRuntimeImports(source, "edge").map((item) => item.capability)).toEqual([
"filesystem",
"tcp",
]);
expect(analyzeRuntimeImports(source, "bun")).toEqual([]);
expect(runtimeCapabilities("service-worker").has("filesystem")).toBe(false);
});