deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Node 24 Runs TypeScript Natively, but Stripping Types Is All It Does

Node 24 executes .ts files directly by blanking out type annotations in place — but it never reads tsconfig., never type-checks, and cannot emit code.

Node 24 Runs TypeScript Natively, but Stripping Types Is All It Does

Node 24 will execute a TypeScript file straight from the command line. Running node server.ts simply works — no loader hook, no tsx, no compiled output folder. According to a post on dev.to, the --experimental-strip-types flag has been unnecessary since Node 23.6, and on Node 24 you get identical output whether or not you pass it. The author admits typing it for a week out of pure muscle memory.

The mechanism, the post explains, is character-level erasure rather than compilation. Node ships Amaro, a thin layer over swc's TypeScript stripper compiled to WebAssembly. When it loads a .ts, .mts or .cts file, it parses the source, replaces every piece of syntax that exists only for the type checker with blank spaces, and hands the result to V8.

Blanks, not deletions

The detail that makes this usable is that annotations are overwritten in place with an equal number of spaces rather than deleted. In the author's demo file, a nine-character annotation becomes nine spaces; a thirty-four-character Readonly<Record<string, string>> annotation becomes a thirty-four-character gap, and the executed line stays exactly as long as the one that was written. Line and column positions therefore match the original file, so a runtime stack trace points at the correct character without any source map.

That same demo shows the flip side. The sample code called a function with an empty options object and failed with a TypeError at runtime — the process never objected to the mismatch beforehand, because nothing was ever checked.

You can ask a running process which mode it is in via process.features.typescript: strip is the Node 24 default, transform appears when --experimental-transform-types is passed, and false shows up under --no-strip-types. That last flag turns .ts back into an unknown extension, which the author notes is a useful way to prove a deployment is running precompiled output rather than stripping types on the fly.

What stripping cannot do

Three hard limits follow directly from the erasure approach, per the post. Node never reads tsconfig. — not paths, not experimentalDecorators, not target; it never looks for the file at all. It performs no type checking, meaning nothing is validated and nothing is even resolved. And it cannot generate code, so any TypeScript feature that depends on the compiler emitting JavaScript is out of scope by construction.

The author counts five specific refusals in total and warns that one of them fails in a way that can cost an afternoon if nobody warns you first. One practical constraint is visible in the sample code: import specifiers must carry the real on-disk .ts extension, a detail the author says resurfaces later in the piece as an error message.

A build-free project layout

The author reports running this arrangement on a four-module service and on the two scripts that publish their blog, with no build tooling and nothing installed at runtime. The package. declares ESM and maps a #src/* import alias to the source directory so imports avoid long relative paths. The scripts are node --watch server.ts for development, node --test for tests, and tsc --noEmit for type checking — the checking step still exists, it has just moved out of the run path.

The sample service itself is built on node:http with no framework, and includes two things the author says demos tend to omit: a hard cap on request body size so a single client cannot grow the process indefinitely, and a shutdown path that lets in-flight requests finish.

Why it matters

For small services and utility scripts, this collapses the toolchain: nothing to bundle, nothing to transpile at runtime, and stack traces that still line up with the source you wrote. But the post is valuable precisely because it draws the line between running TypeScript and compiling it. Teams adopting this still need tsc --noEmit — or editor integration — for actual type safety, cannot rely on tsconfig. settings being honored at run time, and must steer clear of syntax that requires code generation.

The honest framing is that Node 24 made running TypeScript frictionless while leaving checking and emitting as separate, deliberate steps. The --no-strip-types flag gives operators a concrete way to verify which world a given deployment actually lives in.

  • #node-js
  • #typescript
  • #javascript
  • #runtimes
  • #developer-experience