Usage
Import Node.js builtins from nstdlib and use them across different JavaScript environments.
This page covers everyday use: importing a module, using nstdlib with a bundler, and what to expect in a browser or a worker. To plug in a filesystem, a network or other features your runtime provides, see Host Integration.
#Import a builtin
Each Node builtin has its own import path, named after the module:
import path from "nstdlib/node/path";
import { EventEmitter } from "nstdlib/node/events";
const emitter = new EventEmitter();
emitter.on("greet", (name) => console.log(`hello, ${name}`));
emitter.emit("greet", "world");
console.log(path.join("a", "b", "c")); // 'a/b/c'Like Node's own builtins, most modules have both a default export and named exports, and they point to the same functions. Both of these work:
import fs from "nstdlib/node/fs";
import { readFileSync } from "nstdlib/node/fs";Tip
In a browser or other non-Node environment, modules create a default
process if there isn't one. To set its working directory, environment or
output streams, call installGlobals() from nstdlib/globals before
importing any module. See Host Integration.
#What works out of the box
Modules that are pure JavaScript, like path, events, buffer, util,
url, assert and stream, work anywhere, because they never needed an
operating system.
Two more work without any setup, because they use standard web APIs:
import crypto from "nstdlib/node/crypto";
import dnsPromises from "nstdlib/node/dns/promises";
crypto.randomUUID(); // uses the runtime's built-in WebCrypto
await dnsPromises.resolveMx("gmail.com"); // a real DNS answer, over HTTPS- Randomness and WebCrypto (
crypto.randomBytes(),randomUUID(),crypto.subtle) use theglobalThis.cryptoevery modern runtime has. - DNS (
dns.lookup(),dns.resolve*()) uses DNS-over-HTTPS throughfetch. See Host Integration to choose the resolver. - Hashing (
createHash(),createHmac(),crypto.hash()) is computed in JavaScript, so it works everywhere too.
#What needs your runtime's help
Anything that needs a real filesystem, sockets, child processes or OpenSSL
can't work on its own in a browser or a worker. Calling it doesn't hang or
return undefined. It throws an error that names what's missing:
Error: [nstdlib] internalBinding('fs').open() is not implemented: this
runtime has no such binding, and no host implementation is installed on
`globalThis.node_binding_fs_open`.You can often fill the gap yourself. For example, installFileSystem() gives
fs an in-memory filesystem, and installNetwork() lets net and http run
inside the page. The Host Integration page covers all of these, and
Limitations covers what can't be filled.
Check a module's page in the API reference before relying on it.
#require() needs eval
require() compiles CommonJS modules with new Function(). If your runtime
blocks that, for example a page with a Content-Security-Policy that doesn't
allow unsafe-eval, require() isn't available at all. The same goes for
import() of a CommonJS module. Importing a builtin or a JSON file works
either way.
#Run a whole program
nstdlib/embed runs Node programs rather than importing modules one at a
time. You create a world (files, a working directory and an environment),
start a node on it, and run programs the way you would type node in a
shell:
import { createNode, createWorld } from "nstdlib/embed";
const world = await createWorld({
files: { "hello.js": "console.log(require('os').platform())" },
});
const node = await createNode({ world, engine: "js" });
const { code, stdout, stderr } = await node.run({ argv: ["hello.js"] });It works in a page, a worker or Node, and it sets up process, Buffer and
the filesystem for you, so don't import any nstdlib/node/* module before
createWorld() has finished. After that, world.fs and world.files read and
write the same files the program sees.
engine: "js"runs the program in your own JavaScript engine. Only the builtins an ordinary script uses are loaded by default; passbuiltins: "all"(or a list of ids) when a program needshttp2,tls,sqliteor the test runner.engine: "wasm"runs it innstd.wasm, a separate runtime compiled to WebAssembly, which isn't part of this package. Pass it asmodule(a URL, aResponse, the bytes or a compiledWebAssembly.Module). Without one,createNode()throws anEngineUnavailableerror withreason: "missing-module".child_process,worker_threadsandvmneed a host. Under Node,nodeCommand(),nodeWorkers()andnodeVm()are ready-made ones; pass them tocreateWorld()ascommands,workersandrealms.
createWorld() takes a provider as well, for a filesystem of your own. A
function there is called with the realm's nstdlib/node/vfs once it is set
up, which is how you build a provider from its MemoryProvider or
VirtualProvider without importing that module too early.
#Use it with a bundler
nstdlib/node/* modules are plain ES modules, so any modern bundler (Vite,
Rolldown, webpack, esbuild, Rollup) can import, split and tree-shake them like
any other package.
nstdlib doesn't redirect node: imports for you, but because each import path
matches the builtin's name, you can do it with a simple alias in your bundler
config:
// vite.config.js
export default {
resolve: {
alias: {
"node:fs": "nstdlib/node/fs",
"node:path": "nstdlib/node/path",
"node:events": "nstdlib/node/events",
},
},
};Only alias the modules your code actually uses. An alias always resolves, but if the module doesn't support what your code needs in your environment (see the API reference), the call will still fail at run time.
nstdlib/embed needs no configuration: every module it loads is imported by
name, so a bundler finds and splits them like the rest of your code.