export interface View { component: render.Component; meta: | meta.Meta | ((props: { context?: hono.Context }) => Promise | meta.Meta); layout?: render.Component; inlineCss: string; scripts: Record; } let views: Record = null!; let scripts: Record = null!; 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: render.Element = render.element(component, props); if (layout) page = render.element(layout, { children: page }); const { text: body, addon: { [sg.userData.key]: sitegen }, } = await render.async(page, { [sg.userData.key]: 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 render from "#engine/render"; import * as sg from "./sitegen.ts";