Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155import { build } from "esbuild";import { copyFileSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";import { execFileSync } from "node:child_process";import { createRequire } from "node:module";import { dirname, join } from "node:path";
// Step 1 bundles + the step-6 Service Worker. The kernel and runtime run in workers;// the host runs on the page; the SW serves the ESM virtual origin (/~/ URLs).//// dist/runtime/ is what the package ships, as one unit: its files find each other by// import.meta.url, so they must stay side by side. dist/node/ holds the Node-side modules// the package also ships (./snapshot, ./server, ./vite). dist/demo/ holds the harness// bundle, which is never packed. (slopbox's own bundles live in examples/slopbox's build.)//// @swc/wasm-typescript embeds its 2.8MB wasm as base64 in its CJS entry. The kernel// build cuts that out: the binary is written to dist/runtime/swc.wasm, which strip.ts streams// and compiles (no 3.7MB string to parse, no decode copy, a third fewer bytes on the// wire and better compression), and the entry takes the compiled module from// src/kernel/swc-module.ts. The loader's `util` import aliases to a browser shim.//// The WebSocket shim is a classic script (it must run before a guest page's own// scripts, module or not) that the SW serves as /~/ws-shim.js. It is bundled into// dist/runtime/sw.js as a string rather than shipped as a file: the SW is its only server, so// guest documents load it without a network fetch and without knowing where it is served from.const RUNTIME = "dist/runtime";const NODE = "dist/node";const DEMO = "dist/demo";const TYPES = "dist/types";const SWC_ENTRY = createRequire(import.meta.url).resolve("@swc/wasm-typescript");const SWC_EMBED = /const \{ Buffer \} = require\('node:buffer'\);\s*const bytes = Buffer\.from\('([A-Za-z0-9+/=]+)', 'base64'\);\s*const wasmModule = new WebAssembly\.Module\(bytes\);/;const swcWasm = { name: "swc-wasm", setup(b) { b.onLoad({ filter: /[\\/]@swc[\\/]wasm-typescript[\\/]wasm\.js$/ }, (args) => { const src = readFileSync(args.path, "utf8"); const m = SWC_EMBED.exec(src); if (!m) throw new Error(`${SWC_ENTRY}: embedded wasm not found; the package's loader changed shape`); mkdirSync(RUNTIME, { recursive: true }); writeFileSync(`${RUNTIME}/swc.wasm`, Buffer.from(m[1], "base64")); const take = `const wasmModule = require(${JSON.stringify(new URL("./src/kernel/swc-module.ts", import.meta.url).pathname)}).takeSwcModule();`; return { contents: src.slice(0, m.index) + take + src.slice(m.index + m[0].length), loader: "js" }; }); },};
// Every bundle is minified: each process and thread worker parses runtime.js, and the// page parses host.js before boot. Comments go with minification, so the bundle that// carries code derived from Node.js states its license in a banner.const common = { bundle: true, target: "es2022", minify: true };// The compat target; the per-file "Derived from Node.js vX" headers record what was ported.const PINNED_NODE = readFileSync(new URL("../../.node-version", import.meta.url), "utf8").trim();const NODE_NOTICE = `/*! Portions derived from Node.js v${PINNED_NODE}. MIT License; Copyright Node.js contributors, and Joyent, Inc. and other Node contributors. */`;// process.versions.nib, inlined as __NIB_VERSION__.const NIB_VERSION = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8")).version;
// A clean slate, so a file the build no longer emits can't linger and get packed.rmSync(RUNTIME, { recursive: true, force: true });rmSync(NODE, { recursive: true, force: true });rmSync(DEMO, { recursive: true, force: true });rmSync(TYPES, { recursive: true, force: true });
const shim = await build({ ...common, entryPoints: ["src/shim/ws-shim.ts"], format: "iife", write: false });// The guest npm command is guest code the kernel seeds into the VFS at boot, so it rides// dist/runtime/kernel.js as a string, as the WebSocket shim rides dist/runtime/sw.js. Not// minified: a guest reading /usr/local/lib/node_modules/npm/bin/npm-cli.js sees readable// code.const npmCli = await build({ bundle: true, target: "es2022", entryPoints: ["src/npm-cli/npm-cli.ts"], format: "cjs", platform: "node", banner: { js: "#!/usr/bin/env node" }, write: false,});
await Promise.all([ build({ entryPoints: ["src/host/index.ts"], ...common, format: "esm", outfile: `${RUNTIME}/host.js` }), build({ ...common, entryPoints: ["src/kernel/main.ts"], format: "esm", outfile: `${RUNTIME}/kernel.js`, alias: { util: "./src/kernel/swc-shims.ts" }, define: { __NPM_CLI__: JSON.stringify(npmCli.outputFiles[0].text) }, plugins: [swcWasm], }), // keepNames: guests observe builtin class and function names (util.inspect prints // `<Buffer ..>` and `extends EventEmitter`), so bundling must not rename them. build({ ...common, entryPoints: ["src/runtime/main.ts"], format: "esm", outfile: `${RUNTIME}/runtime.js`, keepNames: true, banner: { js: NODE_NOTICE }, define: { __NIB_VERSION__: JSON.stringify(NIB_VERSION) }, }), build({ ...common, entryPoints: ["src/sw/sw.ts"], // A classic script a site's own worker can importScripts(): an IIFE keeps the // bundle's top-level names out of that worker's global scope. format: "iife", outfile: `${RUNTIME}/sw.js`, define: { __WS_SHIM__: JSON.stringify(shim.outputFiles[0].text) }, }), // Node-side modules, unminified: they run in the consumer's build or server, where // readable stack traces beat bytes. ...["snapshot", "server"].map((name) => build({ entryPoints: [`src/node/${name}.ts`], bundle: true, platform: "node", format: "esm", target: "node20", outfile: `${NODE}/${name}.js` })), // The Vite plugin. It takes only types from vite (the consumer's copy, a peer), and it // locates dist/runtime from its own import.meta.url, two levels up like its source. build({ entryPoints: ["src/vite/index.ts"], bundle: true, platform: "node", format: "esm", target: "node20", outfile: `${NODE}/vite.js`, external: ["vite"] }), // Editor library for the harness demo (microlighter). It lazy-loads grammars // via a wildcard dynamic import — esbuild expands it at bundle time, so no // runtime fetches and dist/demo stays flat. build({ ...common, entryPoints: ["demo/vendor.ts"], format: "esm", outfile: `${DEMO}/vendor.js` }),]);mkdirSync(DEMO, { recursive: true });// Declarations for the public entries (src/host/index.ts, src/node/{snapshot,server}.ts,// src/vite/index.ts, whose types import vite's)// and the modules they reference. tsc's program also covers every module an entry// imports only for its own internal implementation (shared/protocol.ts and what IT// pulls in), so it emits a .d.ts for those too, even though nothing in them is part of// the public API. Prune the output down to what the entries' import graphs reach.execFileSync(process.execPath, [createRequire(import.meta.url).resolve("typescript/bin/tsc"), "-p", "tsconfig.build.json"], { stdio: "inherit" });pruneUnreachableTypes([`${TYPES}/host/index.d.ts`, `${TYPES}/node/snapshot.d.ts`, `${TYPES}/node/server.d.ts`, `${TYPES}/vite/index.d.ts`]);console.log(`built ${RUNTIME}/{host,kernel,runtime,sw}.js + swc.wasm, ${NODE}/{snapshot,server,vite}.js, ${TYPES}/, ${DEMO}/vendor.js`);
function pruneUnreachableTypes(entries) { const keep = new Set(); const queue = [...entries]; while (queue.length) { const file = queue.pop(); if (keep.has(file)) continue; keep.add(file); const src = readFileSync(file, "utf8"); for (const m of src.matchAll(/\bfrom\s*["']([^"']+)["']/g)) { if (!m[1].startsWith(".")) continue; // a bare specifier never resolves inside dist/types queue.push(join(dirname(file), m[1].replace(/\.js$/, ".d.ts"))); } } const allDts = []; const walk = (dir) => { for (const name of readdirSync(dir)) { const p = join(dir, name); if (statSync(p).isDirectory()) walk(p); else if (p.endsWith(".d.ts")) allDts.push(p); } }; walk(TYPES); for (const file of allDts) if (!keep.has(file)) unlinkSync(file);}