Point node at a .ts file and it runs. No flag, no loader, no dist directory.
// greet.ts
export interface User { id: number; name: string; roles?: string[] }
export function greet(u: User): string {
return `${u.name} (#${u.id}) has ${(u.roles ?? []).length} role(s)`;
}
// main.ts
import { greet, type User } from './greet.ts';
const ada: User = { id: 1, name: 'Ada', roles: ['admin'] };
console.log(greet(ada), process.features.typescript); // Ada (#1) has 1 role(s) stripprocess.features.typescript is the runtime's own answer to "am I stripping?": the string 'strip' when stripping is active, false when it is off.
Node compiles nothing. It parses the file and overwrites every TypeScript-only construct with spaces — the experimental stripTypeScriptTypes export of node:module shows the result, turning const n: number = 1; into const n = 1;. Because each annotation becomes blanks of the same width, line and column numbers match the file on disk: a throw on source line 7, column 31 reports at assertPositive (file:///C:/work/trace.ts:7:31), honest in a way a transpile-and-run loader with a stale cache is not.

Stripping is a syntactic transform with no project model behind it, and that produces four hard rules.
Extensions are mandatory in every specifier. import './greet.ts' works, import './greet' does not, and the same applies to require('./greet.ts').
.ts follows package.json, exactly as .js does: ESM when "type": "module", CommonJS otherwise. .mts is always ESM, .cts always CommonJS, and .tsx is unsupported.
tsconfig.json is ignored. Not partially read: ignored. paths aliases and target downleveling have no effect; use Node's # subpath imports (Import Maps and Subpaths) for an alias.
node_modules is off limits. Node refuses to strip a .ts file under any node_modules directory, a deliberate push against publishing uncompiled source.
To prove a build really emitted JavaScript, pass --no-strip-types, renamed from --no-experimental-strip-types in Node 25.2.0; a .ts file then fails with ERR_UNKNOWN_FILE_EXTENSION.