# @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 `