release: WRNexusJS 0.8.3
Quality / quality (ubuntu-latest) (push) Failing after 12m9s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-03 19:47:30 +05:30
parent e8f630f12d
commit 4cebacadfe
156 changed files with 2608 additions and 473 deletions
+108 -20
View File
@@ -29,6 +29,7 @@ import {
analyzeRuntimeRequirements,
assertValidAst,
generate,
generateTargets,
parse,
type RuntimeRequirements,
type DeploymentRuntime,
@@ -106,6 +107,7 @@ export async function runBuild(appRoot: string): Promise<void> {
const appDir = join(root, "app");
const distDir = join(root, "dist");
const compiledDir = join(distDir, "compiled");
const clientModulesDir = join(distDir, "client");
const reactivePath = join(distDir, "reactive.js");
const publicDir = join(root, "public");
const distPublicDir = join(distDir, "public");
@@ -117,6 +119,7 @@ export async function runBuild(appRoot: string): Promise<void> {
// Clean output.
rmSync(distDir, { recursive: true, force: true });
mkdirSync(compiledDir, { recursive: true });
mkdirSync(clientModulesDir, { recursive: true });
if (existsSync(publicDir)) {
cpSync(publicDir, distPublicDir, { recursive: true });
console.log(`✓ Public: ${distPublicDir}`);
@@ -169,15 +172,29 @@ export async function runBuild(appRoot: string): Promise<void> {
// keep the exact compiler path and output contract by default.
let compiledCount = 0;
const compiledFiles = new Map<string, string>();
const clientFiles = new Map<string, string>();
const runtimeAnalysis = new Map<string, RuntimeRequirements>();
const partialStaticFiles = new Set<string>();
const compileWrn = async (file: string): Promise<void> => {
if (!file.endsWith(".wrn") || compiledFiles.has(file)) return;
const out = join(compiledDir, `route${compiledCount++}.ts`);
// Reserve the artifact before resolving imports so cycles terminate and
// mutually dependent generated modules can point at deterministic paths.
compiledFiles.set(file, out);
const source = readFileSync(file, "utf8");
const artifactId = compiledCount++;
const out = join(compiledDir, `route${artifactId}.ts`);
const clientHash = createHash("sha256")
.update(fwd(file))
.update("\0")
.update(source)
.digest("hex")
.slice(0, 16);
const clientOut = join(clientModulesDir, `${basename(file, ".wrn")}-${clientHash}.mjs`);
const clientUrl = `/__wrnexus/client/${basename(clientOut)}`;
const clientEntry = join(compiledDir, `client${artifactId}.entry.mjs`);
// Reserve both artifacts before resolving imports so dependency cycles
// terminate and every generated import has a deterministic target.
compiledFiles.set(file, out);
clientFiles.set(file, clientOut);
let ast = parse(source);
assertValidAst(ast, { file, accessibility: true });
ast = await pluginRunner.transformAst(ast, file);
@@ -193,18 +210,35 @@ export async function runBuild(appRoot: string): Promise<void> {
errors.map((diagnostic) => `[${diagnostic.code}] ${diagnostic.message}`).join("\n"),
);
}
try {
let code = `// compiled from .wrn\n${generate(ast)}`;
const targets = generateTargets(ast);
let code = `// compiled from ${fwd(relative(root, file))}\n${generate(ast)}`.replaceAll(
"__WRNEXUS_CLIENT_MODULE__",
clientUrl,
);
let browserCode = `// browser module compiled from ${fwd(relative(root, file))}\n${targets.browser}`;
code = await pluginRunner.transformCode(code, file);
browserCode = await pluginRunner.transformCode(browserCode, file);
for (const [id, target] of virtualModules) {
const sourcePattern = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const relativeTarget = relative(dirname(out), target).replace(/\\/g, "/");
const specifier = relativeTarget.startsWith(".") ? relativeTarget : `./${relativeTarget}`;
const mainRelative = relative(dirname(out), target).replace(/\\/g, "/");
const mainSpecifier = mainRelative.startsWith(".") ? mainRelative : `./${mainRelative}`;
code = code.replace(
new RegExp(`(["'])${sourcePattern}\\1`, "g"),
JSON.stringify(specifier),
JSON.stringify(mainSpecifier),
);
const browserRelative = relative(dirname(clientEntry), target).replace(/\\/g, "/");
const browserSpecifier = browserRelative.startsWith(".")
? browserRelative
: `./${browserRelative}`;
browserCode = browserCode.replace(
new RegExp(`(["'])${sourcePattern}\\1`, "g"),
JSON.stringify(browserSpecifier),
);
}
const resolvedImports = resolveWrnImports(ast.structuredImports, file, {
appRoot: root,
mode: config.imports?.mode ?? "compatible",
@@ -214,23 +248,74 @@ export async function runBuild(appRoot: string): Promise<void> {
if (imported.diagnostic?.severity === "error") {
throw new Error(`${imported.diagnostic.code}: ${imported.diagnostic.message}`);
}
if (!imported.resolved || !imported.declaration.source.startsWith(".")) continue;
let target = imported.resolved;
if (target.endsWith(".wrn")) {
await compileWrn(target);
target = compiledFiles.get(target)!;
}
if (!imported.resolved) continue;
const sourcePattern = imported.declaration.source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const relativeTarget = relative(dirname(out), target).replace(/\\/g, "/");
const specifier = relativeTarget.startsWith(".") ? relativeTarget : `./${relativeTarget}`;
code = code.replace(
new RegExp(`(["'])${sourcePattern}\\1`, "g"),
JSON.stringify(specifier),
const importAliases = { "@": "./app", ...(config.imports?.aliases ?? {}) };
const isApplicationImport =
imported.declaration.source.startsWith(".") ||
Object.keys(importAliases).some(
(alias) =>
imported.declaration.source === alias ||
imported.declaration.source.startsWith(`${alias}/`),
);
if (isApplicationImport) {
let target = imported.resolved;
if (target.endsWith(".wrn")) {
await compileWrn(target);
target = compiledFiles.get(target)!;
}
const relativeTarget = relative(dirname(out), target).replace(/\\/g, "/");
const specifier = relativeTarget.startsWith(".") ? relativeTarget : `./${relativeTarget}`;
code = code.replace(
new RegExp(`(["'])${sourcePattern}\\1`, "g"),
JSON.stringify(specifier),
);
}
// Browser modules keep only imports referenced by client/shared code.
// Resolve those imports relative to the original .wrn source before
// bundling so no filesystem or bare package specifier reaches browsers.
if (isApplicationImport && new RegExp(`(["'])${sourcePattern}\\1`).test(browserCode)) {
let browserTarget = imported.resolved;
if (browserTarget.endsWith(".wrn")) {
await compileWrn(browserTarget);
browserTarget = clientFiles.get(browserTarget)!;
}
const browserRelative = relative(dirname(clientEntry), browserTarget).replace(/\\/g, "/");
const browserSpecifier = browserRelative.startsWith(".")
? browserRelative
: `./${browserRelative}`;
browserCode = browserCode.replace(
new RegExp(`(["'])${sourcePattern}\\1`, "g"),
JSON.stringify(browserSpecifier),
);
}
}
writeFileSync(out, code, "utf8");
writeFileSync(clientEntry, browserCode, "utf8");
const browserResult = await Bun.build({
entrypoints: [clientEntry],
target: "browser",
format: "esm",
splitting: false,
minify: true,
sourcemap: config.build?.sourceMaps ? "inline" : "none",
});
if (!browserResult.success || !browserResult.outputs.length) {
throw new Error(
`WRN-CLIENT-BUNDLE: failed to bundle ${file}\n${browserResult.logs.map(String).join("\n")}`,
);
}
writeFileSync(out, code, "utf8");
writeFileSync(clientOut, await browserResult.outputs[0]!.text(), "utf8");
rmSync(clientEntry, { force: true });
} catch (error) {
compiledFiles.delete(file);
clientFiles.delete(file);
rmSync(out, { force: true });
rmSync(clientOut, { force: true });
rmSync(clientEntry, { force: true });
throw error;
}
};
@@ -369,6 +454,8 @@ export async function runBuild(appRoot: string): Promise<void> {
console.log(`✓ Partial shells: ${partialShells.size} build-time route shell(s)`);
}
const assetHash = createHash("sha256");
for (const file of clientFiles.values()) assetHash.update(readFileSync(file));
if (clientFiles.size) console.log(`✓ Client modules: ${clientFiles.size} bundled module(s)`);
const emittedPluginAssets = await emitPluginAssets(
pluginContributions,
distDir,
@@ -533,6 +620,7 @@ await createProductionServer(
},
{
reactivePath: join(import.meta.dir, "reactive.js"),
clientModulesDir: join(import.meta.dir, "client"),
themePath: join(import.meta.dir, "theme.css"),
themeJsPath: join(import.meta.dir, "theme.js"),
theme: ${JSON.stringify(theme)},
+44 -3
View File
@@ -3,6 +3,7 @@ import { basename, extname, join, relative, resolve } from "node:path";
import { buildRouter, createRouteManifest, nameRoutes } from "@wrnexus/router";
import { checkWrnFile, type WrnTypeDiagnostic } from "@wrnexus/typecheck";
import { parse } from "@wrnexus/syntax";
import { generate, generateTargets } from "@wrnexus/compiler";
import { regenerateRoutes } from "./routes.ts";
import { loadAppConfig } from "@wrnexus/styles";
import { createPluginRunner, discoverPlugins, type PluginContributions } from "@wrnexus/plugin";
@@ -258,11 +259,51 @@ export async function generateApplicationTypesWithPlugins(
return generateApplicationTypes(root, await runner.contributions());
}
function codegenDiagnostics(file: string): WrnTypeDiagnostic[] {
try {
const ast = parse(readFileSync(file, "utf8"));
const main = generate(ast);
const targets = generateTargets(ast);
const bun = (
globalThis as unknown as {
Bun?: {
Transpiler?: new (options: { loader: string }) => {
transformSync(code: string): unknown;
};
};
}
).Bun;
if (bun?.Transpiler) {
const transpiler = new bun.Transpiler({ loader: "ts" });
transpiler.transformSync(main);
transpiler.transformSync(targets.browser);
transpiler.transformSync(targets.server);
}
return [];
} catch (error) {
return [
{
code: "WRN-CODEGEN",
category: "error",
message: error instanceof Error ? error.message : String(error),
file,
line: 1,
column: 1,
length: 1,
hint: "Fix the reported SSR/client code generation issue before starting or building the app.",
},
];
}
}
export function checkApplication(appRoot: string): WrnTypeDiagnostic[] {
const root = resolve(appRoot);
return files(join(root, "app"), (path) => extname(path) === ".wrn").flatMap((file) =>
checkWrnFile(file, { appRoot: root }),
);
return files(join(root, "app"), (path) => extname(path) === ".wrn").flatMap((file) => {
const diagnostics = checkWrnFile(file, { appRoot: root });
return diagnostics.some((item) => item.category === "error")
? diagnostics
: [...diagnostics, ...codegenDiagnostics(file)];
});
}
export async function runTypecheck(appRoot: string): Promise<boolean> {
+10
View File
@@ -1989,6 +1989,16 @@ const MIGRATIONS: Migration[] = [
// Runtime stability fixes require no application source rewrite.
},
},
{
version: "0.8.3",
id: "0.8.3-client-ssr-realtime-stability",
description:
"Fixes browser function bundling, SSR computed values, Async aliases, typed loop props, realtime identity isolation, loader invalidation, and strict package imports.",
apply() {
// Framework runtime and compiler fixes require no application source rewrite.
// The compile-cache generation changes automatically invalidate old artifacts.
},
},
];
/** Release tooling uses this to require an explicit migration entry per version. */