# @nibling/kernel The `nib` prefix on its error codes, worker names, and internals comes from node-in-browser, the project's original inspiration. A browser-run Node.js-compatible runtime. JavaScript runs in the browser's own engine; the parts of Node the browser can't express fall through to a small WASM residue. **Browser-first is a precedence rule:** for every Node API, use a browser-native primitive when it can meet Node's behavior (OPFS, WebCrypto, TextEncoder/Decoder, Web Streams, Workers); compile to WASM only what's left. Not affiliated with and not derived from StackBlitz WebContainers — the architecture is our own (see `DESIGN.md`). ## Use it with Vite ```ts // vite.config.ts import nib from "@nibling/kernel/vite"; export default defineConfig({ plugins: [nib()] }); ``` ```ts import { getKernel } from "@nibling/kernel"; import app from "./examples/express?nib"; // a dir with package.json + installed node_modules const runtime = await getKernel(); // one per page; survives HMR const { root } = await runtime.mount(app); runtime.exec(`${root}/${app.entry}`, { cwd: root }); iframe.src = await runtime.waitForPort(3000); ``` `nib()` sets COOP/COEP on your pages in `vite dev` and `vite preview`, serves the runtime at `__nib//` and its Service Worker at `nib-sw.js`, and points the `@nibling/kernel` import at them, so `getKernel()` needs no options. A `?nib` import is a snapshot of that directory, described by its metadata (`{ src, root, entry, kind, files, bytes }`), never its contents. In dev, edits under it reach the running guest without a page reload. `vite build` emits the same layout plus a `_headers` for Netlify and Cloudflare; on another host, `nib({ host: "none" })` prints the headers to set. `pages: ["/playground/**"]` limits COOP/COEP to the pages that boot the runtime (COOP breaks OAuth and payment popups). For the `?nib` types, declare the module: ```ts declare module "*?nib" { const app: import("@nibling/kernel").NibApp; export default app; } ``` ## Architecture in one paragraph One **kernel worker** owns the filesystem and process table (the only writer); **runtime workers** run user JS and reach the kernel through a typed syscall table over a sync mailbox (`SharedArrayBuffer` + `Atomics.wait`), which is what makes `require()` and `readFileSync()` honestly synchronous without blocking the page. Node's event loop is emulated one callback per browser task with native microtask checkpoints. The WASM residue is derived from the browser-first precedence table, not designed top-down. ## Status: step 1 ✅ · resolver v2 ✅ · ESM (step 6, early) ✅ · TypeScript stripping ✅ · node:http ✅ · Hono from npm ✅ · event loop + process (step 3, partial) ✅ · Express 5 from npm ✅ · static files ✅ · slopbox shell ✅ · zlib + Buffer on browser codecs ✅ · stdin, signals, ptys + readline ✅ · slopbox terminal pane ✅ · WebSockets to guest servers ✅ · worker_threads + esbuild-wasm ✅ · Vite dev server ✅ · npm install from a lockfile ✅ · npm install from package.json ✅ · guest `npm install`/`ci`/`uninstall` ✅ · guest `npm run`/`exec` and npx ✅ · slopbox bash Shell tab ✅ `DESIGN.md §Build order` tracks the milestones. Done so far: - [x] kernel worker + runtime worker, in-memory FS, typed syscall table, sync mailbox - [x] CJS `require` of local files, `node:fs` via the builtin registry - [x] demo page boots a runtime, runs a main that requires a local module, exits 0 - [x] missing module → `Cannot find module`, exit 1 - [x] no cross-origin isolation → `IsolationError` (no silent degraded mode) - [x] resolver v2: `node_modules` walk, `package.json` `exports` (conditions + wildcard patterns), `main` fallback, self-reference, nearest-`type` format detection; with no `type`, a `.js`/`.ts` with module syntax (import/export, `import.meta`, top-level await) loads as ESM with Node's `MODULE_TYPELESS_PACKAGE_JSON` warning, once per package.json and never under node_modules - [x] `writeFileSync` round-trip through the mailbox - [x] **SW routing probe: PASS in Chromium for direct AND nested workers** - [x] **Guest ESM via the browser's native linker** over the SW virtual origin: specifier rewriting in the kernel (es-module-lexer), builtin + CJS facades sharing `require.cache`, `import.meta.url` = `file:///app/...`, live-binding cycles for free. Dynamic `import()` from ESM and CJS (computed specifiers included) goes through the same resolver and keeps the process alive until it settles; a missing target rejects with `ERR_MODULE_NOT_FOUND`. - [x] **TypeScript via type stripping** (Node's strip-only contract, `@swc/wasm-typescript` — the engine Node's amaro wraps): `.ts` follows nearest `type` (and is syntax-detected on its stripped source when there is none), `.mts`/`.mts` → ESM, `.cts` → CJS; `import type` erased *before* resolution; no extension rewriting; `.tsx` and enums refused with Node's own error codes; node_modules `.ts` refused (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`); engine failure is contained (plain JS keeps running). - [x] **Guest `node:http` servers over the SW virtual origin**: `createServer().listen(port)` in CJS or ESM, `runtime.waitForPort(port)` → `/~/@/http//`, an `