release: WRNexusJS 0.8.3
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
import { eraseFunctionTypes, type PageAst, type RuntimeFunctionDecl } from "@wrnexus/syntax";
|
||||
import {
|
||||
eraseFunctionTypes,
|
||||
type PageAst,
|
||||
type RuntimeFunctionDecl,
|
||||
type StructuredImportDecl,
|
||||
type ViewNode,
|
||||
} from "@wrnexus/syntax";
|
||||
|
||||
const RESERVED_BINDINGS = new Set([
|
||||
"await",
|
||||
@@ -63,6 +69,111 @@ function safeIdentifier(name: string): boolean {
|
||||
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !RESERVED_BINDINGS.has(name);
|
||||
}
|
||||
|
||||
function identifierReferenced(source: string, name: string): boolean {
|
||||
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
return new RegExp(`(?:^|[^A-Za-z0-9_$])${escaped}(?![A-Za-z0-9_$])`).test(source);
|
||||
}
|
||||
|
||||
function viewReferenceSource(nodes: ViewNode[]): string[] {
|
||||
return nodes.flatMap((node): string[] => {
|
||||
if (node.type === "text") return [node.value];
|
||||
if (node.type === "each") {
|
||||
return [
|
||||
node.list,
|
||||
node.key ?? "",
|
||||
...viewReferenceSource(node.body),
|
||||
...viewReferenceSource(node.empty),
|
||||
];
|
||||
}
|
||||
if (node.type === "if") {
|
||||
return node.branches.flatMap((branch) => [
|
||||
branch.cond ?? "",
|
||||
...viewReferenceSource(branch.body),
|
||||
]);
|
||||
}
|
||||
return [...node.attrs.map((attr) => attr.value), ...viewReferenceSource(node.children)];
|
||||
});
|
||||
}
|
||||
|
||||
function browserReferenceSource(ast: PageAst, functions: RuntimeFunctionDecl[]): string {
|
||||
return [
|
||||
...functions.flatMap((fn) => [fn.source, fn.body]),
|
||||
...ast.computed.map((entry) => entry.expr),
|
||||
...ast.effects,
|
||||
ast.lifecycle.mount ?? "",
|
||||
ast.lifecycle.update ?? "",
|
||||
ast.lifecycle.unmount ?? "",
|
||||
...viewReferenceSource(ast.view),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function renderSelectedImport(
|
||||
entry: StructuredImportDecl,
|
||||
source: string,
|
||||
): { code: string; bindings: string[] } | null {
|
||||
if (entry.typeOnly) return null;
|
||||
const isStore = entry.source.endsWith(".wrn") && /(?:^|\/)stores?\//.test(entry.source);
|
||||
if (entry.source.endsWith(".wrn") && !isStore) return null;
|
||||
|
||||
const defaultImport =
|
||||
entry.defaultImport && (isStore || identifierReferenced(source, entry.defaultImport))
|
||||
? entry.defaultImport
|
||||
: undefined;
|
||||
const namespaceImport =
|
||||
entry.namespaceImport && (isStore || identifierReferenced(source, entry.namespaceImport))
|
||||
? entry.namespaceImport
|
||||
: undefined;
|
||||
const namedImports = entry.namedImports.filter(
|
||||
(item) => !item.typeOnly && (isStore || identifierReferenced(source, item.local)),
|
||||
);
|
||||
|
||||
const bindings = [
|
||||
...(defaultImport ? [defaultImport] : []),
|
||||
...(namespaceImport ? [namespaceImport] : []),
|
||||
...namedImports.map((item) => item.local),
|
||||
].filter(safeIdentifier);
|
||||
|
||||
const hasBindings = Boolean(defaultImport || namespaceImport || namedImports.length);
|
||||
const sideEffectOnly =
|
||||
!entry.defaultImport && !entry.namespaceImport && entry.namedImports.length === 0;
|
||||
if (!hasBindings && !sideEffectOnly) return null;
|
||||
if (sideEffectOnly) return { code: entry.raw, bindings: [] };
|
||||
|
||||
const clauses: string[] = [];
|
||||
if (defaultImport) clauses.push(defaultImport);
|
||||
if (namespaceImport) clauses.push(`* as ${namespaceImport}`);
|
||||
if (namedImports.length) {
|
||||
clauses.push(
|
||||
`{ ${namedImports
|
||||
.map((item) =>
|
||||
item.imported === item.local ? item.imported : `${item.imported} as ${item.local}`,
|
||||
)
|
||||
.join(", ")} }`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
code: `import ${clauses.join(", ")} from ${JSON.stringify(entry.source)};`,
|
||||
bindings,
|
||||
};
|
||||
}
|
||||
|
||||
function selectedBrowserImports(
|
||||
ast: PageAst,
|
||||
functions: RuntimeFunctionDecl[],
|
||||
): { code: string; bindings: string[] }[] {
|
||||
const referenceSource = browserReferenceSource(ast, functions);
|
||||
return ast.structuredImports
|
||||
.map((entry) => renderSelectedImport(entry, referenceSource))
|
||||
.filter((entry): entry is { code: string; bindings: string[] } => entry !== null);
|
||||
}
|
||||
|
||||
export function browserModuleRequired(ast: PageAst): boolean {
|
||||
const functions = ast.runtimeFunctions.filter((fn) =>
|
||||
["legacy", "client", "shared"].includes(fn.runtime),
|
||||
);
|
||||
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
|
||||
}
|
||||
|
||||
function functionEntry(
|
||||
ast: PageAst,
|
||||
fn: RuntimeFunctionDecl,
|
||||
@@ -164,22 +275,9 @@ export function generateBrowserModule(ast: PageAst): string {
|
||||
);
|
||||
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);
|
||||
const selectedImports = selectedBrowserImports(ast, functions);
|
||||
const imports = selectedImports.map((entry) => entry.code).join("\n");
|
||||
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))];
|
||||
return `// generated WRNexusJS browser module for ${ast.name}
|
||||
${imports}
|
||||
export const __wrnexusClientFunctions = {
|
||||
|
||||
Reference in New Issue
Block a user