import { partialPrerender } from "@wrnexus/ssr"; import { fillSlots, normalizeComponentName, parseComponentProps, readElementBody, } from "./runtime.ts"; export interface PartialBuildModule { default?: unknown; render?: (props?: Record) => string | Promise; layout?: string | { name?: string; render?: (props?: Record) => string }; __wrnexusBuildStaticShell?: (ctx?: Record) => string | Promise; } export interface PartialBuildEntry { name: string; mod: PartialBuildModule; } const MOUNT_OPEN_RE = /<([A-Za-z][A-Za-z0-9-]*)\b([^>]*?\bdata-component="([A-Za-z0-9_-]+)"[^>]*?)(\/?)>/; /** Expand compiler component mounts at build time using only their pure render exports. */ export async function expandStaticComponents( html: string, components: readonly PartialBuildEntry[], depth = 0, ): Promise { if (depth > 15) throw new Error("WRN-PARTIAL-STATIC-DEPTH: component nesting exceeds 15"); if (!html.includes("data-component=")) return html; let output = ""; let cursor = 0; for (;;) { const match = MOUNT_OPEN_RE.exec(html.slice(cursor)); if (!match) return output + html.slice(cursor); const start = cursor + match.index; output += html.slice(cursor, start); const [open, tag, attributes, name, selfClosing] = match; const openEnd = start + open.length; const body = selfClosing === "/" ? { inner: "", end: openEnd } : readElementBody(html, tag!, openEnd); const component = components.find( (entry) => normalizeComponentName(entry.name) === normalizeComponentName(name!), ); if (!component || typeof component.mod.render !== "function") { throw new Error(`WRN-PARTIAL-STATIC-COMPONENT: '${name}' has no build-time renderer`); } const rendered = await component.mod.render(parseComponentProps(attributes!)); output += await expandStaticComponents( fillSlots(String(rendered), body.inner, true), components, depth + 1, ); cursor = body.end; } } /** Produce the body shell stored in dist; dynamic region bodies are never evaluated here. */ export async function precomputePartialStaticShell( page: PartialBuildModule, components: readonly PartialBuildEntry[], ): Promise<{ shell: string; regions: number }> { if (typeof page.__wrnexusBuildStaticShell !== "function") { throw new Error("WRN-PARTIAL-STATIC-EXPORT: compiler did not emit a static-shell renderer"); } const body = await expandStaticComponents( String(await page.__wrnexusBuildStaticShell({})), components, ); const result = partialPrerender(body); return { shell: result.shell, regions: result.regions.length }; }