Skip to main content

5 posts tagged with "Node.js"

Compatibility with the real Node.js API surface.

View All Tags

Node can require() an ES module now, and it refuses two things. We could not refuse either

· 15 min read

Recent Node versions will let you require() an ES module. It is a genuinely hard thing to have shipped, and it comes with two documented refusals: ERR_REQUIRE_ASYNC_MODULE if anything in the required graph uses top-level await, and ERR_REQUIRE_CYCLE_MODULE if the graph has a cycle that crosses the CommonJS boundary.

Node can refuse, because import() is always sitting there as an escape hatch. Tell the user to await it and the problem is theirs.

In a browser worker there is no escape hatch. require() is synchronous all the way down because the filesystem under it is, and a project's entry point is required by a loader that cannot return a promise to anyone. Every import becomes CommonJS at load time or the program does not start. Refusing was not on the menu.

A step debugger with no inspector to talk to, and the second SharedArrayBuffer that makes it pause

· 12 min read

A breakpoint is not a feature you write. It is a favour the engine does you. When you set one in VS Code, nothing in your program changes: V8's inspector holds the isolate, walks the real call stack, and hands back scopes it already had. Every step debugger you have used is a thin client in front of that.

A Web Worker has no inspector. There is no --inspect port to open, no inspector binding to require, no way to ask the engine to stop. So the first question here was not how to build a debug UI. It was where a pause could possibly come from.

Next.js 16 renders React Server Components in a browser tab, and the AsyncLocalStorage trap

· 8 min read

For most of this project's life, our notes said Next.js was out of reach. The reasoning looked solid: Next compiles with SWC, SWC is native Rust, there is no native code in a browser tab, therefore no Next.js. A hard wall, filed away.

That verdict was wrong, and it was wrong in the most ordinary way: we had decided something was impossible and then stopped rechecking it.

next dev --webpack now boots inside a browser tab, compiles an App Router page, renders React Server Components, and answers GET / → 200 with real HTML. No server. The kernel, the filesystem, the process model, the dev server and the React render all live in one tab.

Getting there was mostly unremarkable engineering, plus one problem that has no correct solution.

Real npm, yarn and pnpm in the browser, and how each one broke the runtime

· 8 min read

Once you are running Node's real lib/ in a tab, the interesting question stops being "can we reimplement npm?" and becomes "why would we?". The package managers are just Node programs. If the runtime underneath them is honest, the real CLIs should run unmodified.

They do. npm, yarn, pnpm and corepack in Vivari are the actual published releases, vendored and executed as-is. But "should run" and "runs" are separated by every Node API each tool happens to touch, and the four of them touch almost disjoint sets. Each one, in turn, walked into a different corner of the runtime and found the wall.

Running Node's real lib/ in a browser tab

· 6 min read

There are two ways to give a browser a Node-compatible runtime, and for a long time we were confidently building the wrong one.

Path A is the obvious one: hand-write the core modules. Implement fs on top of your virtual filesystem, implement path as string manipulation, implement events as a small emitter, and keep going. It feels productive immediately. path takes an afternoon. events takes a morning. fs takes a week and mostly works.

Then you reach stream, and progress stops.