release: WRNexusJS 0.8.3
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## 0.8.3
|
||||
|
||||
- Rebuilt the embedded WRN compiler with the 0.8.3 SSR, computed-value, Async-scope, and typed loop-prop fixes.
|
||||
- Added compiler awareness for pruned and bundled browser hydration imports.
|
||||
- Aligned the extension version with the WRNexusJS 0.8.3 framework release.
|
||||
|
||||
## 0.8.0
|
||||
|
||||
- Fixed excessive extension-host and language-server memory growth in large workspaces.
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wrnexus",
|
||||
"version": "0.8.0",
|
||||
"version": "0.8.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wrnexus",
|
||||
"version": "0.8.0",
|
||||
"version": "0.8.3",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"dependencies": {
|
||||
"vscode-languageclient": "^10.1.0"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "wrnexus",
|
||||
"displayName": "WRNexus Language Support",
|
||||
"description": "Complete WRNexus v0.6 language support for typed imports, props, state, outputs, runtime functions, stores, diagnostics, formatting, navigation, and migration assistance.",
|
||||
"version": "0.8.0",
|
||||
"description": "Complete WRNexus v0.8.3 language support for typed imports, props, state, outputs, runtime functions, stores, diagnostics, formatting, navigation, and migration assistance.",
|
||||
"version": "0.8.3",
|
||||
"publisher": "wrnexus",
|
||||
"private": true,
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
|
||||
+303
-61
@@ -1,5 +1,8 @@
|
||||
"use strict";
|
||||
// Generated by scripts/build-editor-compiler.mjs. Do not edit directly.
|
||||
// WRN editor compiler source hash: b9e9a9ed0492c387198e368436696480ef2eca5a850ed94852c78c73c70d2959
|
||||
// WRN editor compiler generator hash: c71e7fe4258c97b73b384ff14b321f0cf0b30cc2ed0322f5f84b04e757159b18
|
||||
// Generated with TypeScript: 5.9.3
|
||||
const __nodeRequire = require;
|
||||
const __path = __nodeRequire("node:path");
|
||||
const __modules = {
|
||||
@@ -406,6 +409,7 @@ exports.DependencyGraph = DependencyGraph;
|
||||
"packages/compiler/src/client-codegen.ts": function (module, exports, require, __filename, __dirname) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.browserModuleRequired = browserModuleRequired;
|
||||
exports.generateBrowserModule = generateBrowserModule;
|
||||
const syntax_1 = require("@wrnexus/syntax");
|
||||
const RESERVED_BINDINGS = new Set([
|
||||
@@ -469,6 +473,91 @@ const RUNTIME_BINDINGS = new Set([
|
||||
function safeIdentifier(name) {
|
||||
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !RESERVED_BINDINGS.has(name);
|
||||
}
|
||||
function identifierReferenced(source, name) {
|
||||
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
return new RegExp(`(?:^|[^A-Za-z0-9_$])${escaped}(?![A-Za-z0-9_$])`).test(source);
|
||||
}
|
||||
function viewReferenceSource(nodes) {
|
||||
return nodes.flatMap((node) => {
|
||||
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, functions) {
|
||||
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, source) {
|
||||
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 = [];
|
||||
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, functions) {
|
||||
const referenceSource = browserReferenceSource(ast, functions);
|
||||
return ast.structuredImports
|
||||
.map((entry) => renderSelectedImport(entry, referenceSource))
|
||||
.filter((entry) => entry !== null);
|
||||
}
|
||||
function browserModuleRequired(ast) {
|
||||
const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
|
||||
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
|
||||
}
|
||||
function functionEntry(ast, fn, availableFunctions) {
|
||||
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
|
||||
const stateNames = ast.states
|
||||
@@ -551,19 +640,9 @@ function generateBrowserModule(ast) {
|
||||
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);
|
||||
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 = {
|
||||
@@ -611,6 +690,7 @@ const types_ts_1 = require("./types.js");
|
||||
const syntax_1 = require("@wrnexus/syntax");
|
||||
const store_codegen_ts_1 = require("./store-codegen.js");
|
||||
const analysis_ts_1 = require("./analysis.js");
|
||||
const client_codegen_ts_1 = require("./client-codegen.js");
|
||||
function isComponentTag(tag) {
|
||||
return /^[A-Z][A-Za-z0-9_$]*$/.test(tag);
|
||||
}
|
||||
@@ -802,7 +882,7 @@ function evalStateSeeds(states) {
|
||||
* runtime keeps it live. Non-state `{expr}` and un-evaluable expressions are
|
||||
* left as literal client mustaches.
|
||||
*/
|
||||
function substituteReactiveText(raw, reactive) {
|
||||
function substituteReactiveText(raw, reactive, dynamicExpressions) {
|
||||
const text = substituteTMarkers(raw);
|
||||
if (!reactive || reactive.stateNames.size === 0)
|
||||
return text;
|
||||
@@ -810,6 +890,11 @@ function substituteReactiveText(raw, reactive) {
|
||||
const expr = inner.trim();
|
||||
if (expr.startsWith("t:") || !exprRefsState(expr, reactive.stateNames))
|
||||
return whole;
|
||||
if (exprRefsState(expr, reactive.runtimeStateNames) && dynamicExpressions) {
|
||||
dynamicExpressions.push(`\${__wrnexusEscapeHtml(${expr})}`);
|
||||
const sentinel = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
|
||||
return `<span data-text="${attrEscape(expr)}">${sentinel}</span>`;
|
||||
}
|
||||
let value;
|
||||
try {
|
||||
value = new Function("with(this){return (" + expr + ");}").call(reactive.scope);
|
||||
@@ -845,7 +930,10 @@ function bakeLoopText(raw) {
|
||||
return out + escLit(raw.slice(last));
|
||||
}
|
||||
/** Bake a loop-body attribute value (same rules as text; escapeHtml is attribute-safe). */
|
||||
function bakeLoopAttr(raw) {
|
||||
function bakeLoopAttr(raw, typed = false) {
|
||||
const wholeExpression = wholeAttributeExpression(raw);
|
||||
if (typed && wholeExpression)
|
||||
return "${__wrnexusPropAttr(" + wholeExpression + ")}";
|
||||
if (!raw.includes("{"))
|
||||
return escLit(attrEscape(raw));
|
||||
let out = "";
|
||||
@@ -878,7 +966,7 @@ function renderLoopBody(node) {
|
||||
if (attr.boolean) {
|
||||
return escLit(` ${name}`);
|
||||
}
|
||||
return escLit(` ${name}="`) + bakeLoopAttr(attr.value) + escLit(`"`);
|
||||
return escLit(` ${name}="`) + bakeLoopAttr(attr.value, componentTag) + escLit(`"`);
|
||||
})
|
||||
.join("");
|
||||
const inner = node.children.map(renderLoopBody).join("");
|
||||
@@ -998,7 +1086,7 @@ function collectControlExprs(nodes, out = []) {
|
||||
}
|
||||
function renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive = null) {
|
||||
if (node.type === "text")
|
||||
return substituteReactiveText(node.value, reactive); // {t:key} + state baking
|
||||
return substituteReactiveText(node.value, reactive, loops); // {t:key} + state baking
|
||||
// Server control block (loop / conditional) → a sentinel that survives
|
||||
// templateEscape, swapped for its real `${…}` code after escaping.
|
||||
if (node.type === "each" || node.type === "if") {
|
||||
@@ -1033,10 +1121,11 @@ function renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive
|
||||
if (node.tag === "Async") {
|
||||
const source = attrValue(node.attrs, "source") ?? "data";
|
||||
const retries = attrValue(node.attrs, "retries") ?? "2";
|
||||
const tags = attrValue(node.attrs, "tags") ?? source;
|
||||
const serverResolved = attrValue(node.attrs, "data-wrn-async-server") === "true";
|
||||
const asyncIndex = serverResolved ? loops.push("") - 1 : -1;
|
||||
const branchElement = (name) => node.children.find((child) => child.type === "element" && child.tag === name);
|
||||
const branch = (name) => {
|
||||
const element = node.children.find((child) => child.type === "element" && child.tag === name);
|
||||
const element = branchElement(name);
|
||||
return (element?.children ?? [])
|
||||
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
|
||||
.join("");
|
||||
@@ -1044,16 +1133,26 @@ function renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive
|
||||
const loading = branch("Loading");
|
||||
const success = branch("Success");
|
||||
const error = branch("Error");
|
||||
const successElement = branchElement("Success");
|
||||
const errorElement = branchElement("Error");
|
||||
const identifier = (value, fallback) => value && isSafeGeneratedIdentifier(value) ? value : fallback;
|
||||
const successAlias = identifier(successElement ? attrValue(successElement.attrs, "data") : undefined, identifier(source, "data"));
|
||||
const errorAlias = identifier(errorElement ? attrValue(errorElement.attrs, "error") : undefined, "error");
|
||||
const nested = (value) => value.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
|
||||
const scoped = (value, alias, expression) => {
|
||||
const index = loops.push(`\${(() => { const ${alias} = ${expression}; return \`${nested(value)}\`; })()}`) - 1;
|
||||
return `\x00WRNEACH${index}\x00`;
|
||||
};
|
||||
const successTemplate = scoped(success, successAlias, `(ctx[${JSON.stringify(source)}] ?? {})`);
|
||||
const errorTemplate = scoped(error, errorAlias, `{ message: "" }`);
|
||||
let initial = loading;
|
||||
if (serverResolved) {
|
||||
const nested = (value) => value.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
|
||||
const sourcePattern = source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const sourcePattern = successAlias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const serverSuccess = success.replace(new RegExp(`\\{\\s*(${sourcePattern}(?:\\.[A-Za-z_$][\\w$]*)*)\\s*\\}`, "g"), (_whole, expression) => `\${__wrnexusEscapeHtml(${expression})}`);
|
||||
loops[asyncIndex] =
|
||||
`\${ctx[${JSON.stringify(source)}] !== undefined ? \`${nested(serverSuccess)}\` : \`${nested(loading)}\`}`;
|
||||
initial = `\x00WRNEACH${asyncIndex}\x00`;
|
||||
const index = loops.push(`\${ctx[${JSON.stringify(source)}] !== undefined ? (() => { const ${successAlias} = ctx[${JSON.stringify(source)}]; return \`${nested(serverSuccess)}\`; })() : \`${nested(loading)}\`}`) - 1;
|
||||
initial = `\x00WRNEACH${index}\x00`;
|
||||
}
|
||||
return `<section data-wrn-async="${attrEscape(source)}" data-wrn-async-retries="${attrEscape(retries)}"${serverResolved ? ' data-wrn-async-resolved="true"' : ""} aria-busy="${serverResolved ? "false" : "true"}"><div data-wrn-async-content>${initial}</div><template data-wrn-async-loading>${loading}</template><template data-wrn-async-success>${success}</template><template data-wrn-async-error>${error}</template></section>`;
|
||||
return `<section data-wrn-async="${attrEscape(source)}" data-wrn-async-tags="${attrEscape(tags)}" data-wrn-async-retries="${attrEscape(retries)}"${serverResolved ? ' data-wrn-async-resolved="true"' : ""} aria-busy="${serverResolved ? "false" : "true"}"><div data-wrn-async-content>${initial}</div><template data-wrn-async-loading>${loading}</template><template data-wrn-async-success data-wrn-async-alias="${attrEscape(successAlias)}">${successTemplate}</template><template data-wrn-async-error data-wrn-async-alias="${attrEscape(errorAlias)}">${errorTemplate}</template></section>`;
|
||||
}
|
||||
if (node.tag === "KeepAlive") {
|
||||
const key = attrValue(node.attrs, "key") ?? "default";
|
||||
@@ -1371,11 +1470,115 @@ function generateSsrStateAliases(stateNames) {
|
||||
}
|
||||
return `const { ${names.join(", ")} } = __state;\n`;
|
||||
}
|
||||
function orderPageStates(entries) {
|
||||
if (entries.length < 2)
|
||||
return entries;
|
||||
const byName = new Map(entries.map((entry) => [entry.name, entry]));
|
||||
const visiting = new Set();
|
||||
const visited = new Set();
|
||||
const ordered = [];
|
||||
const visit = (name) => {
|
||||
if (visited.has(name))
|
||||
return;
|
||||
if (visiting.has(name)) {
|
||||
throw new Error(`WRN-STATE-CYCLE: state value '${name}' has a dependency cycle.`);
|
||||
}
|
||||
const entry = byName.get(name);
|
||||
if (!entry)
|
||||
return;
|
||||
visiting.add(name);
|
||||
for (const dependency of byName.keys()) {
|
||||
if (dependency !== name &&
|
||||
new RegExp(`\\b${dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(entry.expr)) {
|
||||
visit(dependency);
|
||||
}
|
||||
}
|
||||
visiting.delete(name);
|
||||
visited.add(name);
|
||||
ordered.push(entry);
|
||||
};
|
||||
for (const entry of entries)
|
||||
visit(entry.name);
|
||||
return ordered;
|
||||
}
|
||||
function generateSsrStateInitializer(entries) {
|
||||
const ordered = orderPageStates(entries);
|
||||
const declarations = ordered
|
||||
.filter((entry) => isSafeGeneratedIdentifier(entry.name))
|
||||
.map((entry) => `const ${entry.name} = (() => { try { return (${entry.expr}); } catch { return undefined; } })();`)
|
||||
.join(" ");
|
||||
const values = entries
|
||||
.map((entry) => {
|
||||
if (isSafeGeneratedIdentifier(entry.name)) {
|
||||
return `${JSON.stringify(entry.name)}: ${entry.name}`;
|
||||
}
|
||||
return `${JSON.stringify(entry.name)}: (() => { try { return (${entry.expr}); } catch { return undefined; } })()`;
|
||||
})
|
||||
.join(", ");
|
||||
return `(() => { ${declarations} return { ${values} }; })()`;
|
||||
}
|
||||
function orderPageComputed(entries) {
|
||||
if (entries.length < 2)
|
||||
return entries;
|
||||
const byName = new Map(entries.map((entry) => [entry.name, entry]));
|
||||
const visiting = new Set();
|
||||
const visited = new Set();
|
||||
const ordered = [];
|
||||
const visit = (name) => {
|
||||
if (visited.has(name))
|
||||
return;
|
||||
if (visiting.has(name)) {
|
||||
throw new Error(`WRN-COMPUTED-CYCLE: computed value '${name}' has a dependency cycle.`);
|
||||
}
|
||||
const entry = byName.get(name);
|
||||
if (!entry)
|
||||
return;
|
||||
visiting.add(name);
|
||||
for (const dependency of byName.keys()) {
|
||||
if (dependency !== name &&
|
||||
new RegExp(`\\b${dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(entry.expr)) {
|
||||
visit(dependency);
|
||||
}
|
||||
}
|
||||
visiting.delete(name);
|
||||
visited.add(name);
|
||||
ordered.push(entry);
|
||||
};
|
||||
for (const entry of entries)
|
||||
visit(entry.name);
|
||||
return ordered;
|
||||
}
|
||||
function generateSsrComputedAliases(entries) {
|
||||
return orderPageComputed(entries)
|
||||
.filter((entry) => isSafeGeneratedIdentifier(entry.name))
|
||||
.map((entry) => `const ${entry.name} = (() => { try { return (${entry.expr}); } catch { return undefined; } })();`)
|
||||
.join("\n");
|
||||
}
|
||||
function runtimeComputedNames(states, computed, runtimeRoots = []) {
|
||||
const entries = [...states, ...computed];
|
||||
const runtime = new Set([
|
||||
...runtimeRoots,
|
||||
...entries.filter((entry) => /\bctx\b/.test(entry.expr)).map((entry) => entry.name),
|
||||
]);
|
||||
let changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (const entry of entries) {
|
||||
if (runtime.has(entry.name))
|
||||
continue;
|
||||
if ([...runtime].some((name) => new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(entry.expr))) {
|
||||
runtime.add(entry.name);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return runtime;
|
||||
}
|
||||
function hydrationAttribute(ast) {
|
||||
const strategy = ["static", "server"].includes(ast.renderMode ?? "")
|
||||
? "none"
|
||||
: (ast.hydrate ?? "load");
|
||||
const hasBrowserModule = ast.runtimeFunctions.some((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
|
||||
const hasBrowserModule = (0, client_codegen_ts_1.browserModuleRequired)(ast);
|
||||
const moduleAttribute = hasBrowserModule
|
||||
? ' data-wrn-client-module="__WRNEXUS_CLIENT_MODULE__"'
|
||||
: "";
|
||||
@@ -1506,9 +1709,11 @@ function generate(ast) {
|
||||
out.push(`export const __wrnexusNavigation = ${JSON.stringify(ast.navigation, null, 2)};`);
|
||||
}
|
||||
// --- View -> default page component ---
|
||||
const orderedStates = orderPageStates(ast.states);
|
||||
const browserStates = ast.states.filter((state) => state.runtime !== "server");
|
||||
const seedScope = evalStateSeeds(ast.states);
|
||||
for (const entry of ast.computed) {
|
||||
const seedScope = evalStateSeeds(orderedStates);
|
||||
const orderedComputed = orderPageComputed(ast.computed);
|
||||
for (const entry of orderedComputed) {
|
||||
try {
|
||||
seedScope[entry.name] = new Function("with(this){return (" + entry.expr + ");}").call(seedScope);
|
||||
}
|
||||
@@ -1520,7 +1725,14 @@ function generate(ast) {
|
||||
...browserStates.map((entry) => entry.name),
|
||||
...ast.computed.map((entry) => entry.name),
|
||||
];
|
||||
const runtimeStateNames = new Set(ast.states.filter((entry) => /\bctx\b/.test(entry.expr)).map((entry) => entry.name));
|
||||
const storeBindings = importedStoreBindings(ast);
|
||||
const runtimeRoots = [
|
||||
...storeBindings.map((entry) => entry.local),
|
||||
...ast.loads
|
||||
.filter((load) => load.mode === "server" && !load.deferred && load.name)
|
||||
.map((load) => load.name),
|
||||
];
|
||||
const runtimeStateNames = runtimeComputedNames(orderedStates, orderedComputed, runtimeRoots);
|
||||
const reactive = reactiveNames.length > 0
|
||||
? { stateNames: new Set(reactiveNames), runtimeStateNames, scope: seedScope }
|
||||
: null;
|
||||
@@ -1564,9 +1776,7 @@ function generate(ast) {
|
||||
const shellHtml = html.replace(/<wrn-dynamic-region\b[^>]*>[\s\S]*?<\/wrn-dynamic-region>/gi, '<wrn-dynamic-region data-wrn-dynamic="true"></wrn-dynamic-region>');
|
||||
staticShellBody = templateEscape(shellHtml);
|
||||
}
|
||||
const dynamicStateScope = ast.states
|
||||
.map((state) => `${JSON.stringify(state.name)}: (() => { try { return (${state.expr}); } catch { return undefined; } })()`)
|
||||
.join(", ");
|
||||
const dynamicStateInitializer = generateSsrStateInitializer(orderedStates);
|
||||
const stateType = ast.states.length > 0
|
||||
? `{ ${ast.states
|
||||
.map((state) => `${JSON.stringify(state.name)}: ${state.valueType ?? "unknown"}`)
|
||||
@@ -1574,7 +1784,7 @@ function generate(ast) {
|
||||
: "Record<string, never>";
|
||||
const hydrationStateNames = JSON.stringify(browserStates.map((state) => state.name));
|
||||
const ssrStateAliases = generateSsrStateAliases(ast.states.map((state) => state.name));
|
||||
const storeBindings = importedStoreBindings(ast);
|
||||
const ssrComputedAliases = generateSsrComputedAliases(orderedComputed);
|
||||
const storeDeclarations = storeBindings
|
||||
.map((entry) => ` const ${entry.local} = await ctx.__wrnexusUseStore(${entry.internal});`)
|
||||
.join("\n");
|
||||
@@ -1582,12 +1792,16 @@ function generate(ast) {
|
||||
.filter((load) => load.mode === "server" && !load.deferred && load.name)
|
||||
.map((load) => ` const ${load.name} = ctx[${JSON.stringify(load.name)}];`)
|
||||
.join("\n");
|
||||
loops.forEach((code, idx) => {
|
||||
body = body.replace(`\x00WRNEACH${idx}\x00`, () => code);
|
||||
// Inner dynamic expressions are registered before the wrapper that scopes
|
||||
// them (for example an Async branch alias). Resolve from the outside in so a
|
||||
// wrapper sentinel is expanded before its nested sentinels are visited.
|
||||
for (let idx = loops.length - 1; idx >= 0; idx--) {
|
||||
const code = loops[idx];
|
||||
body = body.replaceAll(`\x00WRNEACH${idx}\x00`, code);
|
||||
if (staticShellBody?.includes(`\x00WRNEACH${idx}\x00`)) {
|
||||
staticShellBody = staticShellBody.replace(`\x00WRNEACH${idx}\x00`, () => code);
|
||||
staticShellBody = staticShellBody.replaceAll(`\x00WRNEACH${idx}\x00`, code);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Server loops iterate raw SSR data. Declare a named const for every `ssr` data
|
||||
// binding a loop references, so `{#each <name> as …}` can iterate the real value.
|
||||
const loopConsts = [];
|
||||
@@ -1601,9 +1815,7 @@ function generate(ast) {
|
||||
loopConsts.push(` const ${name} = __wrnexusEvalData(await __wrnexusCallApi(${JSON.stringify(binding.path)}, ${JSON.stringify(binding.method)}, ctx), ${JSON.stringify(binding.body)}, ${JSON.stringify(binding.helpers)}, ctx);`);
|
||||
}
|
||||
}
|
||||
const needsSsrRuntime = ssrBindings.length > 0 ||
|
||||
loops.length > 0 ||
|
||||
ast.states.some((state) => /\bctx\b/.test(state.expr));
|
||||
const needsSsrRuntime = ssrBindings.length > 0 || loops.length > 0 || runtimeStateNames.size > 0;
|
||||
if (needsSsrRuntime) {
|
||||
out.push(ssrRuntimeSource());
|
||||
out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`);
|
||||
@@ -1612,16 +1824,18 @@ function generate(ast) {
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
${decls}
|
||||
const __state: ${stateType} = { ${dynamicStateScope} };
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
const encoded =
|
||||
typeof value === "number" || typeof value === "boolean"
|
||||
? String(value)
|
||||
: JSON.stringify(value == null ? "" : String(value));
|
||||
|
||||
let encoded: string;
|
||||
try {
|
||||
encoded = value === undefined ? "undefined" : JSON.stringify(value);
|
||||
} catch {
|
||||
encoded = JSON.stringify(String(value));
|
||||
}
|
||||
return key + ": " + encoded;
|
||||
})
|
||||
.join(", ")
|
||||
@@ -1642,16 +1856,18 @@ function generate(ast) {
|
||||
out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: any) {
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
const __state: ${stateType} = { ${dynamicStateScope} };
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
const encoded =
|
||||
typeof value === "number" || typeof value === "boolean"
|
||||
? String(value)
|
||||
: JSON.stringify(value == null ? "" : String(value));
|
||||
|
||||
let encoded: string;
|
||||
try {
|
||||
encoded = value === undefined ? "undefined" : JSON.stringify(value);
|
||||
} catch {
|
||||
encoded = JSON.stringify(String(value));
|
||||
}
|
||||
return key + ": " + encoded;
|
||||
})
|
||||
.join(", ")
|
||||
@@ -1671,14 +1887,18 @@ function generate(ast) {
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
${loopConsts.length > 0 ? loopConsts.join("\n") : ""}
|
||||
const __state: ${stateType} = { ${dynamicStateScope} };
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
const encoded = typeof value === "number" || typeof value === "boolean"
|
||||
? String(value)
|
||||
: JSON.stringify(value == null ? "" : String(value));
|
||||
let encoded: string;
|
||||
try {
|
||||
encoded = value === undefined ? "undefined" : JSON.stringify(value);
|
||||
} catch {
|
||||
encoded = JSON.stringify(String(value));
|
||||
}
|
||||
return key + ": " + encoded;
|
||||
})
|
||||
.join(", ")
|
||||
@@ -1845,6 +2065,13 @@ const JS_RESERVED = new Set([
|
||||
"enum",
|
||||
"with",
|
||||
"debugger",
|
||||
"implements",
|
||||
"interface",
|
||||
"package",
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
"static",
|
||||
]);
|
||||
/** A JS reference for a prop/state name (reserved words get a `__p_` prefix). */
|
||||
function safeRef(name) {
|
||||
@@ -2739,13 +2966,28 @@ function candidates(path) {
|
||||
}
|
||||
function resolveWrnImport(declaration, importer, options) {
|
||||
const source = declaration.source;
|
||||
if (!source.startsWith(".") && !source.startsWith("@/"))
|
||||
const aliases = {
|
||||
"@": "./app",
|
||||
...(options.aliases ?? {}),
|
||||
};
|
||||
const alias = Object.keys(aliases)
|
||||
.filter((key) => key.length > 0 && (source === key || source.startsWith(`${key}/`)))
|
||||
.sort((left, right) => right.length - left.length)[0];
|
||||
if (!source.startsWith(".") && !alias)
|
||||
return { declaration, resolved: source };
|
||||
const aliasRoot = options.aliases?.["@"] ?? "./app";
|
||||
const base = source.startsWith("@/")
|
||||
? (0, node_path_1.resolve)(options.appRoot, aliasRoot, source.slice(2))
|
||||
const base = alias
|
||||
? (0, node_path_1.resolve)(options.appRoot, aliases[alias], source === alias ? "" : source.slice(alias.length + 1))
|
||||
: (0, node_path_1.resolve)((0, node_path_1.dirname)(importer), source);
|
||||
const found = candidates(base).find(node_fs_1.existsSync);
|
||||
const found = candidates(base).find((candidate) => {
|
||||
if (!(0, node_fs_1.existsSync)(candidate))
|
||||
return false;
|
||||
try {
|
||||
return (0, node_fs_1.statSync)(candidate).isFile();
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (found)
|
||||
return { declaration, resolved: (0, node_fs_1.realpathSync)(found) };
|
||||
const severity = (options.mode ?? "compatible") === "explicit" ? "error" : "warning";
|
||||
|
||||
@@ -2348,7 +2348,7 @@ var require_connection = __commonJS((exports2) => {
|
||||
}
|
||||
if (responseMessage.id === null) {
|
||||
if (responseMessage.error) {
|
||||
logger.error(`Received response message without id: Error is:
|
||||
logger.error(`Received response message without id: Error is:
|
||||
${JSON.stringify(responseMessage.error, undefined, 4)}`);
|
||||
} else {
|
||||
logger.error(`Received response message without id. No further error information provided.`);
|
||||
@@ -23058,6 +23058,7 @@ ${(0, codegen_ts_1.generate)(ast)}`,
|
||||
},
|
||||
"packages/compiler/src/client-codegen.ts": function(module3, exports3, require2, __filename2, __dirname2) {
|
||||
Object.defineProperty(exports3, "__esModule", { value: true });
|
||||
exports3.browserModuleRequired = browserModuleRequired;
|
||||
exports3.generateBrowserModule = generateBrowserModule;
|
||||
const syntax_1 = require2("@wrnexus/syntax");
|
||||
const RESERVED_BINDINGS = new Set([
|
||||
@@ -23121,6 +23122,84 @@ ${(0, codegen_ts_1.generate)(ast)}`,
|
||||
function safeIdentifier(name) {
|
||||
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !RESERVED_BINDINGS.has(name);
|
||||
}
|
||||
function identifierReferenced(source, name) {
|
||||
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
return new RegExp(`(?:^|[^A-Za-z0-9_$])${escaped}(?![A-Za-z0-9_$])`).test(source);
|
||||
}
|
||||
function viewReferenceSource(nodes) {
|
||||
return nodes.flatMap((node) => {
|
||||
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, functions) {
|
||||
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(`
|
||||
`);
|
||||
}
|
||||
function renderSelectedImport(entry, source) {
|
||||
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 = [];
|
||||
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, functions) {
|
||||
const referenceSource = browserReferenceSource(ast, functions);
|
||||
return ast.structuredImports.map((entry) => renderSelectedImport(entry, referenceSource)).filter((entry) => entry !== null);
|
||||
}
|
||||
function browserModuleRequired(ast) {
|
||||
const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
|
||||
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
|
||||
}
|
||||
function functionEntry(ast, fn, availableFunctions) {
|
||||
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);
|
||||
@@ -23182,14 +23261,10 @@ ${(0, codegen_ts_1.generate)(ast)}`,
|
||||
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(`
|
||||
const selectedImports = selectedBrowserImports(ast, functions);
|
||||
const imports = selectedImports.map((entry) => entry.code).join(`
|
||||
`);
|
||||
const importedBindings = storeImports.flatMap((entry) => [
|
||||
...entry.defaultImport ? [entry.defaultImport] : [],
|
||||
...entry.namespaceImport ? [entry.namespaceImport] : [],
|
||||
...entry.namedImports.map((item) => item.local)
|
||||
]).filter(safeIdentifier);
|
||||
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))];
|
||||
return `// generated WRNexusJS browser module for ${ast.name}
|
||||
${imports}
|
||||
export const __wrnexusClientFunctions = {
|
||||
@@ -23220,6 +23295,7 @@ export function bindClientScope(context) {
|
||||
const syntax_1 = require2("@wrnexus/syntax");
|
||||
const store_codegen_ts_1 = require2("./store-codegen.js");
|
||||
const analysis_ts_1 = require2("./analysis.js");
|
||||
const client_codegen_ts_1 = require2("./client-codegen.js");
|
||||
function isComponentTag(tag) {
|
||||
return /^[A-Z][A-Za-z0-9_$]*$/.test(tag);
|
||||
}
|
||||
@@ -23380,7 +23456,7 @@ export function bindClientScope(context) {
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
function substituteReactiveText(raw, reactive) {
|
||||
function substituteReactiveText(raw, reactive, dynamicExpressions) {
|
||||
const text = substituteTMarkers(raw);
|
||||
if (!reactive || reactive.stateNames.size === 0)
|
||||
return text;
|
||||
@@ -23388,6 +23464,11 @@ export function bindClientScope(context) {
|
||||
const expr = inner.trim();
|
||||
if (expr.startsWith("t:") || !exprRefsState(expr, reactive.stateNames))
|
||||
return whole;
|
||||
if (exprRefsState(expr, reactive.runtimeStateNames) && dynamicExpressions) {
|
||||
dynamicExpressions.push(`\${__wrnexusEscapeHtml(${expr})}`);
|
||||
const sentinel = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
|
||||
return `<span data-text="${attrEscape(expr)}">${sentinel}</span>`;
|
||||
}
|
||||
let value;
|
||||
try {
|
||||
value = new Function("with(this){return (" + expr + ");}").call(reactive.scope);
|
||||
@@ -23415,7 +23496,10 @@ export function bindClientScope(context) {
|
||||
}
|
||||
return out + escLit(raw.slice(last));
|
||||
}
|
||||
function bakeLoopAttr(raw) {
|
||||
function bakeLoopAttr(raw, typed = false) {
|
||||
const wholeExpression = wholeAttributeExpression(raw);
|
||||
if (typed && wholeExpression)
|
||||
return "${__wrnexusPropAttr(" + wholeExpression + ")}";
|
||||
if (!raw.includes("{"))
|
||||
return escLit(attrEscape(raw));
|
||||
let out = "";
|
||||
@@ -23445,7 +23529,7 @@ export function bindClientScope(context) {
|
||||
if (attr.boolean) {
|
||||
return escLit(` ${name}`);
|
||||
}
|
||||
return escLit(` ${name}="`) + bakeLoopAttr(attr.value) + escLit(`"`);
|
||||
return escLit(` ${name}="`) + bakeLoopAttr(attr.value, componentTag) + escLit(`"`);
|
||||
}).join("");
|
||||
const inner = node.children.map(renderLoopBody).join("");
|
||||
if (node.tag === "Static")
|
||||
@@ -23514,7 +23598,7 @@ export function bindClientScope(context) {
|
||||
}
|
||||
function renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive = null) {
|
||||
if (node.type === "text")
|
||||
return substituteReactiveText(node.value, reactive);
|
||||
return substituteReactiveText(node.value, reactive, loops);
|
||||
if (node.type === "each" || node.type === "if") {
|
||||
loops.push(node.type === "each" ? compileEachExpr(node) : compileIfExpr(node));
|
||||
return `\x00WRNEACH${loops.length - 1}\x00`;
|
||||
@@ -23535,24 +23619,36 @@ export function bindClientScope(context) {
|
||||
if (node.tag === "Async") {
|
||||
const source = attrValue(node.attrs, "source") ?? "data";
|
||||
const retries = attrValue(node.attrs, "retries") ?? "2";
|
||||
const tags = attrValue(node.attrs, "tags") ?? source;
|
||||
const serverResolved = attrValue(node.attrs, "data-wrn-async-server") === "true";
|
||||
const asyncIndex = serverResolved ? loops.push("") - 1 : -1;
|
||||
const branchElement = (name) => node.children.find((child) => child.type === "element" && child.tag === name);
|
||||
const branch = (name) => {
|
||||
const element = node.children.find((child) => child.type === "element" && child.tag === name);
|
||||
const element = branchElement(name);
|
||||
return (element?.children ?? []).map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join("");
|
||||
};
|
||||
const loading = branch("Loading");
|
||||
const success = branch("Success");
|
||||
const error = branch("Error");
|
||||
const successElement = branchElement("Success");
|
||||
const errorElement = branchElement("Error");
|
||||
const identifier = (value, fallback) => value && isSafeGeneratedIdentifier(value) ? value : fallback;
|
||||
const successAlias = identifier(successElement ? attrValue(successElement.attrs, "data") : undefined, identifier(source, "data"));
|
||||
const errorAlias = identifier(errorElement ? attrValue(errorElement.attrs, "error") : undefined, "error");
|
||||
const nested = (value) => value.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
|
||||
const scoped = (value, alias, expression) => {
|
||||
const index = loops.push(`\${(() => { const ${alias} = ${expression}; return \`${nested(value)}\`; })()}`) - 1;
|
||||
return `\x00WRNEACH${index}\x00`;
|
||||
};
|
||||
const successTemplate = scoped(success, successAlias, `(ctx[${JSON.stringify(source)}] ?? {})`);
|
||||
const errorTemplate = scoped(error, errorAlias, `{ message: "" }`);
|
||||
let initial = loading;
|
||||
if (serverResolved) {
|
||||
const nested = (value) => value.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
|
||||
const sourcePattern = source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const sourcePattern = successAlias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const serverSuccess = success.replace(new RegExp(`\\{\\s*(${sourcePattern}(?:\\.[A-Za-z_$][\\w$]*)*)\\s*\\}`, "g"), (_whole, expression) => `\${__wrnexusEscapeHtml(${expression})}`);
|
||||
loops[asyncIndex] = `\${ctx[${JSON.stringify(source)}] !== undefined ? \`${nested(serverSuccess)}\` : \`${nested(loading)}\`}`;
|
||||
initial = `\x00WRNEACH${asyncIndex}\x00`;
|
||||
const index = loops.push(`\${ctx[${JSON.stringify(source)}] !== undefined ? (() => { const ${successAlias} = ctx[${JSON.stringify(source)}]; return \`${nested(serverSuccess)}\`; })() : \`${nested(loading)}\`}`) - 1;
|
||||
initial = `\x00WRNEACH${index}\x00`;
|
||||
}
|
||||
return `<section data-wrn-async="${attrEscape(source)}" data-wrn-async-retries="${attrEscape(retries)}"${serverResolved ? ' data-wrn-async-resolved="true"' : ""} aria-busy="${serverResolved ? "false" : "true"}"><div data-wrn-async-content>${initial}</div><template data-wrn-async-loading>${loading}</template><template data-wrn-async-success>${success}</template><template data-wrn-async-error>${error}</template></section>`;
|
||||
return `<section data-wrn-async="${attrEscape(source)}" data-wrn-async-tags="${attrEscape(tags)}" data-wrn-async-retries="${attrEscape(retries)}"${serverResolved ? ' data-wrn-async-resolved="true"' : ""} aria-busy="${serverResolved ? "false" : "true"}"><div data-wrn-async-content>${initial}</div><template data-wrn-async-loading>${loading}</template><template data-wrn-async-success data-wrn-async-alias="${attrEscape(successAlias)}">${successTemplate}</template><template data-wrn-async-error data-wrn-async-alias="${attrEscape(errorAlias)}">${errorTemplate}</template></section>`;
|
||||
}
|
||||
if (node.tag === "KeepAlive") {
|
||||
const key = attrValue(node.attrs, "key") ?? "default";
|
||||
@@ -23836,9 +23932,104 @@ ${css}
|
||||
return `const { ${names.join(", ")} } = __state;
|
||||
`;
|
||||
}
|
||||
function orderPageStates(entries) {
|
||||
if (entries.length < 2)
|
||||
return entries;
|
||||
const byName = new Map(entries.map((entry) => [entry.name, entry]));
|
||||
const visiting = new Set;
|
||||
const visited = new Set;
|
||||
const ordered = [];
|
||||
const visit = (name) => {
|
||||
if (visited.has(name))
|
||||
return;
|
||||
if (visiting.has(name)) {
|
||||
throw new Error(`WRN-STATE-CYCLE: state value '${name}' has a dependency cycle.`);
|
||||
}
|
||||
const entry = byName.get(name);
|
||||
if (!entry)
|
||||
return;
|
||||
visiting.add(name);
|
||||
for (const dependency of byName.keys()) {
|
||||
if (dependency !== name && new RegExp(`\\b${dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(entry.expr)) {
|
||||
visit(dependency);
|
||||
}
|
||||
}
|
||||
visiting.delete(name);
|
||||
visited.add(name);
|
||||
ordered.push(entry);
|
||||
};
|
||||
for (const entry of entries)
|
||||
visit(entry.name);
|
||||
return ordered;
|
||||
}
|
||||
function generateSsrStateInitializer(entries) {
|
||||
const ordered = orderPageStates(entries);
|
||||
const declarations = ordered.filter((entry) => isSafeGeneratedIdentifier(entry.name)).map((entry) => `const ${entry.name} = (() => { try { return (${entry.expr}); } catch { return undefined; } })();`).join(" ");
|
||||
const values = entries.map((entry) => {
|
||||
if (isSafeGeneratedIdentifier(entry.name)) {
|
||||
return `${JSON.stringify(entry.name)}: ${entry.name}`;
|
||||
}
|
||||
return `${JSON.stringify(entry.name)}: (() => { try { return (${entry.expr}); } catch { return undefined; } })()`;
|
||||
}).join(", ");
|
||||
return `(() => { ${declarations} return { ${values} }; })()`;
|
||||
}
|
||||
function orderPageComputed(entries) {
|
||||
if (entries.length < 2)
|
||||
return entries;
|
||||
const byName = new Map(entries.map((entry) => [entry.name, entry]));
|
||||
const visiting = new Set;
|
||||
const visited = new Set;
|
||||
const ordered = [];
|
||||
const visit = (name) => {
|
||||
if (visited.has(name))
|
||||
return;
|
||||
if (visiting.has(name)) {
|
||||
throw new Error(`WRN-COMPUTED-CYCLE: computed value '${name}' has a dependency cycle.`);
|
||||
}
|
||||
const entry = byName.get(name);
|
||||
if (!entry)
|
||||
return;
|
||||
visiting.add(name);
|
||||
for (const dependency of byName.keys()) {
|
||||
if (dependency !== name && new RegExp(`\\b${dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(entry.expr)) {
|
||||
visit(dependency);
|
||||
}
|
||||
}
|
||||
visiting.delete(name);
|
||||
visited.add(name);
|
||||
ordered.push(entry);
|
||||
};
|
||||
for (const entry of entries)
|
||||
visit(entry.name);
|
||||
return ordered;
|
||||
}
|
||||
function generateSsrComputedAliases(entries) {
|
||||
return orderPageComputed(entries).filter((entry) => isSafeGeneratedIdentifier(entry.name)).map((entry) => `const ${entry.name} = (() => { try { return (${entry.expr}); } catch { return undefined; } })();`).join(`
|
||||
`);
|
||||
}
|
||||
function runtimeComputedNames(states, computed, runtimeRoots = []) {
|
||||
const entries = [...states, ...computed];
|
||||
const runtime = new Set([
|
||||
...runtimeRoots,
|
||||
...entries.filter((entry) => /\bctx\b/.test(entry.expr)).map((entry) => entry.name)
|
||||
]);
|
||||
let changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (const entry of entries) {
|
||||
if (runtime.has(entry.name))
|
||||
continue;
|
||||
if ([...runtime].some((name) => new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(entry.expr))) {
|
||||
runtime.add(entry.name);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return runtime;
|
||||
}
|
||||
function hydrationAttribute(ast) {
|
||||
const strategy = ["static", "server"].includes(ast.renderMode ?? "") ? "none" : ast.hydrate ?? "load";
|
||||
const hasBrowserModule = ast.runtimeFunctions.some((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
|
||||
const hasBrowserModule = (0, client_codegen_ts_1.browserModuleRequired)(ast);
|
||||
const moduleAttribute = hasBrowserModule ? ' data-wrn-client-module="__WRNEXUS_CLIENT_MODULE__"' : "";
|
||||
return ` data-wrn-hydration="${attrEscape(hydrationId(ast))}" data-wrn-hydrate="${attrEscape(strategy)}" data-wrn-runtime="${attrEscape(ast.runtime ?? "universal")}"${moduleAttribute}`;
|
||||
}
|
||||
@@ -23960,9 +24151,11 @@ ${helpers}`);
|
||||
if (Object.keys(ast.navigation).length > 0) {
|
||||
out.push(`export const __wrnexusNavigation = ${JSON.stringify(ast.navigation, null, 2)};`);
|
||||
}
|
||||
const orderedStates = orderPageStates(ast.states);
|
||||
const browserStates = ast.states.filter((state) => state.runtime !== "server");
|
||||
const seedScope = evalStateSeeds(ast.states);
|
||||
for (const entry of ast.computed) {
|
||||
const seedScope = evalStateSeeds(orderedStates);
|
||||
const orderedComputed = orderPageComputed(ast.computed);
|
||||
for (const entry of orderedComputed) {
|
||||
try {
|
||||
seedScope[entry.name] = new Function("with(this){return (" + entry.expr + ");}").call(seedScope);
|
||||
} catch {
|
||||
@@ -23973,7 +24166,12 @@ ${helpers}`);
|
||||
...browserStates.map((entry) => entry.name),
|
||||
...ast.computed.map((entry) => entry.name)
|
||||
];
|
||||
const runtimeStateNames = new Set(ast.states.filter((entry) => /\bctx\b/.test(entry.expr)).map((entry) => entry.name));
|
||||
const storeBindings = importedStoreBindings(ast);
|
||||
const runtimeRoots = [
|
||||
...storeBindings.map((entry) => entry.local),
|
||||
...ast.loads.filter((load) => load.mode === "server" && !load.deferred && load.name).map((load) => load.name)
|
||||
];
|
||||
const runtimeStateNames = runtimeComputedNames(orderedStates, orderedComputed, runtimeRoots);
|
||||
const reactive = reactiveNames.length > 0 ? { stateNames: new Set(reactiveNames), runtimeStateNames, scope: seedScope } : null;
|
||||
const loops = [];
|
||||
let html = ast.view.map((node) => renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive)).join("");
|
||||
@@ -24007,21 +24205,22 @@ ${helpers}`);
|
||||
const shellHtml = html.replace(/<wrn-dynamic-region\b[^>]*>[\s\S]*?<\/wrn-dynamic-region>/gi, '<wrn-dynamic-region data-wrn-dynamic="true"></wrn-dynamic-region>');
|
||||
staticShellBody = templateEscape(shellHtml);
|
||||
}
|
||||
const dynamicStateScope = ast.states.map((state) => `${JSON.stringify(state.name)}: (() => { try { return (${state.expr}); } catch { return undefined; } })()`).join(", ");
|
||||
const dynamicStateInitializer = generateSsrStateInitializer(orderedStates);
|
||||
const stateType = ast.states.length > 0 ? `{ ${ast.states.map((state) => `${JSON.stringify(state.name)}: ${state.valueType ?? "unknown"}`).join("; ")} }` : "Record<string, never>";
|
||||
const hydrationStateNames = JSON.stringify(browserStates.map((state) => state.name));
|
||||
const ssrStateAliases = generateSsrStateAliases(ast.states.map((state) => state.name));
|
||||
const storeBindings = importedStoreBindings(ast);
|
||||
const ssrComputedAliases = generateSsrComputedAliases(orderedComputed);
|
||||
const storeDeclarations = storeBindings.map((entry) => ` const ${entry.local} = await ctx.__wrnexusUseStore(${entry.internal});`).join(`
|
||||
`);
|
||||
const serverLoadAliases = ast.loads.filter((load) => load.mode === "server" && !load.deferred && load.name).map((load) => ` const ${load.name} = ctx[${JSON.stringify(load.name)}];`).join(`
|
||||
`);
|
||||
loops.forEach((code, idx) => {
|
||||
body = body.replace(`\x00WRNEACH${idx}\x00`, () => code);
|
||||
for (let idx = loops.length - 1;idx >= 0; idx--) {
|
||||
const code = loops[idx];
|
||||
body = body.replaceAll(`\x00WRNEACH${idx}\x00`, code);
|
||||
if (staticShellBody?.includes(`\x00WRNEACH${idx}\x00`)) {
|
||||
staticShellBody = staticShellBody.replace(`\x00WRNEACH${idx}\x00`, () => code);
|
||||
staticShellBody = staticShellBody.replaceAll(`\x00WRNEACH${idx}\x00`, code);
|
||||
}
|
||||
});
|
||||
}
|
||||
const loopConsts = [];
|
||||
if (loops.length > 0) {
|
||||
const lists = collectControlExprs(ast.view);
|
||||
@@ -24033,7 +24232,7 @@ ${helpers}`);
|
||||
loopConsts.push(` const ${name} = __wrnexusEvalData(await __wrnexusCallApi(${JSON.stringify(binding.path)}, ${JSON.stringify(binding.method)}, ctx), ${JSON.stringify(binding.body)}, ${JSON.stringify(binding.helpers)}, ctx);`);
|
||||
}
|
||||
}
|
||||
const needsSsrRuntime = ssrBindings.length > 0 || loops.length > 0 || ast.states.some((state) => /\bctx\b/.test(state.expr));
|
||||
const needsSsrRuntime = ssrBindings.length > 0 || loops.length > 0 || runtimeStateNames.size > 0;
|
||||
if (needsSsrRuntime) {
|
||||
out.push(ssrRuntimeSource());
|
||||
out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`);
|
||||
@@ -24044,16 +24243,18 @@ ${helpers}`);
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
${decls}
|
||||
const __state: ${stateType} = { ${dynamicStateScope} };
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
const encoded =
|
||||
typeof value === "number" || typeof value === "boolean"
|
||||
? String(value)
|
||||
: JSON.stringify(value == null ? "" : String(value));
|
||||
|
||||
let encoded: string;
|
||||
try {
|
||||
encoded = value === undefined ? "undefined" : JSON.stringify(value);
|
||||
} catch {
|
||||
encoded = JSON.stringify(String(value));
|
||||
}
|
||||
return key + ": " + encoded;
|
||||
})
|
||||
.join(", ")
|
||||
@@ -24073,16 +24274,18 @@ ${helpers}`);
|
||||
out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: any) {
|
||||
${storeDeclarations}
|
||||
${serverLoadAliases}
|
||||
const __state: ${stateType} = { ${dynamicStateScope} };
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
const encoded =
|
||||
typeof value === "number" || typeof value === "boolean"
|
||||
? String(value)
|
||||
: JSON.stringify(value == null ? "" : String(value));
|
||||
|
||||
let encoded: string;
|
||||
try {
|
||||
encoded = value === undefined ? "undefined" : JSON.stringify(value);
|
||||
} catch {
|
||||
encoded = JSON.stringify(String(value));
|
||||
}
|
||||
return key + ": " + encoded;
|
||||
})
|
||||
.join(", ")
|
||||
@@ -24103,14 +24306,18 @@ ${helpers}`);
|
||||
${serverLoadAliases}
|
||||
${loopConsts.length > 0 ? loopConsts.join(`
|
||||
`) : ""}
|
||||
const __state: ${stateType} = { ${dynamicStateScope} };
|
||||
const __state: ${stateType} = ${dynamicStateInitializer};
|
||||
${ssrStateAliases}
|
||||
${ssrComputedAliases}
|
||||
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, (__state as any)[key]]));
|
||||
const __scopeValue = Object.entries(__hydrationState)
|
||||
.map(([key, value]) => {
|
||||
const encoded = typeof value === "number" || typeof value === "boolean"
|
||||
? String(value)
|
||||
: JSON.stringify(value == null ? "" : String(value));
|
||||
let encoded: string;
|
||||
try {
|
||||
encoded = value === undefined ? "undefined" : JSON.stringify(value);
|
||||
} catch {
|
||||
encoded = JSON.stringify(String(value));
|
||||
}
|
||||
return key + ": " + encoded;
|
||||
})
|
||||
.join(", ")
|
||||
@@ -24270,7 +24477,14 @@ ${handlers.join(`
|
||||
"await",
|
||||
"enum",
|
||||
"with",
|
||||
"debugger"
|
||||
"debugger",
|
||||
"implements",
|
||||
"interface",
|
||||
"package",
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
"static"
|
||||
]);
|
||||
function safeRef(name) {
|
||||
return JS_RESERVED.has(name) ? `__p_${name}` : name;
|
||||
@@ -25044,11 +25258,23 @@ function __wireRaw(v: any): string {
|
||||
}
|
||||
function resolveWrnImport(declaration, importer, options) {
|
||||
const source = declaration.source;
|
||||
if (!source.startsWith(".") && !source.startsWith("@/"))
|
||||
const aliases = {
|
||||
"@": "./app",
|
||||
...options.aliases ?? {}
|
||||
};
|
||||
const alias = Object.keys(aliases).filter((key) => key.length > 0 && (source === key || source.startsWith(`${key}/`))).sort((left, right) => right.length - left.length)[0];
|
||||
if (!source.startsWith(".") && !alias)
|
||||
return { declaration, resolved: source };
|
||||
const aliasRoot = options.aliases?.["@"] ?? "./app";
|
||||
const base = source.startsWith("@/") ? (0, node_path_1.resolve)(options.appRoot, aliasRoot, source.slice(2)) : (0, node_path_1.resolve)((0, node_path_1.dirname)(importer), source);
|
||||
const found = candidates(base).find(node_fs_1.existsSync);
|
||||
const base = alias ? (0, node_path_1.resolve)(options.appRoot, aliases[alias], source === alias ? "" : source.slice(alias.length + 1)) : (0, node_path_1.resolve)((0, node_path_1.dirname)(importer), source);
|
||||
const found = candidates(base).find((candidate) => {
|
||||
if (!(0, node_fs_1.existsSync)(candidate))
|
||||
return false;
|
||||
try {
|
||||
return (0, node_fs_1.statSync)(candidate).isFile();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (found)
|
||||
return { declaration, resolved: (0, node_fs_1.realpathSync)(found) };
|
||||
const severity = (options.mode ?? "compatible") === "explicit" ? "error" : "warning";
|
||||
|
||||
@@ -40,12 +40,12 @@ var require_typescript = __commonJS((exports2, module2) => {
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
|
||||
See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
***************************************************************************** */
|
||||
|
||||
Reference in New Issue
Block a user