C.W.K.
Stream
Lesson 04 of 05 · published

Native TypeScript — --experimental-strip-types

~11 min · modern-node, typescript, strip-types

Level 0Node Curious
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Node 22 added the ability to run TypeScript directly. It's not full TypeScript support — it strips types and runs the JavaScript underneath. For most code, that's enough."

What --experimental-strip-types Does

Node 22 introduced --experimental-strip-types. Node 24 stabilized it. As of Node 26, you can run .ts files 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 --experimental-strip-types 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, as assertions, type imports

Doesn't Work (in strip-types mode):

  • Enums (enum X { a, b }) — these compile to runtime code, not just types.
  • Namespaces with values (TS namespaces that aren't pure type-only).
  • 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

Pair strip-types with tsc --noEmit for type checking:
  • Run the actual program with node --experimental-strip-types (Node executes, types are decoration).
  • Run tsc --noEmit in CI and pre-commit (TypeScript verifies types, emits nothing).
Type errors fail CI; runtime errors fail at runtime. The two roles split cleanly. You no longer need ts-node, tsx, esbuild-register, or any other transpile-on-import hook. The TypeScript compiler becomes pure linter; Node runs your code.

This is the cleanest dev experience for Node + TypeScript in 2026. Faster than transpile-on-import (no extra process per execution), simpler than build-step setups, and you get to keep all the type safety.

The .ts / .mts / .cts Convention

Same as .js / .mjs / .cjs:

  • .mts — always ESM
  • .cts — always CJS
  • .ts — follows the nearest package.json type field

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 \
  --experimental-strip-types \
  --env-file=.env \
  --watch \
  --enable-source-maps \
  server.ts

That replaces: ts-node, dotenv, nodemon, esbuild-register, source-map-support. Five npm packages, gone. The package.json script gets shorter every Node release.

Pippa's Confession

When Node 22 shipped this, my first reaction was "interesting but my projects use Vite/tsx already." Dad pushed back: "What do tsx and Vite actually do that strip-types doesn't?" For backend code, the answer was mostly "nothing useful." Both ship a TS compiler that takes 200MB and does work the runtime now does for free. cwkPippa's backend scripts that used tsx switched to node --experimental-strip-types and the dev startup got faster — no compiler warm-up. The lesson, again: check whether your tool justification still applies.

Code

tsconfig + scripts for the strip-types workflow·json
// tsconfig.json for a project using strip-types + tsc-as-linter
{
  "compilerOptions": {
    "target": "esnext",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "strict": true,
    "noEmit": true,                 // tsc only checks; doesn't emit
    "allowImportingTsExtensions": true,  // import './foo.ts' is OK
    "verbatimModuleSyntax": true,   // play nice with strip-types
    "isolatedModules": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

// package.json scripts
//   "dev":   "node --experimental-strip-types --env-file=.env --watch src/server.ts",
//   "start": "node --experimental-strip-types --env-file=.env.production src/server.ts",
//   "check": "tsc --noEmit",
//   "test":  "node --experimental-strip-types --test src/**/*.test.ts"
Plain TypeScript Node runs directly·typescript
// A real TS file that strip-types handles
import { readFile } from 'node:fs/promises';

interface Config {
  port: number;
  hosts: readonly string[];
  database: {
    url: string;
    poolSize: number;
  };
}

async function loadConfig(path: string): Promise<Config> {
  const raw = await readFile(path, 'utf-8');
  return JSON.parse(raw) as Config;
}

const config = await loadConfig('./config.json');
console.log(`starting on port ${config.port}`);
config.hosts.forEach((h: string) => console.log(`  - ${h}`));

External links

Exercise

Take a small TypeScript project that currently uses tsx or ts-node for development. Switch the dev script to node --experimental-strip-types. Run the existing tests; they should pass without changes. Time the cold startup of both — strip-types should be visibly faster on second invocation because there's no transpiler warm-up. If something breaks, suspect (a) enum usage or (b) missing .ts extensions on imports.
Hint
For enums, search your codebase for enum — if you have them, decide whether to replace with as const literal-typed objects (works with strip-types) or keep using tsx. For extension imports, your tsconfig needs allowImportingTsExtensions: true and your imports need explicit .ts extensions. A codemod script can rewrite import paths in one pass.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Ttoriwarm

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.