Nub runs the whole TypeScript surface, not just the erasable subset Node's built-in type-stripping accepts. Files ending in .ts, .tsx, .mts, and .cts execute directly: Nub transpiles them with its oxc-based transpiler through a module.registerHooks() load hook, then hands the result to Node.

nub index.ts
nub server.tsx

Transpilation happens before Node sees the code, so the transforms on this page work on every Node version Nub supports (18.19+). The one carve-out is source maps; see Source maps below.

Types are stripped, not checked

Nub does not type-check — types are stripped for execution. Keep tsc --noEmit in your editor and CI for type validation. Files inside node_modules are never transpiled either; they load exactly as the package shipped them.

Non-erasable syntax

Syntax like enum, namespace, parameter properties, and import = requires transformation, not just type removal — Node's type-stripping rejects all of it. Nub's transpiler emits the runtime form as part of the normal pipeline.

enum Color { Red, Green, Blue }

namespace Geometry {
  export const PI = 3.14159;
}

class User {
  constructor(public id: string, private name: string) {}
}

import fs = require("node:fs");

You opt into the syntax by writing it; Nub makes it run.

JSX and decorators each have their own page: see JSX for the .jsx / .tsx runtime and tsconfig configuration, and Decorators for the legacy experimentalDecorators form and emitDecoratorMetadata.

Explicit resource management

Explicit Resource Management — the using and await using declarations — runs on Nub's entire Node floor. Older Node (Node 22's V8) cannot parse using natively — it's a hard SyntaxError — so Nub's transpiler lowers it to the helper-based form before Node ever sees it.

class Handle {
  [Symbol.dispose]() { console.log("disposed"); }
}

{
  using h = new Handle();
  console.log("using h");
}
console.log("after scope");
$ nub resource.ts
using h
disposed
after scope

The lowering targets es2022 — the highest target that still rewrites using while leaving everything Node 22 already supports (top-level await, class fields, private methods) untouched. The disposal helper resolves through Nub's vendored runtime; you don't install anything.

Source maps

Nub generates an inline source map for every transpiled file and injects --enable-source-maps, so uncaught errors and console.trace() print frames that point at your original TypeScript source and line numbers — not the generated JS. It's on by default; there's no flag to remember, and breakpoints set in .ts source land correctly under nub --inspect.

$ nub app.ts
/path/to/app.ts:2
	throw new Error("kaboom");
	^

Error: kaboom
    at boom (/path/to/app.ts:2:8)
    at Object.<anonymous> (/path/to/app.ts:4:1)

The stack frame reports app.ts:2:8 — the line in your source, not the transpiled output. To turn it off, pass --no-enable-source-maps.

Source maps are injected on every supported Node version except the 26.2.x patch band, where a Node regression makes a no-message assert(false) rethrow as a TypeError instead of an AssertionError when source maps are on. On 26.2.x Nub withholds the flag, so stack traces there are not remapped. Every other version (18.19 through 26.1, and 26.3+) gets source maps.