ssrSource

tcmts.cli.web.ssrSource · #nv74ofsj · term · runs on the server

uses node:path

The server-rendering entry for a component app: streams its HTML. The shell (everything outside <Suspense> boundaries still waiting) goes out first, the rest as it's ready. Server components render through the host (see renderPage).

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import type { AppState } from "#codebase/tcmts/cli/web";
import { exportId } from "#codebase/tcmts/core/hash";
import path from "node:path";

export function ssrSource(cb: Codebase, appName: string, s: AppState, cacheRel: string): string {
  const rec = cb.store.getDef(s.hash)!;
  return [
    `// generated by tcm: server rendering for ${appName} ${rec.hash}`,
    `import { Writable } from "node:stream";`,
    `import { createElement } from "${s.react!.react}";`,
    `import { renderToPipeableStream, resumeToPipeableStream } from "${s.react!.dom}/server";`,
    `import { prerenderToNodeStream } from "${s.react!.dom}/static";`,
    `import { ${exportId(rec.hash)} as App } from "${cacheRel}/${path.basename(cb.modulePath(rec.componentHash))}";`,
    `/** Resolves once the shell is written; \`done\` once all of it is. Rejects if the shell can't render. */`,
    `export function render(write: (html: string) => void, onError: (e: unknown) => void, onHeaders?: (headers: Headers) => void): Promise<{ done: Promise<void> }> {`,
    `  return new Promise((shell, fail) => {`,
    `    let finished!: () => void;`,
    `    const done = new Promise<void>((r) => (finished = r));`,
    `    const sink = new Writable({ write(chunk, _e, next) { write(String(chunk)); next(); }, final(next) { finished(); next(); } });`,
    `    const stream = renderToPipeableStream(createElement(App), {`,
    `      onShellReady() { stream.pipe(sink); shell({ done }); },`,
    `      onShellError: fail,`,
    `      onError,`,
    `      ...(onHeaders ? { onHeaders } : {}),`,
    `    });`,
    `  });`,
    `}`,
    `/** The page's shell, rendered ahead: what's rendered until \`signal\` aborts, and what's left (\`postponed\`, null if nothing). */`,
    `export async function prerenderShell(signal: AbortSignal, onError: (e: unknown) => void): Promise<{ html: string; postponed: unknown }> {`,
    `  const { prelude, postponed } = await prerenderToNodeStream(createElement(App), { signal, onError });`,
    `  let html = "";`,
    `  for await (const chunk of prelude) html += String(chunk);`,
    `  return { html, postponed: postponed ?? null };`,
    `}`,
    `/** What a shell left, rendered for this request: as \`render\` streams, after the shell. */`,
    `export function resume(postponed: unknown, write: (html: string) => void, onError: (e: unknown) => void): Promise<{ done: Promise<void> }> {`,
    `  return new Promise((shell, fail) => {`,
    `    let finished!: () => void;`,
    `    const done = new Promise<void>((r) => (finished = r));`,
    `    const sink = new Writable({ write(chunk, _e, next) { write(String(chunk)); next(); }, final(next) { finished(); next(); } });`,
    `    const stream = resumeToPipeableStream(createElement(App), postponed as never, {`,
    `      onShellReady() { stream.pipe(sink); shell({ done }); },`,
    `      onShellError: fail,`,
    `      onError,`,
    `    });`,
    `  });`,
    `}`,
    "",
  ].join("\n");
}

uses

used by