Node.js 6 🟢 Native TypeStrip Execution (–experimental-strip-types / –type-stripping)
Node.js can run TypeScript files directly, without a separate build step or transpiler. This capability is called type stripping. It works by erasing TypeScript-specific syntax—type annotations, interfaces, type aliases, and import type statements—replacing them with whitespace, and then executing the remaining JavaScript. The result is that .ts files run natively in Node.js, with no tsc compilation step and no source maps needed, because line numbers are preserved by the whitespace replacement.
The mechanism is deliberately lightweight. Node.js does not type-check your code, does not read tsconfig.json, and does not transform TypeScript features that require generating new JavaScript code, such as enum declarations or parameter properties. This means type stripping is not a replacement for the TypeScript compiler; it is a fast execution path for TypeScript that is already type-checked separately.
This chapter covers how to enable type stripping, the flags that control it, which TypeScript syntax runs and which errors, the module system rules for .ts files, and the recommended tsconfig.json settings that keep your editor and type checker aligned with Node.js runtime behavior.
Key point: Node.js runs TypeScript by erasing inline type syntax and executing the remainder. It does not type-check, does not read tsconfig.json, and does not support syntax that requires JavaScript code generation, such as enum, namespace with runtime code, or parameter properties. Use --experimental-strip-types on older versions; it is unflagged in Node.js 22.18+ and 24+.
Why native type stripping exists
The build-step problem. TypeScript has traditionally required a compilation step before execution. tsc converts .ts files to .js, often producing a dist/ directory and source maps. This adds a build phase to every development cycle and every deployment. Node.js type stripping removes the build step for development and for simple production cases, letting you run .ts files directly.
The type-checking separation problem. TypeScript does two jobs: type checking and type erasure. Type checking catches errors before runtime; type erasure removes the types so the code can run as JavaScript. tsc does both, but type checking is slow and is best run separately from execution. Node.js type stripping performs only the erasure, leaving type checking to tsc --noEmit or the IDE’s language server.
The syntax-limit problem. Not all TypeScript syntax can be erased. enum declarations generate JavaScript code at runtime. Parameter properties (constructor(private x: number)) generate property assignments. namespace with runtime code generates an object. These features require actual transformation, not just erasure. Node.js intentionally does not support them in type stripping because supporting them would require a full compiler and source maps, defeating the purpose of a lightweight runtime path.
The tsconfig.json problem. TypeScript’s tsconfig.json controls many behaviors: path aliases, downleveling, module resolution. Node.js ignores tsconfig.json entirely when running TypeScript. This means paths aliases do not work, and newer ECMAScript syntax is not downleveled. The runtime executes what is written, assuming Node.js supports that syntax natively. For projects that rely on tsconfig.json features, a full loader like tsx is required.
The module-system problem. TypeScript files follow the same module rules as .js files. A .ts file inherits its module system from the nearest package.json "type" field. .mts is always ESM; .cts is always CommonJS. Relative imports require explicit file extensions, including the .ts extension, which is a change from the TypeScript convention of omitting extensions.
a. Enabling type stripping
Node.js type stripping is enabled by default for .ts files that contain only erasable syntax. In Node.js 22.18.0 and later, and in Node.js 24 LTS, no flag is required.
node example.ts
On versions earlier than 22.18.0, the --experimental-strip-types flag is required:
node --experimental-strip-types example.ts
To disable type stripping entirely, use --no-strip-types or --no-experimental-strip-types, depending on the version:
node --no-strip-types example.ts
The legacy flag --experimental-transform-types enabled transformation of non-erasable syntax, but it has been removed. Type stripping is the only built-in mode in current versions.
Type stripping works with --eval and STDIN. The module system is determined by --input-type, as it is for JavaScript. TypeScript syntax is not supported in the REPL, --check, or inspect mode.
b. What runs and what errors
The dividing line is whether the TypeScript syntax can be removed by replacing it with whitespace. If it can, it runs. If it requires generating new JavaScript code, it errors.
| Syntax | Runs | Notes |
|---|---|---|
| Type annotations | Yes | Erased |
| Interfaces | Yes | Erased |
| Type aliases | Yes | Erased |
import type / export type | Yes | Erased |
type qualifier on named imports | Yes | Erased |
namespace with only types | Yes | Erased |
enum | No | Requires codegen |
namespace with runtime code | No | Requires codegen |
| Parameter properties | No | Requires codegen |
import X = require() aliases | No | Requires codegen |
| Decorators | No | Parser error |
.tsx files | No | Unsupported |
An enum declaration produces ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX. A namespace that exports only types is fine; one that exports a value errors. Parameter properties in a constructor error because they generate property assignments.
The erasableSyntaxOnly compiler option in TypeScript 5.8+ makes tsc flag these features at type-check time, so you catch them before runtime.
c. Module system rules for .ts files
Node.js determines the module system for .ts files the same way it does for .js files.
| Extension | Module System |
|---|---|
.ts | Inherited from nearest package.json "type" |
.mts | Always ESM |
.cts | Always CommonJS |
.tsx | Unsupported |
For a .ts file to run as ESM, the nearest package.json must have "type": "module". Without it, the file is treated as CommonJS. Node.js does not convert between module systems; if you write import and export, the file must be in an ESM context. If you write require and module.exports, it must be in a CommonJS context.
Relative imports and require() calls require explicit file extensions, including the .ts extension:
import { helper } from './helper.ts';
const config = require('./config.ts');
The allowImportingTsExtensions compiler option lets tsc type-check files that use .ts extensions in import specifiers.
d. Type-only imports and verbatimModuleSyntax
Because Node.js strips import type statements entirely, the type keyword is mandatory for type-only imports. Without it, Node.js treats the import as a value import, which fails at runtime because the type does not exist as a value.
// Correct: removed at runtime
import type { UserType } from './user.ts';
// Correct: inline type qualifier
import { UserService, type UserType } from './user-service.ts';
// Runtime error: UserType is a type, not a value
import { UserType } from './user.ts';
The verbatimModuleSyntax compiler option enforces this at type-check time. With it enabled, TypeScript requires explicit import type for type-only imports, so the code that tsc accepts is the code that Node.js can strip correctly.
e. Recommended tsconfig.json settings
Node.js recommends TypeScript 5.8 or newer with a tsconfig.json that reflects Node.js runtime behavior. The goal is to make tsc flag anything Node.js cannot strip, so type checking and runtime agree.
{
"compilerOptions": {
"noEmit": true,
"target": "esnext",
"module": "nodenext",
"rewriteRelativeImportExtensions": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true
}
}
| Option | Purpose |
|---|---|
noEmit | tsc only type-checks; Node.js runs the .ts files |
target: esnext | Do not downlevel; Node.js supports modern syntax |
module: nodenext | Align with Node.js module resolution |
rewriteRelativeImportExtensions | Rewrite .ts imports to .js when emitting |
erasableSyntaxOnly | Error on syntax Node.js cannot strip |
verbatimModuleSyntax | Require import type for type-only imports |
allowImportingTsExtensions | Permit .ts extensions in imports |
The erasableSyntaxOnly flag is the key setting for type stripping. It makes tsc reject enum, parameter properties, namespace with runtime code, and other non-erasable syntax, so the errors appear at type-check time rather than at runtime.
Run type checking separately:
npx tsc --noEmit
Node.js does not check types at runtime. The type checker is the only guard against type errors.
f. When type stripping is not enough
Type stripping handles the common TypeScript patterns: annotations, interfaces, type aliases, and type-only imports. When a project uses features that require code generation, or when it relies on tsconfig.json features that Node.js ignores, a full loader is required.
| Limitation | Alternative |
|---|---|
enum | Union types or as const objects |
| Parameter properties | Explicit property assignment |
namespace with runtime code | Module-level exports |
tsconfig.json paths | Node.js subpath imports (#name) |
| Decorators | Wait for TC39 Stage 3 support in JavaScript |
.tsx files | Use a bundler or tsx |
For full TypeScript support with tsconfig.json, path aliases, and transformation of non-erasable syntax, install a loader like tsx:
npm install --save-dev tsx
node --import=tsx your-file.ts
Or run directly:
npx tsx your-file.ts
Amaro is Node.js’s official type-stripping loader. It wraps @swc/wasm-typescript and processes TypeScript files, including those in node_modules when used as a global loader.
Complete Example Session
# ============================================
# PART 1: RUN A TYPESCRIPT FILE DIRECTLY
# ============================================
# Node.js 22.18.0+ requires no flag.
node example.ts
// ============================================
// PART 2: ERASABLE SYNTAX THAT RUNS
// ============================================
// example.ts
interface User {
name: string;
age: number;
}
function greet(user: User): string {
return `Hello, ${user.name}`;
}
const alice: User = { name: 'Alice', age: 30 };
console.log(greet(alice));
// ============================================
// PART 3: TYPE-ONLY IMPORT
// ============================================
// types.ts
export interface Config {
port: number;
host: string;
}
// app.ts
import type { Config } from './types.ts';
const config: Config = { port: 3000, host: 'localhost' };
console.log(config);
// ============================================
// PART 4: ENUM ERRORS
// ============================================
// This will fail with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX
enum Status {
Pending,
Active,
Closed,
}
// ============================================
// PART 5: ERASABLE ALTERNATIVE TO ENUM
// ============================================
// Use a union type and a const object instead.
type Status = 'pending' | 'active' | 'closed';
const Status = {
Pending: 'pending',
Active: 'active',
Closed: 'closed',
} as const satisfies Record<string, Status>;
// ============================================
// PART 6: RECOMMENDED tsconfig.json
// ============================================
{
"compilerOptions": {
"noEmit": true,
"target": "esnext",
"module": "nodenext",
"rewriteRelativeImportExtensions": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true
}
}
# ============================================
# PART 7: TYPE CHECK SEPARATELY
# ============================================
npx tsc --noEmit
// ============================================
// PART 8: MODULE SYSTEM DETERMINATION
// ============================================
// package.json: { "type": "module" }
// app.ts is ESM
import { readFile } from 'node:fs/promises';
const data = await readFile('./data.txt', 'utf-8');
// ============================================
// PART 9: EXPLICIT EXTENSIONS
// ============================================
// Relative imports require the .ts extension.
import { helper } from './helper.ts';
import { config } from './config.ts';
# ============================================
# PART 10: FULL LOADER FOR NON-ERASABLE SYNTAX
# ============================================
# When you need enum, paths, or full support.
npm install --save-dev tsx
node --import=tsx app.ts
These ten parts cover running TypeScript directly, erasable syntax, type-only imports, the enum error, the erasable alternative, recommended tsconfig.json, separate type checking, module system rules, explicit extensions, and the full loader alternative.
Quick Reference
Enabling Type Stripping
| Node.js Version | Flag |
|---|---|
| 22.18.0+ | None required |
| 24+ | None required |
| < 22.18.0 | --experimental-strip-types |
| Disable | --no-strip-types |
Syntax Support
| Syntax | Runs |
|---|---|
| Type annotations | Yes |
| Interfaces | Yes |
| Type aliases | Yes |
import type | Yes |
namespace (types only) | Yes |
enum | No |
| Parameter properties | No |
namespace (runtime code) | No |
| Decorators | No |
.tsx | No |
Module System
| Extension | Module System |
|---|---|
.ts | From package.json "type" |
.mts | ESM |
.cts | CommonJS |
Recommended tsconfig.json
| Option | Value |
|---|---|
noEmit | true |
target | esnext |
module | nodenext |
erasableSyntaxOnly | true |
verbatimModuleSyntax | true |
allowImportingTsExtensions | true |
Best Practices
✅ Do This:
import type { Config } from './types.ts'; // Type-only import
const Status = { Pending: 'pending' } as const; // Erasable alternative to enum
class User {
constructor(private name: string) {} // ❌ parameter property errors
}
{
"compilerOptions": {
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true
}
}
node app.ts # Node 22.18+
npx tsc --noEmit # Separate type check
❌ Don’t Do This:
enum Status { Pending, Active } // ❌ Requires codegen
import { UserType } from './user.ts'; // ❌ Type imported as value
import { helper } from './helper'; // ❌ Missing .ts extension
node --experimental-transform-types app.ts // ❌ Removed in current versions
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX | enum or parameter properties | Use erasable alternatives |
| Type imported as value | Missing type keyword | Use import type |
| Module not found | Missing .ts extension | Add explicit extension |
tsconfig.json paths not working | Node.js ignores tsconfig.json | Use a loader or Node subpath imports |
| Types not checked | Node.js does not type-check | Run tsc --noEmit separately |
.tsx unsupported | Node.js does not support JSX | Use a bundler or loader |
Real-World Examples
1. Run a TypeScript File
node script.ts
2. Type-Only Import
import type { User } from './types.ts';
3. Erasable Alternative to Enum
type Status = 'pending' | 'active';
const Status = { Pending: 'pending', Active: 'active' } as const;
4. Explicit Property Assignment
class UserService {
readonly repo: Repo;
constructor(repo: Repo) { this.repo = repo; }
}
5. Recommended tsconfig.json
{
"compilerOptions": {
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"module": "nodenext"
}
}
6. Type Check Separately
npx tsc --noEmit
7. ESM TypeScript File
// package.json
{ "type": "module" }
// app.ts
import { readFile } from 'node:fs/promises';
8. Full Loader for Enum Support
npx tsx app.ts
9. Disable Type Stripping
node --no-strip-types app.ts
10. Check Node.js Version
node --version
Visual
Type Stripping Flow
┌──────────────────────────────────────────────────────────────┐
│ example.ts │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Erase inline types (replace with whitespace) │ │
│ │ - type annotations │ │
│ │ - interfaces │ │
│ │ - import type │ │
│ └────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Execute remaining JavaScript │ │
│ │ (no type checking, no source maps needed) │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
What Strips and What Errors
┌──────────────────────────────────────────────────────────────┐
│ ERASABLE (runs) │ NON-ERASABLE (errors) │
│ ─────────────────────────────┼───────────────────────────── │
│ const x: number = 5 │ enum Status { A, B } │
│ interface User { } │ constructor(private x) { } │
│ type Config = { } │ namespace N { let x = 1 } │
│ import type { T } │ import X = require() │
│ namespace N { type T } │ @decorator │
└──────────────────────────────────────────────────────────────┘
Module System Determination
┌──────────────────────────────────────────────────────────────┐
│ file.ts │
│ │ │
│ ▼ │
│ Nearest package.json has "type": "module"? │
│ │ │
│ ├── Yes ──▶ ESM │
│ │ │
│ └── No ──▶ CommonJS │
│ │
│ file.mts ──▶ Always ESM │
│ file.cts ──▶ Always CommonJS │
└──────────────────────────────────────────────────────────────┘
Type Checking Separation
┌──────────────────────────────────────────────────────────────┐
│ node app.ts npx tsc --noEmit │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ Erases types │ │ Checks types │ │
│ │ Executes JS │ │ No output │ │
│ │ No type check │ │ Reports errors │ │
│ └────────────────────┘ └────────────────────┘ │
│ │
│ Run both: execution for speed, type checking for safety. │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Type stripping | Erases TypeScript types and executes JavaScript |
| Default flag | None required in Node.js 22.18+ and 24+ |
| Older flag | --experimental-strip-types |
| Disable flag | --no-strip-types |
| Type checking | Not performed; run tsc --noEmit |
tsconfig.json | Ignored at runtime |
| Erasable syntax | Annotations, interfaces, type aliases, import type |
| Non-erasable syntax | enum, parameter properties, runtime namespaces |
| Module system | Same rules as .js files |
.tsx | Unsupported |
| Recommended TypeScript | 5.8+ with erasableSyntaxOnly and verbatimModuleSyntax |
Key takeaways:
- Node.js runs TypeScript by stripping types, not compiling. Type annotations, interfaces, and type aliases are replaced with whitespace, and the remaining JavaScript executes. No type checking is performed.
- No flag is required in Node.js 22.18.0 and later. The feature is enabled by default. On older versions, use
--experimental-strip-types. The legacy--experimental-transform-typesflag has been removed. - Only erasable syntax runs.
enum, parameter properties, runtime namespaces, and import aliases error withERR_UNSUPPORTED_TYPESCRIPT_SYNTAX. Use union types,as constobjects, and explicit property assignments instead. import typeis mandatory for type-only imports. Without thetypekeyword, Node.js treats the import as a value import and fails at runtime.verbatimModuleSyntaxenforces this at type-check time.tsconfig.jsonis ignored at runtime. Path aliases, downleveling, and othertsconfigfeatures do not apply. For those, use a full loader liketsx.- Module system follows
.jsrules..tsinherits frompackage.json"type";.mtsis ESM;.ctsis CommonJS. Relative imports require explicit.tsextensions. - Run
tsc --noEmitseparately. Type stripping does not check types. The type checker is the only guard against type errors, and it should run in development and CI.
Remember: Node.js type stripping is a lightweight execution path for TypeScript, not a replacement for the TypeScript compiler. It erases types and runs the code, but it does not check types, does not read tsconfig.json, and does not transform syntax that requires generating JavaScript. The key is to write TypeScript that is erasable: use import type, avoid enum and parameter properties, and rely on erasableSyntaxOnly in tsconfig.json to catch non-erasable syntax before runtime. Run the type checker separately, and use a full loader when the project needs features that type stripping cannot handle. When these constraints are respected, .ts files run directly in Node.js with no build step, preserving line numbers and eliminating the compilation phase from the development loop.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!