Open-Source Wikis

/

Deno

/

Extensions

/

`ext/node`

denoland/deno

ext/node

Active contributors: Yoshiya Hinosawa, Marvin Hagemeister, Bartek Iwańczuk

Purpose

ext/node is Deno's Node.js compatibility layer. It provides every node:* module (node:fs, node:net, node:http, node:crypto, node:child_process, node:os, node:path, node:process, …) by translating their APIs onto Deno's primitives — its ops, the web platform extensions, and the platform abstraction crates.

This is one of the largest subsystems in the repo. The polyfills directory ext/node/polyfills/ holds dozens of TypeScript files that re-implement Node's API surface, often with extensive carve-outs for behavior parity with Node's quirks. Two siblings split the load:

  • ext/node_crypto — Node's crypto layer (10 Rust files). Big enough to be its own crate.
  • ext/node_sqlite — Node's node:sqlite (8 Rust files). Similarly split.

Directory layout

ext/node/
├── Cargo.toml
├── lib.rs                # extension wiring
├── build.rs              # bundles polyfills/ into snapshotted form
├── ops/                  # Rust ops backing node-specific APIs
│   ├── …
├── polyfills/            # the JS/TS implementing node:*
│   ├── _next_tick.ts, _process.ts, _stream.mjs
│   ├── async_hooks.ts, buffer.ts, child_process.ts,
│   │ cluster.ts, console.ts, crypto.ts, dns.ts, events.ts,
│   │ fs.ts, http.ts, http2.ts, https.ts, module.ts, net.ts,
│   │ os.ts, path.ts, perf_hooks.ts, process.ts, querystring.ts,
│   │ readline.ts, repl.ts, stream.ts, string_decoder.ts,
│   │ timers.ts, tls.ts, tty.ts, url.ts, util.ts, v8.ts, vm.ts,
│   │ worker_threads.ts, zlib.ts, …
│   └── internal/         # node's "internal" namespace polyfills
├── benchmarks/           # node-shaped microbenchmarks
└── update_node_stream.ts # pulls upstream node:stream code

ext/node_crypto/          # node:crypto (and its sub-modules)
ext/node_sqlite/          # node:sqlite

Key abstractions

Type Where Role
NodeExtInitServices ext/node/lib.rs Bundle of services the extension needs at init: NpmPackageFolderResolver, InNpmPackageChecker, NpmProcessStateProviderRc, FS handle
NpmPackageFolderResolver libs/node_resolver Maps require('foo') to the right node_modules/foo/
InNpmPackageChecker libs/node_resolver Tells the runtime whether a module is part of an npm package (and thus subject to Node's resolution rules)
NpmProcessStateProviderRc ext/process (re-exported) Provides process.argv, process.env, etc., shared between Deno and Node code
polyfills/internal/ ext/node/polyfills/internal/ Mirrors Node's internal/* namespace; the polyfills require('internal/foo') and get the Deno re-implementation

How it works

graph TD
    UserCode["import 'node:fs'<br/>or require('fs')"] --> Resolver["libs/node_resolver"]
    Resolver --> Polyfill["ext/node/polyfills/fs.ts<br/>(snapshotted)"]
    Polyfill --> Op["op_node_fs_*<br/>ext/node/ops/"]
    Polyfill -->|for things already in deno_fs| FsExt["ext/fs"]
    Polyfill -->|for streams| WebExt["ext/web (streams)"]
    Op --> RustImpl["Rust impl<br/>(typically delegates to deno_fs / std::fs)"]
    UserCode2["import 'node:crypto'"] --> Polyfill2["ext/node_crypto/* + polyfills/crypto.ts"]
    UserCode3["import 'node:sqlite'"] --> Polyfill3["ext/node_sqlite/*"]

Polyfills

Each node:<module> is implemented by one or more files under ext/node/polyfills/<module>.ts. The pattern:

  • The polyfill re-uses what's already there: node:fs.readFile calls Deno's op_fs_* ops; node:net.Socket is built on ext/net; node:stream uses ext/web's streams (with extensive shimming for Node-specific stream behavior).
  • It adds what's missing: callback-based APIs that Deno doesn't natively expose (fs.read(fd, ...), net.Socket events), Node-specific error codes, the internal/* helper modules.
  • It shims the differences: error class names, errno codes, the difference between Node's Buffer and Deno's Uint8Array.

Some polyfills delegate almost entirely to Deno: node:url is mostly a thin wrapper. Others are nearly from-scratch reimplementations: node:http2, node:tls, node:dns all have hundreds to thousands of lines of polyfill JS.

Resolution rules

When Node code does require('lodash'), the resolver in libs/node_resolver is responsible for finding the right node_modules/lodash/ directory and applying Node's CJS/ESM/conditional-export rules. This is a strict implementation of Node's algorithm, not a paraphrase — interop with the npm ecosystem demands it.

The resolver is plumbed into the runtime via NodeExtInitServices so that ops which load Node modules (e.g., module.createRequire) can answer correctly.

Build-time bundling

ext/node/build.rs runs at compile time to bundle the polyfills directory. Without this step, import 'node:fs' would have to fetch and parse polyfill source from disk on every cold start. The bundling output is snapshotted alongside the rest of the runtime.

Recent commit themes

The recent commit log is dominated by fix(ext/node): entries. This is the area of the codebase with the highest churn at the snapshot date, often fixing edge-case parity bugs against Node's documented or observed behavior. Examples:

  • fix(ext/node): port internal/priority_queue and expose it via require
  • fix(ext/node): dns resolveAny with real ANY query
  • fix(ext/node): rewrite Windows TTY reading to match libuv
  • fix(ext/node): bind to IPv6 wildcard for default Server.listen() to enable dual-stack

Integration points

  • Resolver: libs/node_resolver (Node-style resolution) and libs/resolver (the unified entry).
  • CLI: cli/npm.rs and libs/npm for package installation; libs/node_shim translates Node CLI args (node script.js -e ...) into Deno-equivalents for the deno --node mode.
  • Runtime: runtime/worker.rs constructs NodeExtInitServices and passes it into deno_node::deno_node::init_ops_and_esm.
  • Sibling extensions: ext/node_crypto, ext/node_sqlite. Other crypto-related code lives there to keep the main ext/node build time reasonable.

Entry points for modification

  • New node:* module or missing API: add a polyfill in ext/node/polyfills/<module>.ts. Wire any new ops in ext/node/ops/. Look at neighboring polyfills for the pattern.
  • Crypto-related work: ext/node_crypto/ for the Rust ops, ext/node/polyfills/crypto.ts and ext/node/polyfills/internal/crypto/* for the JS surface.
  • node:sqlite: ext/node_sqlite/.
  • Resolution bug: libs/node_resolver (algorithm) plus possibly cli/module_loader.rs for CLI-specific glue.

Testing

  • Unit tests for node:* APIs go in tests/unit_node/.
  • Run as ./x node-test.
  • The big "compat" suite at tests/node_compat/ runs Node's own test suite. Enabling a new test usually means making a polyfill behave more like Node — see the recent test: enable parallel/test-... commits for the pattern.

Key source files

File Purpose
ext/node/lib.rs Extension registration
ext/node/build.rs Bundles polyfills at compile time
ext/node/ops/ Rust ops backing node-specific APIs
ext/node/polyfills/ The TypeScript implementing node:*
ext/node/polyfills/internal/ Mirror of Node's internal/*
ext/node_crypto/ Crypto-specific extension (10 Rust files)
ext/node_sqlite/ node:sqlite (8 Rust files)
libs/node_resolver/ Node-style module resolution algorithm
libs/node_shim/lib.rs Translates node CLI args into Deno args (5,006 lines)
tests/node_compat/ Node's own test suite, run under Deno
tests/unit_node/ Per-module Deno-authored unit tests

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

`ext/node` – Deno wiki | Factory