FullStacked

Node.js compatibility

A FullStacked project imports Node’s built-in modules the way it would in Node. Both spellings work: import fs from "fs" and import fs from "node:fs". Nothing comes from npm. The runtime serves each built-in from its own library, written in TypeScript and embedded in the core (core/internal/bundle/lib). Work that needs the operating system, or that is too heavy for JavaScript, goes over the bridge to the Go core.

The modules fall into three groups:

Module table

Module Status Runs in Page
assert full page Pure modules
buffer full page Pure modules
console the global console page Pure modules
constants full page Pure modules
diagnostics_channel full page Pure modules
events full page Pure modules
perf_hooks performance only page Pure modules
querystring full page Pure modules
stream, stream/promises, stream/web, stream/consumers full page Pure modules
string_decoder full page Pure modules
timers, timers/promises full page Pure modules
url WHATWG + helpers (no legacy parse) page Pure modules
util, util/types full, two documented differences page Pure modules
path partial page + core System modules
fs, fs/promises partial core System modules
os partial core System modules
process partial page + core System modules
net client sockets core Networking
dgram UDP sockets core Networking
dns, dns/promises lookup core Networking
crypto broad page + core Crypto and zlib
zlib deflate, gzip, brotli (no zstd) core (+ crc32 in page) Crypto and zlib
worker_threads Worker, parentPort web workers Workers and terminals
readline, readline/promises full page Workers and terminals
tty isatty() only page Workers and terminals
http, https, http2, tls not available (stubs) none Unavailable
child_process, async_hooks, module, vm, v8, sqlite not available none Unavailable
cluster, inspector, repl, wasi, domain, punycode, trace_events, test not provided none Unavailable

Only the subpaths listed above are mapped. Others, such as assert/strict, path/posix and path/win32, don’t resolve. Import the parent module and use its property instead (assert.strict, path.posix).

“Full” means the module’s public API matches Node and passes the conformance suite in test/shim. Each page notes the differences that are left. They are deliberate choices, and each one is tested.

How a built-in is resolved

The bundler (esbuild, run inside the core) has a plugin that catches every import before normal resolution:

  1. A node: prefix is removed (node:fs → fs), and so is a trailing / (process/ → process, the form some browserify-era packages use).
  2. A fullstacked/ prefix is removed (fullstacked/git → git). See the runtime API.
  3. If the name is in the runtime’s module table, the import resolves to the embedded library file. Otherwise normal node_modules resolution applies.

The table is matched before node_modules. A project therefore can’t install an npm package whose name is a runtime module (config, git, run, bundle, packages, plugin, tunnel, auth, fetch, websocket, test, parentWindow) and import it by its bare name: the runtime’s module wins. Polyfill packages such as buffer, events, util or process are replaced the same way, which is the intended effect. Old code that imports them gets the runtime’s version.

require() of a built-in returns what Node’s require returns. For assert, buffer, constants, diagnostics_channel, events, querystring, stream, string_decoder, util and util/types, that is the module object itself: require("events") is the EventEmitter function and require("assert") is the assert function. Every other built-in gives its namespace object, which has the same shape for those modules.

Imports of *.node native addons are left external, so they fail at runtime. Native addons can’t run in a webview.

Globals

The runtime installs these on globalThis before the project’s code runs:

Global What it is
process The runtime’s process (see System modules), if the environment has none
Buffer buffer.Buffer
setImmediate / clearImmediate A MessageChannel turn where the environment has none (Node’s own is kept under fullstacked --file)
fetch The core’s fetch: it reaches any origin with no CORS restriction (see fetch and WebSocket)
WebSocket The core’s WebSocket client, in pages and workers
DOMParser, XMLSerializer and DOM node classes Installed in workers and under Node, where there is no DOM. The main thread keeps the browser’s.
__dirname "/"
fullstacked Runtime internals (fullstacked.bridge, …). Not a stable API.

The original fetch stays available as globalThis.fullstacked.fetch. A relative fetch("/data.json") still reads the project’s static files (see Project structure).

Running the conformance suite

npm test -- test/shim

Each test/shim/<module>/index.ts runs the same function against the runtime’s module and against node:<module>, then compares the values or errors (name and code, not the message). Known differences are listed in test/shim/known-failures.ts, which is empty today.