Usage

Import Node.js builtins from nstdlib and use them across different JavaScript environments.

This page covers day-to-day use: importing a builtin, using it with a bundler, and what changes when your code runs in a browser or a worker. To connect a filesystem, DNS resolver, or WebCrypto implementation supplied by your runtime, see Host.

#Importing a builtin

Each node: builtin is its own subpath, matching the name you'd use with Node itself:

import fs from "nstdlib/fs";
import path from "nstdlib/path";
import { EventEmitter } from "nstdlib/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'

Most modules offer both a default export and named exports, the same way Node's own builtins do - import fs from "nstdlib/fs" and import { readFileSync } from "nstdlib/fs" both work and refer to the same function. Use whichever your code already expects; there's no subpath that only supports one form.

A module that isn't published as an entry can't be imported at all, no matter how you write the specifier - see Installation for what that means and the API index for the full list of entries.

Two modules worth calling out because they genuinely work end to end, not just import cleanly - crypto's randomness half runs on the host's real WebCrypto, and dns resolves over DNS-over-HTTPS on the host's fetch:

import crypto from "nstdlib/crypto";
import dnsPromises from "nstdlib/dns/promises";

crypto.randomUUID(); // real entropy, Node's own RFC 4122 v4 serialiser
await dnsPromises.resolveMx("gmail.com"); // a real DNS answer

Most other modules that need direct access to the operating system don't have that backing yet. Check a module's own page in the API index before relying on it.

#Using it with a bundler

nstdlib/* entries are plain ES modules with no bundler-specific tricks, so any modern bundler (Vite, Rolldown, webpack, esbuild, Rollup) can import, code-split and tree-shake them like any other package.

Each entry's subpath is the builtin's own name, which means redirecting node:* specifiers at nstdlib is a plain alias in your bundler's own config - nstdlib doesn't do this for you, but nothing stops you from setting it up yourself:

// vite.config.js
export default {
  resolve: {
    alias: {
      "node:fs": "nstdlib/fs",
      "node:path": "nstdlib/path",
      "node:events": "nstdlib/events",
    },
  },
};

Add only the specifiers your code actually uses. An alias for a module that doesn't work the way your code needs (see the API index) will still resolve and import - it just won't behave like Node's version at the call you rely on.

#In a browser or a worker

The pure-JavaScript parts of the standard library - path, events, buffer, util, url, assert, stream and more - run in a browser tab or a worker the same way they run under Node, because they never needed an operating system to begin with.

Parts that do need one behave differently depending on what the host provides:

  • Randomness and WebCrypto (crypto.randomBytes, randomUUID, crypto.subtle) work natively, because every modern JavaScript engine ships a real globalThis.crypto.

  • DNS (dns.lookup, dns.resolve*) works over DNS-over-HTTPS, because it only needs fetch, which browsers and most workers have natively.

  • Everything that needs a real filesystem, a socket, or OpenSSL has no substitute in a plain browser or worker. Calling it doesn't hang or return undefined - it throws, naming exactly 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`.

    A host can fill that gap by installing the named global itself - that's what nstd does, and it's what the Host page covers.

  • require() needs a runtime that allows compiling code from a string. It's built on new Function(source), so a page under a Content-Security-Policy without unsafe-eval, or a worker running with code generation from strings disabled, gets no require() from this library at all - not a degraded one, none. import() of a CommonJS module needs the same capability, because it uses the same compiler. import() of a registered builtin or a JSON file does not.

  • There's no process or Buffer global by default. Node installs both from C++ before your code runs; a browser or worker has neither. If your code (or a dependency) expects them, see nstdlib/globals in Host.

For the full, per-module picture of what's verified, what's API-only, and what doesn't work at all, see the API index.