From ec63090006d6905b39f3b6ce70da5153845d8e18 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Wed, 19 Aug 2026 23:13:05 +0530 Subject: [PATCH] refactor: replace the legacy function runtime with shared Co-Authored-By: Claude Opus 5 --- editors/vscode/src/compiler.cjs | 24 +++++----- editors/vscode/src/extension.bundle.cjs | 2 +- editors/vscode/src/language-server.cjs | 4 +- packages/compiler/src/client-codegen.ts | 8 +--- packages/compiler/src/codegen.ts | 4 +- packages/compiler/src/server-codegen.ts | 6 +-- packages/compiler/src/store-codegen.ts | 4 +- packages/compiler/src/type-codegen.ts | 2 +- .../test/legacy-runtime-removal.test.ts | 47 +++++++++++++++++++ packages/syntax/src/v060.ts | 4 +- 10 files changed, 72 insertions(+), 33 deletions(-) create mode 100644 packages/compiler/test/legacy-runtime-removal.test.ts diff --git a/editors/vscode/src/compiler.cjs b/editors/vscode/src/compiler.cjs index bcc6bf17..0f1cf93d 100644 --- a/editors/vscode/src/compiler.cjs +++ b/editors/vscode/src/compiler.cjs @@ -1,6 +1,6 @@ "use strict"; // Generated by scripts/build-editor-compiler.mjs. Do not edit directly. -// WRN editor compiler source hash: b0e3094d8c2a70ee2b34fe961c186b58de9527aa072ca12292b715d3d5f51c87 +// WRN editor compiler source hash: b5b5e076bca1aa1b5603d5b6caf5d5a3169a04e33da8b83ae94e952f8685ab3e // WRN editor compiler generator hash: a54ca847c758bc98d8e353ad6d70088df31de1820f6cf9d1c3462505f563e6b8 // Generated with TypeScript: 6.0.3 const __nodeRequire = require; @@ -600,7 +600,7 @@ function selectedBrowserImports(ast, functions) { .filter((entry) => entry !== null); } function browserModuleRequired(ast) { - const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime)); + const functions = ast.runtimeFunctions.filter((fn) => ["client", "shared"].includes(fn.runtime)); return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0; } function _functionEntry(ast, fn, availableFunctions) { @@ -734,7 +734,7 @@ function apiBindings(ast) { return members.length ? `const api = {\n${members.join(",\n")}\n };` : ""; } function generateBrowserModule(ast) { - const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime)); + const functions = ast.runtimeFunctions.filter((fn) => ["client", "shared"].includes(fn.runtime)); const functionNames = functions.map((fn) => fn.name); const state = ast.states.filter((entry) => entry.runtime !== "server").map((entry) => entry.name); const selectedImports = selectedBrowserImports(ast, functions); @@ -1900,9 +1900,7 @@ function hydrationAttribute(ast) { return ` data-wrn-hydration="${attrEscape(hydrationId(ast))}" data-wrn-hydrate="${attrEscape(strategy)}" data-wrn-runtime="${attrEscape(ast.runtime ?? "universal")}"${moduleAttribute}`; } function targetFunctions(ast, target) { - const runtimes = target === "browser" - ? ["legacy", "client", "shared"] - : ["legacy", "server", "shared"]; + const runtimes = target === "browser" ? ["client", "shared"] : ["server", "shared"]; return ast.functions .map((body) => (0, syntax_1.stripRuntimeFunctionModifiers)(body, [...runtimes])) .map((body) => body.trim()) @@ -4163,7 +4161,7 @@ function stableId(value) { */ function remotelyReferencedServerFunctions(ast) { const browserSources = ast.runtimeFunctions - .filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime)) + .filter((fn) => ["client", "shared"].includes(fn.runtime)) .map((fn) => fn.body); for (const [hook, body] of Object.entries(ast.storeLifecycle)) { if (hook !== "serverInit" && body) @@ -4195,11 +4193,11 @@ function rpcManifest(ast) { } function generateServerFunctionsModule(ast) { const source = ast.functions - .map((body) => (0, syntax_1.stripRuntimeFunctionModifiers)(body, ["legacy", "server", "shared"])) + .map((body) => (0, syntax_1.stripRuntimeFunctionModifiers)(body, ["server", "shared"])) .filter(Boolean) .join("\n\n"); const names = ast.runtimeFunctions - .filter((fn) => ["legacy", "server", "shared"].includes(fn.runtime)) + .filter((fn) => ["server", "shared"].includes(fn.runtime)) .map((fn) => fn.name); const manifest = rpcManifest(ast); return `// generated WRNexusJS server module for ${ast.name}\n${source}\n\nexport const __wrnexusServerFunctions = { ${[...new Set(names)].join(", ")} };\nexport const __wrnexusRpcManifest = ${JSON.stringify(manifest, null, 2)};\n`; @@ -4381,7 +4379,7 @@ function generateStoreBrowserModule(ast) { .map((entry) => `${JSON.stringify(entry.name)}: (state) => { ${safeStateNames.length ? `const { ${safeStateNames.join(", ")} } = state;` : ""} return (${entry.expr}); }`) .join(",\n"); const groups = new Map(); - for (const fn of ast.runtimeFunctions.filter((entry) => ["client", "shared", "legacy"].includes(entry.runtime))) { + for (const fn of ast.runtimeFunctions.filter((entry) => ["client", "shared"].includes(entry.runtime))) { const group = groups.get(fn.name) ?? []; group.push(fn); groups.set(fn.name, group); @@ -4496,7 +4494,7 @@ function __create(definition) { Object.keys(actions).forEach(function (name) { delete actions[name]; }); Object.entries(currentDefinition.actions || {}).forEach(function (pair) { const name = pair[0], candidates = pair[1]; - const selected = candidates.find(function (entry) { return entry.runtime === "client"; }) || candidates.find(function (entry) { return entry.runtime === "shared"; }) || candidates.find(function (entry) { return entry.runtime === "legacy"; }); + const selected = candidates.find(function (entry) { return entry.runtime === "client"; }) || candidates.find(function (entry) { return entry.runtime === "shared"; }); if (!selected) return; actions[name] = async function () { const args = Array.prototype.slice.call(arguments); @@ -4689,7 +4687,7 @@ function generateDeclarations(ast) { .map((output) => ` ${member(output.name)}(${output.payload ? `${member(output.payload.name)}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}): void;`) .join("\n"); const clientFunctions = ast.runtimeFunctions - .filter((fn) => fn.runtime === "client" || fn.runtime === "shared" || fn.runtime === "legacy") + .filter((fn) => fn.runtime === "client" || fn.runtime === "shared") .map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): ${fn.returnType ?? (fn.async ? "Promise" : "unknown")};`) .join("\n"); const serverFunctions = ast.runtimeFunctions @@ -7973,7 +7971,7 @@ function parseRuntimeFunctions(source) { i++; continue; } - let runtime = "legacy"; + let runtime = "shared"; if (["client", "server", "shared"].includes(token.word)) { runtime = token.word; i = skipTrivia(source, token.end); diff --git a/editors/vscode/src/extension.bundle.cjs b/editors/vscode/src/extension.bundle.cjs index 8fe22327..37257659 100644 --- a/editors/vscode/src/extension.bundle.cjs +++ b/editors/vscode/src/extension.bundle.cjs @@ -1,4 +1,4 @@ -// WRN editor extension source hash: 548e7e0c27d5c951325c977cc22f2ee0340dc3e34bd896904e519ee357ca37a8 +// WRN editor extension source hash: 791b7ed86b1d5958a71ee3886a8dbd26301171be0e0d4d01f747d20556d51aad // WRN editor extension generator hash: 456d1d614e44e5fb1f19b784176c09cf2ade9b64ef73a17934c2698150b62728 "use strict"; var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); diff --git a/editors/vscode/src/language-server.cjs b/editors/vscode/src/language-server.cjs index bcccbcf1..b22fa2ee 100644 --- a/editors/vscode/src/language-server.cjs +++ b/editors/vscode/src/language-server.cjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// WRN editor language server source hash: cf98947fd66392200bdfb18524a13e8bbb77d4920ba65a4d724e1124152571a3 +// WRN editor language server source hash: 047a3b701a20619b02c7dacecdb3af63e2989e1837abbc83c8b235981f694562 // WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72 // @bun @bun-cjs (function(exports, require, module, __filename, __dirname) {var __create = Object.create; @@ -171090,7 +171090,7 @@ function parseRuntimeFunctions(source) { i++; continue; } - let runtime = "legacy"; + let runtime = "shared"; if (["client", "server", "shared"].includes(token.word)) { runtime = token.word; i = skipTrivia(source, token.end); diff --git a/packages/compiler/src/client-codegen.ts b/packages/compiler/src/client-codegen.ts index 2dd8186f..86a711df 100644 --- a/packages/compiler/src/client-codegen.ts +++ b/packages/compiler/src/client-codegen.ts @@ -169,9 +169,7 @@ function selectedBrowserImports( } export function browserModuleRequired(ast: PageAst): boolean { - const functions = ast.runtimeFunctions.filter((fn) => - ["legacy", "client", "shared"].includes(fn.runtime), - ); + const functions = ast.runtimeFunctions.filter((fn) => ["client", "shared"].includes(fn.runtime)); return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0; } @@ -336,9 +334,7 @@ function apiBindings(ast: PageAst): string { } export function generateBrowserModule(ast: PageAst): string { - const functions = ast.runtimeFunctions.filter((fn) => - ["legacy", "client", "shared"].includes(fn.runtime), - ); + const functions = ast.runtimeFunctions.filter((fn) => ["client", "shared"].includes(fn.runtime)); const functionNames = functions.map((fn) => fn.name); const state = ast.states.filter((entry) => entry.runtime !== "server").map((entry) => entry.name); const selectedImports = selectedBrowserImports(ast, functions); diff --git a/packages/compiler/src/codegen.ts b/packages/compiler/src/codegen.ts index f12b2856..0776206e 100644 --- a/packages/compiler/src/codegen.ts +++ b/packages/compiler/src/codegen.ts @@ -1310,9 +1310,7 @@ function hydrationAttribute(ast: PageAst): string { function targetFunctions(ast: PageAst, target: "browser" | "server"): string { const runtimes = - target === "browser" - ? (["legacy", "client", "shared"] as const) - : (["legacy", "server", "shared"] as const); + target === "browser" ? (["client", "shared"] as const) : (["server", "shared"] as const); return ast.functions .map((body) => stripRuntimeFunctionModifiers(body, [...runtimes])) .map((body) => body.trim()) diff --git a/packages/compiler/src/server-codegen.ts b/packages/compiler/src/server-codegen.ts index 9e94a915..bf302aeb 100644 --- a/packages/compiler/src/server-codegen.ts +++ b/packages/compiler/src/server-codegen.ts @@ -25,7 +25,7 @@ function stableId(value: string): string { */ export function remotelyReferencedServerFunctions(ast: PageAst): Set { const browserSources = ast.runtimeFunctions - .filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime)) + .filter((fn) => ["client", "shared"].includes(fn.runtime)) .map((fn) => fn.body); for (const [hook, body] of Object.entries(ast.storeLifecycle)) { if (hook !== "serverInit" && body) browserSources.push(body); @@ -57,11 +57,11 @@ export function rpcManifest(ast: PageAst): RpcManifestEntry[] { export function generateServerFunctionsModule(ast: PageAst): string { const source = ast.functions - .map((body) => stripRuntimeFunctionModifiers(body, ["legacy", "server", "shared"])) + .map((body) => stripRuntimeFunctionModifiers(body, ["server", "shared"])) .filter(Boolean) .join("\n\n"); const names = ast.runtimeFunctions - .filter((fn) => ["legacy", "server", "shared"].includes(fn.runtime)) + .filter((fn) => ["server", "shared"].includes(fn.runtime)) .map((fn) => fn.name); const manifest = rpcManifest(ast); return `// generated WRNexusJS server module for ${ast.name}\n${source}\n\nexport const __wrnexusServerFunctions = { ${[...new Set(names)].join(", ")} };\nexport const __wrnexusRpcManifest = ${JSON.stringify(manifest, null, 2)};\n`; diff --git a/packages/compiler/src/store-codegen.ts b/packages/compiler/src/store-codegen.ts index ac9ab56f..ff1a672c 100644 --- a/packages/compiler/src/store-codegen.ts +++ b/packages/compiler/src/store-codegen.ts @@ -175,7 +175,7 @@ export function generateStoreBrowserModule(ast: PageAst): string { .join(",\n"); const groups = new Map(); for (const fn of ast.runtimeFunctions.filter((entry) => - ["client", "shared", "legacy"].includes(entry.runtime), + ["client", "shared"].includes(entry.runtime), )) { const group = groups.get(fn.name) ?? []; group.push(fn); @@ -296,7 +296,7 @@ function __create(definition) { Object.keys(actions).forEach(function (name) { delete actions[name]; }); Object.entries(currentDefinition.actions || {}).forEach(function (pair) { const name = pair[0], candidates = pair[1]; - const selected = candidates.find(function (entry) { return entry.runtime === "client"; }) || candidates.find(function (entry) { return entry.runtime === "shared"; }) || candidates.find(function (entry) { return entry.runtime === "legacy"; }); + const selected = candidates.find(function (entry) { return entry.runtime === "client"; }) || candidates.find(function (entry) { return entry.runtime === "shared"; }); if (!selected) return; actions[name] = async function () { const args = Array.prototype.slice.call(arguments); diff --git a/packages/compiler/src/type-codegen.ts b/packages/compiler/src/type-codegen.ts index c49d5d0f..3ca724ac 100644 --- a/packages/compiler/src/type-codegen.ts +++ b/packages/compiler/src/type-codegen.ts @@ -47,7 +47,7 @@ export function generateDeclarations(ast: PageAst): string { ) .join("\n"); const clientFunctions = ast.runtimeFunctions - .filter((fn) => fn.runtime === "client" || fn.runtime === "shared" || fn.runtime === "legacy") + .filter((fn) => fn.runtime === "client" || fn.runtime === "shared") .map( (fn) => ` ${member(fn.name)}(${params(fn.parameters)}): ${fn.returnType ?? (fn.async ? "Promise" : "unknown")};`, diff --git a/packages/compiler/test/legacy-runtime-removal.test.ts b/packages/compiler/test/legacy-runtime-removal.test.ts new file mode 100644 index 00000000..8d700317 --- /dev/null +++ b/packages/compiler/test/legacy-runtime-removal.test.ts @@ -0,0 +1,47 @@ +import { expect, test } from "bun:test"; +import { parse } from "@wrnexus/syntax"; +import { generateTargets } from "../src/targets.ts"; + +const SOURCE = `page Probe { + functions { + function unmarkedHelper() { + return "both"; + } + + client function clientOnly() { + return "browser"; + } + + server function serverOnly() { + return "server"; + } + } + + view {
x
} +} +`; + +test("an unmarked function is emitted into both the browser and server modules", () => { + // This is the property the "legacy" runtime provided. Removing the variant + // must not change it. + const targets = generateTargets(parse(SOURCE)); + + expect(targets.browser).toContain("unmarkedHelper"); + expect(targets.server).toContain("unmarkedHelper"); +}); + +test("marked functions still go only where they belong", () => { + const targets = generateTargets(parse(SOURCE)); + + expect(targets.browser).toContain("clientOnly"); + expect(targets.browser).not.toContain("serverOnly"); + expect(targets.server).toContain("serverOnly"); + expect(targets.server).not.toContain("clientOnly"); +}); + +test("no emitted target mentions the removed legacy runtime", () => { + const targets = generateTargets(parse(SOURCE)); + + expect(targets.browser).not.toContain('"legacy"'); + expect(targets.server).not.toContain('"legacy"'); +}); diff --git a/packages/syntax/src/v060.ts b/packages/syntax/src/v060.ts index 57dbd47f..d2711fed 100644 --- a/packages/syntax/src/v060.ts +++ b/packages/syntax/src/v060.ts @@ -1,6 +1,6 @@ import { Lexer, LexError } from "./tokenizer.ts"; -export type FunctionRuntime = "legacy" | "client" | "server" | "shared"; +export type FunctionRuntime = "client" | "server" | "shared"; export type StateRuntime = "shared" | "client" | "server"; export type StoreKind = "global" | "page"; @@ -206,7 +206,7 @@ export function parseRuntimeFunctions(source: string): RuntimeFunctionDecl[] { i++; continue; } - let runtime: FunctionRuntime = "legacy"; + let runtime: FunctionRuntime = "shared"; if (["client", "server", "shared"].includes(token.word)) { runtime = token.word as FunctionRuntime; i = skipTrivia(source, token.end);