release: WRNexusJS 0.6.0
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
import { eraseFunctionTypes, type PageAst, type RuntimeFunctionDecl } from "@wrnexus/syntax";
|
||||
|
||||
const RESERVED_BINDINGS = new Set([
|
||||
"await",
|
||||
"break",
|
||||
"case",
|
||||
"catch",
|
||||
"class",
|
||||
"const",
|
||||
"continue",
|
||||
"debugger",
|
||||
"default",
|
||||
"delete",
|
||||
"do",
|
||||
"else",
|
||||
"enum",
|
||||
"export",
|
||||
"extends",
|
||||
"false",
|
||||
"finally",
|
||||
"for",
|
||||
"function",
|
||||
"if",
|
||||
"implements",
|
||||
"import",
|
||||
"in",
|
||||
"instanceof",
|
||||
"interface",
|
||||
"let",
|
||||
"new",
|
||||
"null",
|
||||
"package",
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
"return",
|
||||
"static",
|
||||
"super",
|
||||
"switch",
|
||||
"this",
|
||||
"throw",
|
||||
"true",
|
||||
"try",
|
||||
"typeof",
|
||||
"var",
|
||||
"void",
|
||||
"while",
|
||||
"with",
|
||||
"yield",
|
||||
]);
|
||||
const RUNTIME_BINDINGS = new Set([
|
||||
"context",
|
||||
"state",
|
||||
"output",
|
||||
"server",
|
||||
"props",
|
||||
"refs",
|
||||
"event",
|
||||
"payload",
|
||||
]);
|
||||
|
||||
function safeIdentifier(name: string): boolean {
|
||||
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !RESERVED_BINDINGS.has(name);
|
||||
}
|
||||
|
||||
function functionEntry(
|
||||
ast: PageAst,
|
||||
fn: RuntimeFunctionDecl,
|
||||
availableFunctions: string[],
|
||||
): string {
|
||||
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
|
||||
const stateNames = ast.states
|
||||
.filter(
|
||||
(state) =>
|
||||
state.runtime !== "server" &&
|
||||
safeIdentifier(state.name) &&
|
||||
!RUNTIME_BINDINGS.has(state.name) &&
|
||||
!parameterNames.has(state.name),
|
||||
)
|
||||
.map((state) => state.name);
|
||||
const stateSet = new Set(stateNames);
|
||||
const propNames = ast.props
|
||||
.filter(
|
||||
(prop) =>
|
||||
safeIdentifier(prop.name) &&
|
||||
!RUNTIME_BINDINGS.has(prop.name) &&
|
||||
!parameterNames.has(prop.name) &&
|
||||
!stateSet.has(prop.name),
|
||||
)
|
||||
.map((prop) => prop.name);
|
||||
const functionAliases = availableFunctions.filter(
|
||||
(name) =>
|
||||
safeIdentifier(name) &&
|
||||
!RUNTIME_BINDINGS.has(name) &&
|
||||
!parameterNames.has(name) &&
|
||||
!stateSet.has(name) &&
|
||||
!propNames.includes(name),
|
||||
);
|
||||
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
|
||||
const initialStateSnapshot = stateNames.length
|
||||
? `const __wrnexusInitialState = { ${stateNames.map((name) => `${JSON.stringify(name)}: context.state.${name}`).join(", ")} };`
|
||||
: "";
|
||||
const stateAliases = stateNames.length ? `let { ${stateNames.join(", ")} } = context.state;` : "";
|
||||
const propAliases = propNames.length ? `const { ${propNames.join(", ")} } = context.props;` : "";
|
||||
const syncStateToContext = stateNames.map((name) => `context.state.${name} = ${name};`).join(" ");
|
||||
const syncStateFromContext = stateNames
|
||||
.map((name) => `${name} = context.state.${name};`)
|
||||
.join(" ");
|
||||
const peerAliases = functionAliases
|
||||
.map((name) => {
|
||||
const call = `context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs)`;
|
||||
if (!stateNames.length) {
|
||||
return `const ${name} = (...__wrnexusPeerArgs) => ${call};`;
|
||||
}
|
||||
return `const ${name} = (...__wrnexusPeerArgs) => {
|
||||
${syncStateToContext}
|
||||
let __wrnexusPeerResult;
|
||||
try {
|
||||
__wrnexusPeerResult = ${call};
|
||||
} catch (__wrnexusPeerError) {
|
||||
${syncStateFromContext}
|
||||
throw __wrnexusPeerError;
|
||||
}
|
||||
if (__wrnexusPeerResult && typeof __wrnexusPeerResult.then === "function") {
|
||||
return Promise.resolve(__wrnexusPeerResult).finally(() => { ${syncStateFromContext} });
|
||||
}
|
||||
${syncStateFromContext}
|
||||
return __wrnexusPeerResult;
|
||||
};`;
|
||||
})
|
||||
.join("\n");
|
||||
const copyBack = stateNames
|
||||
.map(
|
||||
(name) =>
|
||||
`if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`,
|
||||
)
|
||||
.join("\n");
|
||||
const body = eraseFunctionTypes(fn.body);
|
||||
const runtimeBindings = [
|
||||
!parameterNames.has("output") ? "const output = context.output;" : "",
|
||||
!parameterNames.has("server") ? "const server = context.server;" : "",
|
||||
!parameterNames.has("props") ? "const props = context.props;" : "",
|
||||
!parameterNames.has("refs") ? "const refs = context.refs;" : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n ");
|
||||
return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(context${parameters ? `, ${parameters}` : ""}) {
|
||||
${initialStateSnapshot}
|
||||
${stateAliases}
|
||||
${propAliases}
|
||||
${peerAliases}
|
||||
${runtimeBindings}
|
||||
try {
|
||||
${body}
|
||||
} finally {
|
||||
${copyBack}
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
export function generateBrowserModule(ast: PageAst): string {
|
||||
const functions = ast.runtimeFunctions.filter((fn) =>
|
||||
["legacy", "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 storeImports = ast.structuredImports.filter(
|
||||
(entry) =>
|
||||
!entry.typeOnly && entry.source.endsWith(".wrn") && /(?:^|\/)stores?\//.test(entry.source),
|
||||
);
|
||||
const imports = ast.structuredImports
|
||||
.filter((entry) => !entry.typeOnly)
|
||||
.filter((entry) => !entry.source.endsWith(".wrn") || /(?:^|\/)stores?\//.test(entry.source))
|
||||
.map((entry) => entry.raw)
|
||||
.join("\n");
|
||||
const importedBindings = storeImports
|
||||
.flatMap((entry) => [
|
||||
...(entry.defaultImport ? [entry.defaultImport] : []),
|
||||
...(entry.namespaceImport ? [entry.namespaceImport] : []),
|
||||
...entry.namedImports.map((item) => item.local),
|
||||
])
|
||||
.filter(safeIdentifier);
|
||||
return `// generated WRNexusJS browser module for ${ast.name}
|
||||
${imports}
|
||||
export const __wrnexusClientFunctions = {
|
||||
${functions.map((fn) => ` ${functionEntry(ast, fn, functionNames)}`).join(",\n")}
|
||||
};
|
||||
export const __wrnexusClientState = ${JSON.stringify(state)};
|
||||
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
|
||||
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} };
|
||||
export function bindClientScope(context) {
|
||||
const functions = {};
|
||||
const scopedContext = { ...context, functions };
|
||||
for (const [name, handler] of Object.entries(__wrnexusClientFunctions)) {
|
||||
functions[name] = (...args) => handler(scopedContext, ...args);
|
||||
}
|
||||
return functions;
|
||||
}
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user