/** * @nifrajs/web-vanilla - the zero-framework render adapter for @nifrajs/web. * * Pages are plain functions returning auto-escaping `html` tagged templates; the client ships * **no framework runtime at all**. Use it for content/SEO surfaces (landing pages, listings, * docs, comparison tables) where HTML is the product - interactivity comes from * `@nifrajs/web/islands` entries (`islandScripts`), each a hand-sized vanilla module. * * Routes using this adapter are server-rendered documents, not hydrated apps - set * `export const hydrate = false` on the route (there is no client runtime to hydrate with); * `renderPage` then omits the framework bootstrap entirely. Loaders, actions, ISR, SSG, head * management, and streaming all work unchanged - they live in @nifrajs/web, not the view layer. * * import { html, vanillaAdapter } from "@nifrajs/web-vanilla" * * export default function Hotels({ data }) { * const { hotels } = data as { hotels: Array<{ name: string; pricePaise: number }> } * return html`` * } */ import type { RenderAdapter, RenderProps } from "@nifrajs/web" import { compose } from "./compose.ts" export { compose, type VanillaComponent } from "./compose.ts" export { type HtmlValue, html, RawHtml, raw, Template } from "./html.ts" const ENCODER = new TextEncoder() /** The zero-framework server render adapter - pass to @nifrajs/web's `renderPage`/`createWebApp`. */ export const vanillaAdapter: RenderAdapter = { // One-pass string render - renderPage's buffered fast path uses this for every non-deferred // page, which for vanilla routes is the norm. renderToString(chain: readonly unknown[], props: RenderProps): string { return compose(chain, props).html }, // Deferred pages need the streaming seam; a synchronous renderer satisfies it with a one-chunk // stream (the shell IS the document - vanilla has no Suspense boundaries of its own; @nifrajs/web's // defer()/ machinery handles progressive data above this seam). renderToStream(chain: readonly unknown[], props: RenderProps): ReadableStream { const body = compose(chain, props).html return new ReadableStream({ start(controller) { controller.enqueue(ENCODER.encode(body)) controller.close() }, }) }, // No client runtime ⇒ no per-document bootstrap markup. hydrationHead(): string { return "" }, }