| |

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.

SyntaxRunsNotes
Type annotationsYesErased
InterfacesYesErased
Type aliasesYesErased
import type / export typeYesErased
type qualifier on named importsYesErased
namespace with only typesYesErased
enumNoRequires codegen
namespace with runtime codeNoRequires codegen
Parameter propertiesNoRequires codegen
import X = require() aliasesNoRequires codegen
DecoratorsNoParser error
.tsx filesNoUnsupported

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.

ExtensionModule System
.tsInherited from nearest package.json "type"
.mtsAlways ESM
.ctsAlways CommonJS
.tsxUnsupported

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
  }
}
OptionPurpose
noEmittsc only type-checks; Node.js runs the .ts files
target: esnextDo not downlevel; Node.js supports modern syntax
module: nodenextAlign with Node.js module resolution
rewriteRelativeImportExtensionsRewrite .ts imports to .js when emitting
erasableSyntaxOnlyError on syntax Node.js cannot strip
verbatimModuleSyntaxRequire import type for type-only imports
allowImportingTsExtensionsPermit .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.

LimitationAlternative
enumUnion types or as const objects
Parameter propertiesExplicit property assignment
namespace with runtime codeModule-level exports
tsconfig.json pathsNode.js subpath imports (#name)
DecoratorsWait for TC39 Stage 3 support in JavaScript
.tsx filesUse 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 VersionFlag
22.18.0+None required
24+None required
< 22.18.0--experimental-strip-types
Disable--no-strip-types

Syntax Support

SyntaxRuns
Type annotationsYes
InterfacesYes
Type aliasesYes
import typeYes
namespace (types only)Yes
enumNo
Parameter propertiesNo
namespace (runtime code)No
DecoratorsNo
.tsxNo

Module System

ExtensionModule System
.tsFrom package.json "type"
.mtsESM
.ctsCommonJS

Recommended tsconfig.json

OptionValue
noEmittrue
targetesnext
modulenodenext
erasableSyntaxOnlytrue
verbatimModuleSyntaxtrue
allowImportingTsExtensionstrue

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

PitfallWhy It HappensFix
ERR_UNSUPPORTED_TYPESCRIPT_SYNTAXenum or parameter propertiesUse erasable alternatives
Type imported as valueMissing type keywordUse import type
Module not foundMissing .ts extensionAdd explicit extension
tsconfig.json paths not workingNode.js ignores tsconfig.jsonUse a loader or Node subpath imports
Types not checkedNode.js does not type-checkRun tsc --noEmit separately
.tsx unsupportedNode.js does not support JSXUse 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

ItemValue
Type strippingErases TypeScript types and executes JavaScript
Default flagNone required in Node.js 22.18+ and 24+
Older flag--experimental-strip-types
Disable flag--no-strip-types
Type checkingNot performed; run tsc --noEmit
tsconfig.jsonIgnored at runtime
Erasable syntaxAnnotations, interfaces, type aliases, import type
Non-erasable syntaxenum, parameter properties, runtime namespaces
Module systemSame rules as .js files
.tsxUnsupported
Recommended TypeScript5.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-types flag has been removed.
  • Only erasable syntax runs. enum, parameter properties, runtime namespaces, and import aliases error with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX. Use union types, as const objects, and explicit property assignments instead.
  • import type is mandatory for type-only imports. Without the type keyword, Node.js treats the import as a value import and fails at runtime. verbatimModuleSyntax enforces this at type-check time.
  • tsconfig.json is ignored at runtime. Path aliases, downleveling, and other tsconfig features do not apply. For those, use a full loader like tsx.
  • Module system follows .js rules. .ts inherits from package.json "type"; .mts is ESM; .cts is CommonJS. Relative imports require explicit .ts extensions.
  • Run tsc --noEmit separately. 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!