November 11, 2025·6 min read·engineeringtypescriptnodejsruntimemodules

Node 25.2 stabilizes the restricted type-stripping execution path

Node.js 25.2 marks type stripping stable while retaining a deliberately limited syntax contract. Direct execution still performs no checking, ignores tsconfig transformations, and leaves package distribution and module behavior under separate rules.

A feature can become stable without expanding to cover every use of the language named in its documentation. Node's TypeScript execution path is a useful example. Its contract is deliberately narrow, removing erasable syntax while executing the remaining JavaScript under Node's module rules.

Node.js 25.2.0, released November 11, marks type stripping stable. The release changes its maturity designation after earlier milestones enabled it by default and removed its experimental warning. It does not turn Node into a replacement for every TypeScript compiler operation.

The 25.2 documentation keeps the distinction between lightweight built-in support and third-party tooling for fuller TypeScript support. Reading that distinction is necessary before deciding what a successful direct execution establishes about a program.

Default, warning-free, and stable are different milestones

The documented history identifies default enablement in Node 23.6 and in the later 22.18 release. It separately records removal of the experimental warning in 24.3 and 22.18. Stable status arrives with 25.2.

Those dates describe different parts of a rollout. A default determines what happens without an opt-in flag. A warning communicates status during execution. The stability designation describes the project's maturity commitment to the supported capability.

The January 23.6 announcement explicitly called the default-enabled feature experimental. A project could therefore encounter the convenience before the supported path acquired today's designation.

This history prevents a misleading account of the November change. The command to run a supported TypeScript file is not new. What changes is the status of the execution mechanism that command already selects.

I remain ambivalent about TypeScript, but its ecosystem is a strong practical reason to use it. A stable direct-execution path is useful when it reduces machinery around code that fits its constraints. The scope of those constraints still belongs in the project's architecture.

Erasure preserves the executable program

Node replaces TypeScript-specific syntax with whitespace and performs no type checking. Keeping the executable source in its original positions lets stack traces use the original locations without generating source maps for the stripping-only path.

An annotation can therefore describe a value to a checker without changing how Node computes it. An interface similarly disappears because it has no required runtime representation. The mechanism is intentionally local to syntax.

That does not mean TypeScript declarations enforce their own promises when the script runs. A runtime value can violate an annotation. Direct execution still follows ordinary JavaScript behavior after removal of the type syntax.

This is particularly important for scripts that never exercise every branch. Successful execution says that the observed path ran. The separate checker can examine relationships across code that was not reached during that run.

The documentation recommends TypeScript 5.8 or newer and settings including erasableSyntaxOnly and verbatimModuleSyntax. Those options help a checker reject assumptions that the runtime does not implement.

Runtime-generating syntax remains a separate category

Enums, parameter properties, and runtime namespaces need JavaScript generated on their behalf. Default stripping rejects them. The separate --experimental-transform-types option handles non-erasable syntax through a different mechanism.

The experimental name on that option matters. Stable type stripping does not confer the same maturity status on every transformation path. A project selecting transformation has made an additional choice beyond using the stable eraser.

The documentation also clarifies that namespaces containing only types are supported. The relevant distinction is runtime code, not the presence of the namespace keyword alone.

namespace Data {
  export type Identifier = string
}

const identifier: Data.Identifier = "item"
console.log(identifier)

This namespace only groups static declarations. Removing it leaves the executable variable initialization and logging statement. A namespace exporting a value requires runtime behavior and does not fit the same rule.

Decorators remain another limit. Node does not transform them or supply a substitute implementation through type stripping. Source using them needs an appropriate execution or compilation arrangement.

The checker can state the runtime subset

TypeScript 5.8 introduced erasableSyntaxOnly because runtime rejection is a late way to discover unsupported source syntax. The option lets the compiler diagnose most TypeScript-specific constructs with runtime semantics.

That is a useful division of responsibility. The compiler can check the program's types and syntax constraints. Node can execute the accepted subset without reproducing the compiler's complete analysis or configuration.

A checking-only project can use noEmit when it intends to execute TypeScript source directly. A package that distributes JavaScript instead still needs an emission path. These are different output contracts even if both projects share many source conventions.

Module settings matter too. Verbatim syntax preserves the distinction between type imports and runtime imports. An unmarked import can remain at runtime and attempt to load a binding that only exists in the type system.

The combination is more precise than assuming that a file accepted by any TypeScript configuration is executable by Node. A compiler configured to synthesize behavior may accept source that an eraser deliberately refuses.

Module identity stays with the loader

Node does not convert between CommonJS and ES modules when stripping types. A .mts file selects ES modules and a .cts file selects CommonJS. A .ts file follows the corresponding JavaScript module-detection rules.

Relative imports require file extensions, including TypeScript extensions for directly executed TypeScript files. The runtime resolves the specifier written in the source. It does not silently reinterpret the import according to an unrelated emitted-file layout.

A type import must be explicit when its declaration should disappear:

import type { Options } from "./options.ts"
import { start } from "./start.ts"

This says that Options is static information and start is a runtime dependency. It allows the eraser to make that decision without loading the rest of the type graph.

The documentation also refuses .tsx files for this mechanism. A source program using JSX therefore has another transformation requirement beyond type erasure. Stable status does not change that requirement.

tsconfig does not configure direct execution

Node ignores tsconfig.json for built-in stripping. Path aliases and downlevel transformations described there do not become runtime capabilities. A project needs either source that matches Node's resolver or tooling that performs the required transformation.

The documentation points to Node subpath imports as a nearby mechanism for aliases, with names beginning in a hash character. That is a package-loader facility, with its own contract. It is different from asking Node to implement arbitrary compiler path mappings.

This separation can simplify small programs because the runtime need not interpret a project's entire build policy. It can also expose accidental dependencies on compiler behavior. The important step is identifying which tool currently supplies each transformation.

My preference is for that ownership to remain visible. TypeScript's frameworks have detailed conventions, and following them is easier when the execution model is explicit. Removing a build command is useful only when the source no longer depends on what that command did.

Application source and published dependencies remain distinct

The runtime refuses TypeScript files under a node_modules path. The documentation explains that this discourages package authors from distributing source that requires consumers to depend on built-in stripping.

That maintains a distinction between choosing direct execution for an application's own files and imposing that choice on downstream package consumers. A script can become simpler without redefining the distribution format of every dependency it imports.

The documentation also distinguishes file execution from other ways of supplying source. Evaluation and standard input can use type stripping with the input-type setting selecting the module system. The REPL, syntax-check command, and inspector entry point do not all accept TypeScript syntax through the same mechanism.

That scope matters for developer tools that construct their own execution command. A task runner invoking a file and a tool sending code through another interface may exercise different support. Describing both as running Node is insufficient to establish that they select the same TypeScript behavior.

The documentation retains third-party tooling as the path for fuller support, including configuration-dependent behavior. A project can choose that path deliberately when its source requires it, while another script uses stable stripping. The two arrangements can coexist because they solve different execution requirements.

The stable path therefore fits a specific class of program. Its TypeScript syntax is erasable, its executable JavaScript is supported, and its imports already match Node's module rules. Checking remains a separate operation that can enforce those source constraints and the program's static relationships.

Node 25.2 gives that restricted execution model a stable designation. The practical result is confidence in a defined erasure mechanism, with fuller transformation, browser-oriented syntax, and package emission still assigned to tools chosen for those requirements.