"Current Node can run TypeScript that uses erasable syntax with no opt-in flag. It removes types and executes the JavaScript underneath; it does not become the TypeScript compiler."
What Built-In Type Stripping Does
Node 22.6 introduced type stripping, Node 22.18 enabled it by default, and Node 24.12/25.2 marked it stable. Node 26 removed the old experimental transform flag. On current Node, run an erasable .ts file directly:
// hello.ts
type Greeting = { msg: string; from: string };
function greet(g: Greeting): string {
return `${g.from} says: ${g.msg}`;
}
console.log(greet({ msg: 'hi', from: 'Pippa' }));
node hello.ts
# Pippa says: hi
The runtime parses the file, removes type annotations, runs the remaining JavaScript. No type checking happens. No transpilation of unsupported features (decorators, namespaces, enums). Just type erasure.
What Works, What Doesn't
Works:
- Type annotations:
const x: number = 5 - Interfaces:
interface Foo { x: number } - Type aliases:
type Foo = { x: number } - Generics:
function id<T>(x: T): T { return x; } satisfies,asassertions, and explicitimport type
Doesn't Work with erasure alone:
- Enums (
enum X { a, b }) — these compile to runtime code, not just types. - Namespaces with values, parameter properties, and import aliases.
- Decorators (TC39 decorators in proposal stages have different semantics).
- JSX — needs a separate transformer.
For most application code that avoids enums and decorators, strip-types covers it. For library code with enum-heavy APIs (older Angular, NestJS) you still need a real TS compiler.
The TS-As-Linter Strategy
tsc --noEmit for type checking:- Run erasable TypeScript with plain
node(Node executes; types are decoration). - Run
tsc --noEmitin CI and pre-commit (TypeScript verifies types, emits nothing).
ts-node, tsx, esbuild-register, or another transpile-on-import hook. TypeScript handles static checking while Node runs the code.For a suitable Node + TypeScript project, this is a clean development path: no transpile-on-import process or build output, while
tsc --noEmit keeps the type-safety gate.The .ts / .mts / .cts Convention
Same as .js / .mjs / .cjs:
.mts— always ESM.cts— always CJS.ts— follows the nearest package.jsontypefield
For new projects, set "type": "module" in package.json and use plain .ts everywhere. The runtime treats it as ESM, the type checker is happy, and you don't need to think about extensions.
Combining Flags — The Full Modern Stack
The complete "modern Node dev" command:
node \
--env-file=.env \
--watch \
server.ts
Depending on the project, that one command can cover common roles previously handled by ts-node, dotenv, nodemon, esbuild-register, and source-map-support. Keep any tool whose additional behavior the project actually uses; the point is to re-check old dependencies against the runtime you have now.
Pippa's Confession
node, and their startup got simpler because there was no transform step. The lesson, again: check whether your tool justification still applies.