100 lines
2.7 KiB
TypeScript
100 lines
2.7 KiB
TypeScript
// This import is generated by code 'bundle.ts'
|
|
export interface View {
|
|
component: engine.Component;
|
|
meta:
|
|
| meta.Meta
|
|
| ((props: { context?: hono.Context }) => Promise<meta.Meta> | meta.Meta);
|
|
layout?: engine.Component;
|
|
inlineCss: string;
|
|
scripts: Record<string, string>;
|
|
}
|
|
|
|
let views: Record<string, View> = null!;
|
|
let scripts: Record<string, string> = 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<string, unknown>,
|
|
) {
|
|
return context.html(await renderViewToString(id, { context, ...props }));
|
|
}
|
|
|
|
export async function renderViewToString(
|
|
id: string,
|
|
props: Record<string, unknown>,
|
|
) {
|
|
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 `<!doctype html><html lang=en><head>${head}${
|
|
inlineCss ? `<style>${inlineCss}</style>` : ""
|
|
}</head><body>${body}${
|
|
scripts ? `<script>${scripts}</script>` : ""
|
|
}</body></html>`;
|
|
}
|
|
|
|
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";
|