
# 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](/library/host-integration).

## Import a builtin

Each Node builtin has its own import path, named after the module:

```js
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:

```js
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](/library/host-integration#process-and-buffer).

## 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:

```js
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 the `globalThis.crypto` every modern runtime has.
- **DNS** (`dns.lookup()`, `dns.resolve*()`) uses DNS-over-HTTPS through
  `fetch`. See [Host Integration](/library/host-integration#dns) 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](/library/host-integration) page covers all of these, and
[Limitations](/library/limitations) covers what can't be filled.

Check a module's page in the [API reference](/api) 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:

```js
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; pass
  `builtins: "all"` (or a list of ids) when a program needs `http2`, `tls`,
  `sqlite` or the test runner.
- **`engine: "wasm"`** runs it in `nstd.wasm`, a separate runtime compiled to
  WebAssembly, which isn't part of this package. Pass it as `module` (a URL,
  a `Response`, the bytes or a compiled `WebAssembly.Module`). Without one,
  `createNode()` throws an `EngineUnavailable` error with `reason:
"missing-module"`.
- **`child_process`, `worker_threads` and `vm`** need a host. Under Node,
  `nodeCommand()`, `nodeWorkers()` and `nodeVm()` are ready-made ones; pass
  them to `createWorld()` as `commands`, `workers` and `realms`.

`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:

```js
// 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](/api)), 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.
