/** * Bundle the guest shell into a string the host can evaluate. * * The shell runs inside QuickJS, which has no module loader and no filesystem, * so it has to arrive as source text. Emitting it as a `.ts` module rather than * a `.js` asset is what lets every consumer — Vite for the browser build, plain * Node for the benchmarks — get at it the same way, with no loader plugin and * no `?raw` import that only one bundler understands. * * The output is committed, like the wasm packages are, so a checkout builds * without this step having run. */ import { build } from "esbuild"; import { mkdirSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; const here = dirname(fileURLToPath(import.meta.url)); const outDir = join(here, "src", "generated"); const result = await build({ entryPoints: [join(here, "src", "guest", "index.ts")], bundle: true, write: false, // A script, not a module: the host evaluates it with `evalCode`, and it // announces itself by assigning `globalThis.__yaak_guest`. format: "iife", // Nothing here may reach for a Node built-in, and "browser" is the closest // description of a target with globals and no filesystem. QuickJS itself has // fewer globals than any browser, which is what `guest/globals.ts` is for. platform: "browser", // QuickJS is ES2023-complete, so nothing needs downleveling. Keeping the // source as written also keeps stack traces from the guest readable. target: "es2022", minify: false, legalComments: "none", }); const source = result.outputFiles[0].text; mkdirSync(outDir, { recursive: true }); writeFileSync( join(outDir, "guest.ts"), [ "// Generated by build-guest.mjs. Do not edit.", "//", "// The runtime shell, as source text, for evaluation inside QuickJS.", "// Regenerate with `npm run build --workspace @yaakapp-internal/plugin-sandbox`.", "", `export const GUEST_SOURCE = ${JSON.stringify(source)};`, "", ].join("\n"), ); console.log(`Bundled guest shell: ${(source.length / 1024).toFixed(1)} KB`);