refactor: replace the legacy function runtime with shared
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -25,7 +25,7 @@ function stableId(value: string): string {
|
||||
*/
|
||||
export function remotelyReferencedServerFunctions(ast: PageAst): Set<string> {
|
||||
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`;
|
||||
|
||||
@@ -175,7 +175,7 @@ export function generateStoreBrowserModule(ast: PageAst): string {
|
||||
.join(",\n");
|
||||
const groups = new Map<string, RuntimeFunctionDecl[]>();
|
||||
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);
|
||||
|
||||
@@ -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>" : "unknown")};`,
|
||||
|
||||
@@ -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 { <main>x</main> }
|
||||
}
|
||||
`;
|
||||
|
||||
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"');
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user