previewInScratch

tcmts.studio.previewInScratch · #lldultwq · term · runs on the server

uses node:fs

A scratch file's watch (the statement beginning on line, text being what the file holds) that comes to a React element, built to run in the browser: its module and what it uses, bundled with the React DOM its React goes with (one script, React DOM's createRoot rendering it), for a notebook to run in a frame of its own; kept by the statement's hash (previewBuilds), so it's built again only when what it runs changes. Code that only runs on the server fails there, saying why.

import { exportId } from "#codebase/tcmts/core/hash";
import { materialize, moduleFile } from "#codebase/tcmts/loader";
import { HttpError } from "#codebase/tcmts/server";
import { analyzedScratch, editableCodebase, previewBuilds, scratchPath } from "#codebase/tcmts/studio";
import fs from "node:fs";
import path from "node:path";

export async function previewInScratch(file: string, text: string, line: number): Promise<string> {
  const cb = editableCodebase();
  const abs = scratchPath(cb, file, text);
  const a = analyzedScratch(cb, abs, text);
  if (a.diagnostics.length || a.errors.length)
    throw new HttpError(409, `${file} has problems: fix them first.`);
  const w = a.watches.find((x) => x.line === line);
  if (!w) throw new HttpError(404, `There's no statement on line ${line} to run.`);
  const src = cb.source([...a.defs, ...a.watches]);
  materialize([w.hash], src, cb.lay);
  const dir = cb.lay.cacheDir;
  const mod = moduleFile(w.componentHash, src);
  // (the React DOM of the module's own React: its JSX names it)
  const react = /@jsxImportSource (npm__react__[\w.-]+)/.exec(
    fs.readFileSync(path.join(dir, mod), "utf8"),
  )?.[1];
  const doms = fs
    .readdirSync(path.join(dir, "node_modules"))
    .filter((d) => d.startsWith("npm__react-dom__"));
  const dom =
    (react && doms.find((d) => d === react.replace("npm__react__", "npm__react-dom__"))) ??
    doms.sort().at(-1);
  if (!dom) throw new HttpError(409, "The codebase runs no React DOM to render it with.");
  // (built before: the same code, all it runs the same)
  const key = `${w.hash} ${dom}`;
  const kept = previewBuilds.get(key);
  if (kept) return kept;
  const entry = path.join(dir, `_preview_${w.hash}.tsx`);
  fs.writeFileSync(
    entry,
    `import { createRoot } from "${dom}/client";\nimport * as m from "./${mod}";\ncreateRoot(document.getElementById("root")!).render((m as Record<string, any>)[${JSON.stringify(exportId(w.hash))}]);\n`,
  );
  try {
    const { build } = await import("vite");
    const out = (await build({
      configFile: false,
      root: dir,
      logLevel: "silent",
      mode: "production",
      define: { "process.env.NODE_ENV": JSON.stringify("production") },
      esbuild: { jsx: "automatic", jsxDev: false },
      oxc: { jsx: { runtime: "automatic", development: false } },
      build: {
        write: false,
        modulePreload: false,
        minify: true,
        target: "es2022",
        rollupOptions: { input: entry, output: { inlineDynamicImports: true, format: "es" } },
      },
    } as never)) as
      | { output: { type: string; code?: string }[] }
      | { output: { type: string; code?: string }[] }[];
    const chunk = (Array.isArray(out) ? out[0]! : out).output.find((o) => o.type === "chunk");
    if (!chunk?.code) throw new Error("nothing was built");
    previewBuilds.set(key, chunk.code);
    for (const k of previewBuilds.keys()) {
      if (previewBuilds.size <= 40) break;
      previewBuilds.delete(k);
    }
    return chunk.code;
  } catch (e) {
    throw new HttpError(
      400,
      `It couldn't be built to run in the browser: ${(e as Error).message.split("\n")[0]}`,
    );
  } finally {
    fs.rmSync(entry, { force: true });
  }
}

uses

used by