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:
buffer, events, stream, util, assert and the rest. They are written from Node 22/24’s behavior and tested against node:<module> itself.fs, path, net, dgram, dns, os, process, crypto, zlib.http, child_process, vm and the others listed on Unavailable modules.| 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.
The bundler (esbuild, run inside the core) has a plugin that catches every import before normal resolution:
node: prefix is removed (node:fs → fs), and so is a trailing / (process/ → process, the form some browserify-era packages use).fullstacked/ prefix is removed (fullstacked/git → git). See the runtime API.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.
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).
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.