// Design render (`RenderRouteMessage.design: true`): the document the design // canvas loads into a `srcdoc` iframe. Compared with the same page rendered // normally in dev mode, it must have // - a `` first in the injected head blob, // - the stylesheet inlined even when it is over PYLON_SSR_INLINE_CSS_MAX, // - no modulepreload links, no `__PYLON_DATA__`, no module script, // - no live-reload snippet and no dev HUD. // // ssr-runtime.ts pulls in runtime.ts (the bun runner entrypoint, which runs // main() on import), so — like ssr-raw-get.test.ts — the render runs in a child // process with a kept-open stdin pipe and reports its frames over stderr. import { expect, test } from "bun:test"; import { mkdirSync, mkdtempSync, symlinkSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { designBaseHref } from "./ssr-runtime"; const SSR_RUNTIME = join(import.meta.dir, "ssr-runtime.ts"); const PKG_NODE_MODULES = join(import.meta.dir, "..", "node_modules"); interface Frame { type: string; status?: number; headers?: Record; data?: string; } function makeApp(): string { const dir = mkdtempSync(join(tmpdir(), "pylon-design-render-")); mkdirSync(join(dir, "app"), { recursive: true }); mkdirSync(join(dir, ".pylon", "client-build"), { recursive: true }); // react / react-dom resolve from this package's install. symlinkSync(PKG_NODE_MODULES, join(dir, "node_modules"), "dir"); writeFileSync( join(dir, "app", "layout.tsx"), `export default function Layout({ children }: any) { return t
{children}
; } `, ); writeFileSync( join(dir, "app", "page.tsx"), `export default function Page() { return

Hello

; } `, ); // A stylesheet larger than the default 32KB inline cap. const css = ".hero{padding:1rem}\n" + "/* pad */\n".repeat(5000); writeFileSync(join(dir, ".pylon", "client-build", "app.css"), css); writeFileSync(join(dir, ".pylon", "client-build", "entry.js"), ""); writeFileSync( join(dir, ".pylon", "client-build", "manifest.json"), JSON.stringify({ outdir: ".pylon/client-build", public_prefix: "/_pylon/build/", routes: { "app/page": { file: "entry.js", imports: ["chunks/shared.js"], css: ["app.css"] }, }, }), ); return dir; } async function render(dir: string, design: boolean): Promise<{ frames: Frame[]; html: string }> { const msg = { type: "render_route", call_id: "c1", component: "app/page", layouts: ["app/layout"], route_path: "/", url: "/", params: {}, search_params: {}, headers: { host: "127.0.0.1:4599" }, cookies: {}, auth: { user_id: null, is_admin: false, tenant_id: null, roles: [] }, design, }; const probe = ` import { handleRenderRoute } from ${JSON.stringify(SSR_RUNTIME)}; const frames = []; await handleRenderRoute(${JSON.stringify(msg)}, (m) => frames.push(m)); console.error("FRAMES " + JSON.stringify(frames)); setTimeout(() => process.exit(0), 50); `; const probePath = join(dir, design ? "probe-design.ts" : "probe-plain.ts"); writeFileSync(probePath, probe); const proc = Bun.spawn(["bun", "run", probePath], { cwd: dir, env: { ...process.env, PYLON_DEV_MODE: "1" }, stdin: "pipe", stdout: "pipe", stderr: "pipe", }); const [out, err] = await Promise.all([ new Response(proc.stdout).text(), new Response(proc.stderr).text(), ]); const code = await proc.exited; // runtime.ts prefixes console.error lines with "[error] ". const line = err.split("\n").find((l) => l.includes("FRAMES ")); if (code !== 0 || !line) { throw new Error(`probe failed (exit ${code})\nstdout: ${out}\nstderr: ${err}`); } const frames = JSON.parse(line.slice(line.indexOf("FRAMES ") + "FRAMES ".length)) as Frame[]; const html = frames .filter((f) => f.type === "render_chunk") .map((f) => Buffer.from(f.data!, "base64").toString("utf8")) .join(""); return { frames, html }; } test("designBaseHref uses the request host, else 127.0.0.1:PYLON_PORT", () => { expect(designBaseHref({ host: "localhost:4321" })).toBe(''); expect(designBaseHref({}, { PYLON_PORT: "5000" })).toBe(''); expect(designBaseHref({}, {})).toBe(''); }); test("design render: base href, inlined CSS, no hydration, no dev chrome", async () => { const dir = makeApp(); const { frames, html } = await render(dir, true); expect(frames[0].type).toBe("response_start"); expect(frames[0].status).toBe(200); expect(frames[frames.length - 1].type).toBe("render_done"); // Page DOM is intact. expect(html).toContain('
is the first injected head tag. const headEnd = html.indexOf(""); const base = html.indexOf(''); expect(base).toBeGreaterThan(-1); expect(base).toBeLessThan(headEnd); const style = html.indexOf('