Node.js TypeScript Type Stripping: What Works and What Breaks
Run .ts files directly in Node.js — but know what breaks
Run node app.ts and watch it execute. No build step, no ts-node, no transpiler config. Starting with Node.js 22.6 and enabled by default since 23.6, type stripping lets you run TypeScript files directly — but it's not full TypeScript support, and the gotchas will bite you in production.
How Type Stripping Actually Works
Type stripping doesn't compile TypeScript to JavaScript — it erases type annotations by replacing them with whitespace. This preserves line numbers and column positions, eliminating the need for source maps.
// Input TypeScript
function greet(name: string): void {
console.log(`Hello, ${name}`);
}
// After type stripping (conceptual)
function greet(name ) {
console.log(`Hello, ${name}`);
}The whitespace replacement is the key insight: your stack traces point to the exact lines in your .ts files because nothing moved.
What Doesn't Work
Type stripping only removes erasable syntax — type annotations that don't affect runtime behavior. Several TypeScript features require actual code generation:
- Enums — Compile to runtime objects with reverse mappings
- Parameter properties —
constructor(public name: string)needs field assignment - Namespaces with runtime code — Generate IIFEs
- import alias —
import Foo = require('foo') - JSX (.tsx files) — Not supported at all
// ❌ These all fail with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX
enum Status { Active, Inactive }
class User {
constructor(public name: string) {} // Parameter property
}
namespace Utils {
export const VERSION = '1.0'; // Runtime code in namespace
}If you need these features, use --experimental-transform-types, which actually transpiles TypeScript (with source maps enabled by default).
The Import Trap
This is the most common production bug: importing types without the type keyword causes runtime errors because type stripping doesn't know they're types.
// ❌ Runtime error — Node tries to import a non-existent value
import { User, UserRole } from './types.ts';
// ✅ Correct — explicit type imports
import type { User, UserRole } from './types.ts';
// ✅ Also correct — inline type modifier
import { createUser, type User } from './user.ts';Enable verbatimModuleSyntax in your tsconfig.json to catch this at type-check time.
File Extensions Are Mandatory
Unlike traditional TypeScript where you import './module.js' (the compiled output), type stripping requires the actual file extension:
// ❌ Won't resolve
import { foo } from './module';
import { bar } from './module.js';
// ✅ Must use .ts extension
import { foo } from './module.ts';This applies to both ESM imports and CommonJS require() calls. The module system is determined by file extension: .mts is always ESM, .cts is always CommonJS, and .ts follows package.json's "type" field.
tsconfig.json Is Ignored
Node.js doesn't read your tsconfig.json during type stripping. Features that depend on compiler options won't work:
- Path aliases —
pathsmapping has no effect - Target downleveling — No ES5/ES6 transpilation
- Decorator metadata —
emitDecoratorMetadataunsupported
For path aliases, use subpath imports in package.json (they must start with #).
Recommended tsconfig.json
While Node ignores it, you still need a tsconfig for IDE support and tsc type-checking:
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"noEmit": true,
"verbatimModuleSyntax": true,
"erasableSyntaxOnly": true,
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true
}
}erasableSyntaxOnly (TypeScript 5.8+) is the key flag — it errors on any syntax type stripping can't handle.
Production Considerations
Type stripping is stable as of Node.js 25.2, but consider these factors:
- No type checking — You're running TypeScript syntax, but not getting any type safety at runtime. Always run
tsc --noEmitin CI. - Dependencies must be JavaScript — Node refuses to type-strip files in
node_modulesto prevent shipping TypeScript packages. - Decorators aren't supported — Stage 3 decorators aren't in JavaScript yet, so Node won't run them.
When to Use It
Type stripping shines for scripts, CLIs, and development workflows where you want to skip the build step. For production services, consider whether the simplicity tradeoff is worth the feature limitations — or use tsx/ts-node for full TypeScript support with tsconfig awareness.
Advertisement
Explore these curated resources to deepen your understanding
Official Documentation
Tools & Utilities
Related Insights
Explore related edge cases and patterns
Advertisement