NewIntroducing Nub

The all-in-one JavaScript toolkit that augments Node.js instead of trying to replace it

A TypeScript-first toolchain for Node.js. Run TypeScript files, package.json scripts, and local CLIs on the node and package manager you already have. No new runtime, no lock-in.

View repo
$ nub index.ts # TypeScript-first Node.js runtime
$ nub run dev # 24× faster pnpm run
$ nubx prisma generate # 19× faster npx
$ nub install # 2.5× faster pnpm install
$ nub watch src/server.ts # native watch mode
$ nub pm shim # built-in Corepack-style shims
$ nub node install 26 # Node version manager

The toolchain

An all-in-one toolkit for Node.js

One Rust binary to run your files and scripts, install dependencies, and manage Node itself.

nub <file>

A TypeScript-first Node.js

Run .ts, .tsx, and .jsx on stock Node with full support for tsconfig.json, .env loading, and modern syntax and Web APIs.

Replacestsxts-nodetsconfig-pathsdotenv
# run a TypeScript file
$ nub index.ts
# restart on changes
$ nub watch src/server.ts
nub <file>

A TypeScript-first Node.js

Nub adds support for TypeScript, JSX, decorators, .env files, YAML/TOML imports, and modern syntax and APIs on top of stock Node. Flag-for-flag compatible with node. Powered by Rust and oxc.

Architecture

Transpiles in Rust, runs on real Node

Nub transpiles your code in memory with oxc (compiled into a native Node addon) and runs the output on the stock node binary. There’s no Nub runtime, just real Node. Runs on Node.js 18 LTS and newer.

$ nub app.ts
# oxc transpiles in memory, then stock node runs it
running on node v26.4.0

TypeScript-first

Full TypeScript support, not just type stripping

Recent versions of Node support type stripping, which erases annotations but rejects non-erasable syntax. Nub’s load hook transpiles each file through its native addon instead, so enums, parameter properties, and extensionless imports that Node doesn’t allow all just work.

import { Model } from "./base"   // extensionless → ./base.ts

enum Status { Draft, Sent, Paid }

class Invoice extends Model {
  constructor(public status = Status.Draft) {} // parameter property
}

tsconfig

Respects your tsconfig.json

Nub resolves your tsconfig.json (including "extends") and feeds its paths into Node’s own resolver through a module.registerHooks() resolve hook. No more tsconfig-paths or disagreement between Node.js and your editor.

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@db": ["src/db/index.ts"]
    }
  }
}

Environment

Loads .env files automatically

Nub reads .env, .env.local, and .env.[NODE_ENV] and injects them before Node starts. No dotenv required. Automatic var expansion via ${VAR} just like Vite and Next.js.

# .env
APP=acme
DATABASE_URL=postgres://localhost/${APP}_dev

# No dotenv. No cross-env. No import "dotenv/config".
$ nub server.ts

Modern syntax

Decorators, JSX, and using

Nub supports decorators and JSX, transpiling it according to your tsconfig.json settings. Full support for emitDecoratorMetadata and explicit resource management, no build step required.

await using db = await connect()    // disposed at scope end

@sealed                             // legacy decorator
class User {}

const view = <Hello name="world" /> // JSX in .tsx

Loaders

Import JSON, YAML, and TOML

Import .yml, .yaml, .toml, .json5, and .jsonc files directly. A module.registerHooks() load hook routes them through fast Rust parsers in Nub’s native addon, resolving each import to a plain JavaScript object. (Oh, .txt works too)

import config from "./config.yaml"   // parsed object
import flags  from "./feature.jsonc" // comments stripped
import pkg    from "./Cargo.toml"    // parsed object
import prompt from "./prompt.txt"    // string

const { host, port } = config        // destructure fields

Auto-restart

A dependency-aware watch mode

Powered by node --watch, Nub’s watch command watches for changes to your entrypoint or any file transitively imported. It also adds TypeScript/JSX sourcemap support and watches your package.json, tsconfigs, and .env files.

$ nub watch src/server.ts
Listening on http://localhost:3000
↺ src/db.ts changed — restarting
Listening on http://localhost:3000

Node version management

Auto-installs Node, on demand

Nub reads your .node-version, .nvmrc, or engines/devEngines pin and runs your code on exactly that version. If it isn’t on your machine, Nub downloads it from nodejs.org, verifies the checksum, and installs it on the fly — replacing nvm and fnm. You can also manage versions manually.

$ echo 26 > .node-version
$ nub hello.ts
Using Node.js 26.4.0 (resolved from .node-version)
Installed in 9.8s
Hello world!

Performance

Negligible overhead over plain Node

Nub transpiles each file in memory through its native Rust addon, then runs it on the real node binary. Its own startup is a few milliseconds of Rust, dwarfed by Node’s, so a .ts file starts up on par with plain node and about 2.9× faster than tsx, which loads esbuild and its loader hooks on every run.

run a TypeScript file · macOS

node hello.ts44 ms
nub hello.ts44 ms
tsx hello.ts128 ms · 2.9× slower
View bench →

Compatibility

Node-compatible, because it is Node

Your code is transpiled and executed with the stock node binary, so it runs on real Node, not a reimplementation. That’s where the compatibility comes from.

Node 25.8
100%
4,368 / 4,368
Nub
98.8%
4,315 / 4,368
Deno 2.8
77.4%
3,380 / 4,368
Bun 1.3.14
40.5%
1,770 / 4,368

Deno’s Node-compat corpus, scored against stock Node. Nub’s misses come from auto-enabling experimental features and loading native addons.
View benchmark repo

truly drop-in

Flag-for-flag compatible with node

Nub is actually a drop-in replacement for node. Every V8 and Node flag, NODE_OPTIONS, argv, exit codes, and signals behave identically — Nub forwards them straight to the real node it runs. Swap node for nub in any script, Dockerfile, or CI step; nothing else changes.

$ NODE_OPTIONS='--enable-source-maps' nub \ --max-old-space-size=8192 \ --import ./instrument.js \ app.ts --port 3000

No Nub-specific APIs

Zero lock-in

Nub is not a runtime. Your code is run using stock node. Nub simply transpiles your code, polyfills missing global APIs, sets some flags, and makes additive modifications to Node’s module resolution to improve TypeScript support.

  • No Nub global
  • No nub:* module namespace
  • No @nub/* npm scope
  • No "nub" field in package.json
  • No nub-named lockfile

Forward compatibility

Modern APIs and syntax, fully supported

Nub polyfills APIs like Temporal and Worker, adds support for new ECMAScript syntax like using, and unflags all experimental Node.js features.

Web Workers
Auto-polyfilled
Temporal
Polyfilled < 26
URLPattern
Polyfilled < 24
WebSocket
Unflagged < 22
navigator.locks
Auto-polyfilled
localStorage
Auto-unflagged
using / await using
Transpiled
node:sqlite
Unflagged < 23
vm.Module
Auto-unflagged
ShadowRealm
Auto-unflagged
RegExp.escape
Polyfilled < 24
Promise.try
Polyfilled < 24
Float16Array
Polyfilled < 24
nub run

A 24× faster pnpm run

A drop-in for npm run and pnpm run with lifecycle hooks, npm_* env vars, and arg forwarding, without the JS startup these Node-based tools pay on every call.

Performance

Run package.json scripts at the speed of Rust

Whereas scripts run with npm run or pnpm run feel perceptibly laggy — they’re Node.js programs, so each call cold-loads the package manager’s own JavaScript (config, workspace probe, the works) before your script runs — Nub’s runner is a Rust binary with no startup of its own.

script dispatch · warm · 50 runs · macOS

nub run14.7 ms
node --run32.2 ms · 2.2× slower
npm run329.9 ms · 22× slower
pnpm run442.7 ms · 30× slower
View bench →

Drop-in for pnpm run

Flag-for-flag compatible with pnpm run

Nub accepts pnpm run’s flags with the same spelling and semantics, down to the obscure recursive ones. Swap pnpm for nub and your CI scripts run unchanged, only faster.

$ nub run build # plain script run
$ nub run test -- --coverage # pass args through
$ nub -r --if-present lint # skip packages without it
$ nub -r --parallel --no-bail test # all at once, collect all results
$ nub -r --resume-from @org/api --stream build # CI restart, streamed

Workspaces

Monorepo-friendly

Nub implements pnpm’s --filter grammar and -r, reading workspaces from package.json#workspaces or pnpm-workspace.yaml. Your existing filter commands work unchanged.

$ nub -r run build # every package, topo-ordered
$ nub --filter @org/api dev # one package
$ nub --filter ...@org/web build # + its deps
$ nub --filter "[main]" test # changed since main
nubx

A 19× faster npx

The nubx command resolves node_modules/.bin in Rust and execs the binary directly — no Node process in the wrapper. A drop-in for npx and pnpm dlx: it runs a local bin, or fetches an uninstalled one from the registry.

Performance

Makes commands feel instantaneous

When invoking native CLIs like esbuild, npx itself (written in JS) adds a noticeable 200ms of cold-start latency, even when running a CLI command that’s instantaneous. Nub walks node_modules/.bin and execs the binary directly.

esbuild --version · macOS

nubx esbuild --version11 ms
pnpm exec esbuild --version191 ms · 17× slower
npx esbuild --version226 ms · 19× slower
View bench →

Drop-in for pnpm exec

Flag-for-flag compatible with pnpm exec

The nubx and nub exec commands take pnpm exec’s flags, and nub dlx matches pnpm dlx, shell mode included. Swap pnpm for nub and the command you already know runs.

$ nub exec -r tsc --build # across the workspace
$ nub exec --parallel vitest # every package at once
$ nub dlx -p cowsay -c 'cowsay hi | tr a-z A-Z' # dlx shell mode

Resolution

Works with any package manager

Nub resolves a locally-installed CLI from node_modules/.bin regardless of which package manager put it there — so you get Nub's performance without switching package managers.

$ nubx eslint . # member's .bin first
$ nubx prisma generate # then workspace root
$ nubx tsc --noEmit # then ancestors
$ nubx --node some-cli # run under plain Node
nub install

A 2.5× faster pnpm

A pnpm-compatible package manager, built in. It reads the lockfile your project already has — pnpm, npm, or bun — writes the same format back, and is hardened against supply-chain attacks out of the box. Powered by the aube engine.

Meta package manager

Change package managers, keep your lockfile.

Nub autodetects your current manager and updates your existing lockfile in place. No migration needed. Verified roundtrip compatibility for package-lock.json, pnpm-lock.yaml, and bun.lock.

$ nub install # npm package-lock.json → in place
$ nub install # pnpm pnpm-lock.yaml → in place
$ nub install # bun bun.lock → in place

Drop-in for pnpm

Drop-in pnpm compatibility

Nub’s install and add accept pnpm’s flags with the same spelling and semantics, down to advanced features like the workspace catalog. Swap pnpm for nub and your install commands run unchanged.

# exact pin · devDeps · workspace catalog
$ nub add -E -D --save-catalog react
$ nub install --frozen-lockfile --prefer-offline --node-linker hoisted

Install speed

Ultrafast installs

Like pnpm, Nub keeps package files in a global content-addressed store and links them into node_modules. Nub embeds aube, a highly optimized Rust-based resolver and linker.

warm frozen install · create-t3-app · 222 deps · macOS

nub1122 ms
bun1444 ms · 29% slower
pnpm2847 ms · 2.5× slower
npm4163 ms · 3.7× slower
View methodology →

Config compatibility

Mirrors your package manager's config rules

Nub supports all the listed configuration mechanisms, but toggles them on and off based on the conventions of your project’s inferred package manager. There is no Nub-specific configuration file.

config fieldnpmpnpmyarnbunnub
workspaces
overrides
resolutions
catalog:
packageExtensions
allowBuilds
trustedDependencies
.npmrc

Supply-chain safe by default

Hardened against supply-chain attacks

The defenses are on out of the box, no config required. Nub treats dependency build scripts as deny-by-default, queries OSV for malicious-package advisories on every fresh resolve, refuses a version whose publish trust evidence weakened against an earlier release, and holds back releases younger than minimumReleaseAge(24h, matching pnpm) so a freshly-compromised version isn’t pulled in.

$ nub add @ledgerhq/connect-kit
Error: refusing to add malicious package(s):
- @ledgerhq/connect-kit (MAL-2023-8697:
https://osv.dev/vulnerability/MAL-2023-8697)
❌ code=ERR_NUB_MALICIOUS_PACKAGE
$ nub install # a version that lost its provenance
Error: trust downgrade for nanoid@3.3.14 (trustPolicy=
no-downgrade): earlier published version 3.3.7 had
provenance attestation but this version has no trust evidence
❌ code=ERR_NUB_TRUST_DOWNGRADE

Deny-by-default build scripts

Build scripts don’t run until you allow them

Install-time postinstall scripts are where most supply-chain payloads land. Nub skips them by default, names what it skipped, and waits for nub approve-builds. A curated default-trust floor can vouch for a package only after it clears provenance, advisory, and cooling gates.

$ nub install
WARN ignored build scripts for 1 package(s): esbuild@0.21.5.
Run `nub approve-builds` to review and enable them.
code=WARN_NUB_IGNORED_BUILD_SCRIPTS
$ nub approve-builds # review + allow, once

The all-in-one toolkit for Node.js

View repo