Open-Source Wikis

/

Deno

/

Features

/

Node compatibility

denoland/deno

Node compatibility

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

Purpose

Deno aims to run a large fraction of the npm ecosystem unchanged. That means implementing not just the node:* modules but also the small behavioral details that npm packages depend on — error codes, callback shapes, stream backpressure semantics, child-process inheritance rules, etc. The Node compatibility surface is one of the most heavily worked-on areas of the codebase.

This page is a feature-level overview. For implementation details see ext/node.

What "Node-compatible" means here

Three axes:

  1. Module specifier compatibilityimport "fs", require("fs"), import "node:fs" all work and resolve to the same place.
  2. API compatibilitynode:fs.readFile(path, cb) invokes the callback with the same arguments and the same error shapes Node would.
  3. Behavioral compatibility — observable behavior under stress (concurrency, streams backpressure, signal handling, TTY modes) matches Node.

Axis 1 is owned by libs/node_resolver. Axes 2 and 3 are owned by ext/node and its split-out siblings ext/node_crypto, ext/node_sqlite.

Surface area

The polyfills directory ext/node/polyfills/ contains TypeScript files for every standard node:* module, including:

  • Filesystem and IO: fs, fs/promises, path, os, tty, readline
  • Networking: net, tls, http, https, http2, dgram, dns
  • Streams: stream, stream/web, stream/promises
  • Crypto: crypto, plus the dedicated ext/node_crypto/ crate for the heavy lifting
  • Process: process, child_process, cluster, worker_threads
  • Async: async_hooks, events, timers, perf_hooks, inspector
  • Compression: zlib
  • Module: module, vm, assert
  • Misc: util, url, querystring, string_decoder, console, buffer, repl, v8, domain, punycode, wasi
  • The node:sqlite module lives in its own crate ext/node_sqlite/.

Plus an internal/ subdirectory mirroring Node's own internal/* namespace — which Node's polyfill code uses for shared helpers.

How resolution flows

graph LR
    UserCode["require('lodash')"] --> NodeRes["libs/node_resolver"]
    NodeRes --> NM["walk node_modules/<br/>or auto-managed npm cache"]
    NM --> PkgJson["read package.json<br/>(libs/package_json)"]
    PkgJson --> Exports["apply 'exports' / 'main' / conditional exports"]
    Exports --> File["resolved .js / .cjs / .mjs file"]
    File --> Loader["cli/module_loader.rs"]
    Loader --> Run["evaluate"]

    UserCode2["require('fs')"] --> NodeRes2["libs/node_resolver"]
    NodeRes2 --> Builtin{"is built-in?"}
    Builtin -->|yes| Polyfill["ext/node/polyfills/fs.ts"]
    Builtin -->|no| NM

The resolver decides whether a specifier is a built-in node:* module, an npm package, or something else, and routes accordingly.

Test infrastructure

tests/node_compat/

This directory imports Node's own test suite (via a submodule) and runs it under Deno. Each Node test is in one of three states:

  • Enabled — runs and is expected to pass.
  • Disabled — currently fails; tracked in a config file.
  • Ignored — irrelevant to Deno (e.g., V8-specific behavior we don't care about, internals).

The recent commit history shows a steady stream of test: enable parallel/... PRs — each one promotes a previously-disabled Node test to "enabled," confirming Deno now passes it. There's a public dashboard at https://node-test-viewer.deno.dev/results/latest.

tests/unit_node/

Deno-authored unit tests for node:* modules. These cover behavior not exercised by Node's own tests, plus regressions found in user reports.

Recent fix patterns

A scan of recent fix(ext/node): commits reveals the kinds of bugs that get filed:

  • Callback shapecancel pending TLS writes when the socket closes, forward http2 protocol errors from invalid frame callback to session error event
  • Error code parityenforce OpenSSL SECLEVEL key-strength check in createSecureContext, align crypto random* validation with Node
  • Behavior under specific OS conditionsrewrite Windows TTY reading to match libuv, bind to IPv6 wildcard for default Server.listen() to enable dual-stack
  • Edge cases in resolutionalign node:module behavior with Node
  • Performanceenable HTTP parser consume fast path

The pattern is roughly: a user report, a minimal repro, a polyfill change in ext/node/polyfills/<module>.ts (sometimes with a Rust op change), and a unit test or node_compat test entry.

--node mode

deno --node script.js is a thin wrapper that translates Node-style CLI args into Deno equivalents. Implemented by libs/node_shim (the lib.rs is 5,006 lines because Node has accumulated many flags). Used by tooling that wants to drop-in replace Node with Deno.

Permissions and Node

Node has no permission model; everything runs with full OS privileges. When you import an npm package, Deno applies its permission model anyway. This means:

  • fs.readFile('/etc/passwd', cb) from an npm package will fail if --allow-read=/etc wasn't granted.
  • Network calls go through --allow-net.
  • child_process.spawn(...) requires --allow-run.

This is intentional. The runtime grants Node-equivalent capability only when the user opts in.

Entry points for modification

  • Polyfill bug — almost always ext/node/polyfills/<module>.ts. Look at the existing functions for the pattern. Add a unit test in tests/unit_node/ and consider promoting a tests/node_compat/ entry from disabled to enabled.
  • New op needed — add to ext/node/ops/ and call from the polyfill. For crypto, prefer adding to ext/node_crypto/ to keep main ext/node build times reasonable.
  • Resolution buglibs/node_resolver/. These are the trickiest because the algorithm has many edge cases.
  • --node flag missinglibs/node_shim/lib.rs.

Key source files

File Purpose
ext/node/lib.rs Extension wiring
ext/node/polyfills/ The TS implementing every node:* module
ext/node/polyfills/internal/ Node's internal/* namespace
ext/node_crypto/ Crypto split-out (10 Rust files)
ext/node_sqlite/ node:sqlite
libs/node_resolver/ Node resolution algorithm
libs/node_shim/lib.rs Node-style CLI translator (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.

Node compatibility – Deno wiki | Factory