erasableSyntaxOnly catches the TypeScript that Node cannot strip

One source file shown twice: on the left the type annotations are greyed out and gone and the runtime code still runs, on the right an enum has nothing to grey out and carries Node's ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX. A strip along the bottom names the six cases the demo covers, all of which the compiler flags as TS1294
COMPANION REPO · LIVE FROM GITHUB

On the laptop I am writing this on, node reading.ts just runs the file. No tsconfig, no output directory, no loader flag, and nothing on stderr about the feature being experimental. node --version says v26.1.0 and process.features.typescript says strip, and that second word is doing more work than it looks like. Node is not compiling anything. It deletes the type syntax and hands the remaining characters to V8, and the gap between those two descriptions is what this post is about. The Node docs mark type stripping stable as of v25.2.0 and v24.12.0, and on by default since v23.6.0 and v22.18.0.

Deleting works when the syntax you are deleting produces nothing. An interface produces nothing. A return type, a generic parameter, an as cast, a declare field, all nothing. Remove them and the remaining characters are valid JavaScript, byte for byte in the same positions, which is why the stripper can replace them with spaces and keep your line numbers intact.

Some TypeScript is not like that. An enum is a real object built by real statements at runtime. A parameter property is a field declaration plus an assignment the compiler writes into your constructor. Those constructs generate JavaScript rather than annotating it, and a tool whose only move is deletion has nothing to do with them except refuse.

DELETE THE TYPES · RUN WHAT IS LEFT · UNLESS IT WAS NEVER A TYPE
reading.ts
interface Reading { celsius: number }
function read(r: Reading): string {
return r.celsius.toFixed(1);
}
console.log(read({ celsius: 71.25 }));
$ node reading.ts
71.3
levels.ts
enum Scale { C, F }
interface Reading { celsius: number }
function read(r: Reading): string {
return Scale[Scale.C] + r.celsius;
}
$ node levels.ts
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]:
TypeScript enum is not supported in strip-only mode
an enum builds a real object at runtime, so there is nothing to delete — tsc --erasableSyntaxOnly reports TS1294 on that line while you are still typing it

What refusal looks like

An enum, in a file run directly:

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode

A const enum gives the same message, which surprised me the first time, since const enum is the one that gets inlined and emits nothing at the use site. It emits nothing after the compiler has resolved every member, and the stripper resolves nothing, so it is refused along with the rest.

A namespace that holds a value:

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript namespace declaration is not supported in strip-only mode

A namespace holding only types is fine, because it emits nothing. So is declare enum, for the same reason. The rule is not "the keyword is forbidden", it is "the declaration would have produced code".

A parameter property:

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript parameter property is not supported in strip-only mode

And import x = require(...), TypeScript's own spelling of a CommonJS require, which has to become an actual call:

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript import equals declaration is not supported in strip-only mode

Every one of those strings is copied out of a run on this machine, not from the documentation. The demo captures them on each run and asserts them, so if a later Node reworks the wording the check fails instead of the post quietly going wrong.

Finding them while typing

TypeScript 5.8 added erasableSyntaxOnly. Turn it on and the compiler errors on the constructs a stripper cannot handle, which moves every failure above from process start into your editor. The error is the same number in all of those cases:

src/cases/parameter-property-case.ts(9,15): error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.

TS1294, with the offending span underlined at the right column. The message does not name the construct, which I find mildly annoying on a long line, but the position is exact and that is most of what you need. The TypeScript team recommends pairing it with verbatimModuleSyntax, and that pairing is worth taking: without it the compiler elides imports it believes are type-only, and elision is another transform a stripper will not perform for you.

There is one case where the two tools agree for a reason that has nothing to do with emitted code. The old angle-bracket assertion, <string>raw, produces no runtime output at all, and yet:

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: The angle-bracket syntax for type assertions, `<T>expr`, is not supported in type strip mode. Instead, use the 'as' syntax: `expr as T`.

erasableSyntaxOnly flags it too, as TS1294, which I checked because the 5.8 release notes list four constructs and this is not one of them. It is refused for parsing reasons rather than because it emits anything, which is the one construct on the list where that is true. I have not found that spelled out in the docs, so treat the reason as my reading and the behaviour as the measured part. Write raw as string and both tools go quiet.

Where the flag stops

erasableSyntaxOnly is not a complete simulation of running the file, and the gap that bit me is decorators. A decorator is no longer TypeScript-specific syntax, it is a TC39 proposal that has not landed in the language yet, so the stripper passes it through untouched and hands it to V8. Its stage is worth a footnote: the Node documentation still calls it stage 3, while the TC39 tracker and the proposal's own README now list it at 2.7. Either way it is not JavaScript yet. The V8 in this Node has not shipped them — the Node docs say decorators stay unsupported until JavaScript supports them natively — so you get this:

SyntaxError: Invalid or unexpected token

No error code, no mention of TypeScript, and the caret points at the @. The compiler says nothing about that file under erasableSyntaxOnly, correctly by its own definition and unhelpfully by yours. If your codebase leans on decorators, that flag will report a clean build and the file will still not run.

The escape hatch that is gone

Node used to ship an opt-in mode that actually transformed these constructs instead of stripping them. --experimental-transform-types arrived in v22.7.0 and was removed in v26.0.0. On this machine it does not even parse:

node: bad option: --experimental-transform-types

That removal is the part I would want to know about before planning anything. If you were counting on a runtime flag to keep your enums working without a build, that plan expired with Node 26, and the alternatives are the ordinary ones: tsc, or a loader like tsx or swc that does a real transform.

Where I would and would not use this

Small things, mostly. A migration script, a build helper, a seed script, a one-file CLI that reads a config and writes a report. Those get types and a .ts extension and no toolchain, and that is a real improvement over the node build.js I would have written before.

For an application I am less sure. Node does not type-check, so tsc --noEmit stays in your pipeline either way, and once it is there the marginal cost of letting it emit is small. Source maps are another thing to check: stripping preserves line positions by padding, so stack traces line up without any map, but the moment you introduce a transforming loader you are back to needing one. I have measured none of this on Linux or in a container, only on Windows with Node v26.1.0 and TypeScript 5.9.3, and I would not extrapolate startup numbers from a laptop.

The narrower claim I am confident about: if you are going to run TypeScript directly, turn on erasableSyntaxOnly first and fix what it reports. Every error it gave me was a process that would otherwise have died at startup, and finding those while typing is worth the one line in the tsconfig.

The demo is at github.com/gdhami-net/erasable-syntax-demo. npm run check runs Node and tsc against each case, compares both results against the EXPECT comments in the files themselves, and prints the raw output at the end so you can read the errors rather than take mine as read.