// This import is generated by code 'bundle.ts' export interface View { component: engine.Component; meta: | meta.Meta | ((props: { context?: hono.Context }) => Promise | meta.Meta); layout?: engine.Component; inlineCss: string; scripts: Record; } let views: Record = null!; let scripts: Record = null!; // An older version of the Clover Engine supported streaming suspense // boundaries, but those were never used. Pages will wait until they // are fully rendered before sending. export async function renderView( context: hono.Context, id: string, props: Record, ) { return context.html(await renderViewToString(id, { context, ...props })); } export async function renderViewToString( id: string, props: Record, ) { views ?? ({ views, scripts } = require("$views")); // The view contains pre-bundled CSS and scripts, but keeps the scripts // separate for run-time dynamic scripts. For example, the file viewer // includes the canvas for the current page, but only the current page. const { component, inlineCss, layout, meta: metadata, }: View = UNWRAP(views[id], `Missing view ${id}`); // -- metadata -- const renderedMetaPromise = Promise.resolve( typeof metadata === "function" ? metadata(props) : metadata, ).then((m) => meta.renderMeta(m)); // -- html -- let page: engine.Element = [engine.kElement, component, props]; if (layout) page = [engine.kElement, layout, { children: page }]; const { text: body, addon: { sitegen } } = await engine.ssrAsync(page, { sitegen: sg.initRender(), }); // -- join document and send -- return wrapDocument({ body, head: await renderedMetaPromise, inlineCss, scripts: joinScripts( Array.from( sitegen.scripts, (id) => UNWRAP(scripts[id], `Missing script ${id}`), ), ), }); } export function provideViewData(v: typeof views, s: typeof scripts) { views = v; scripts = s; } export function joinScripts(scriptSources: string[]) { const { length } = scriptSources; if (length === 0) return ""; if (length === 1) return scriptSources[0]; return scriptSources.map((source) => `{${source}}`).join(";"); } export function wrapDocument({ body, head, inlineCss, scripts, }: { head: string; body: string; inlineCss: string; scripts: string; }) { return `${head}${ inlineCss ? `` : "" }${body}${ scripts ? `` : "" }`; } import * as meta from "./meta.ts"; import type * as hono from "#hono"; import * as engine from "../engine/ssr.ts"; import * as sg from "./sitegen.ts";