Évekig reflex volt, hogy egy TypeScript-fájl futtatásához kell valami a Node és a kód közé: tsc, ts-node, tsx, esetleg egy esbuild-alapú saját megoldás. Ez a reflex mára elavult. A Node.js a 22.18-as verzió óta alapértelmezetten futtatja a .ts fájlokat, a 25.2 (és az LTS-ágon a 24.12) óta pedig a funkció hivatalosan is stabil. A 2026 májusában megjelent Node 26 közben a másik irányban is lezárta a kérdést: kikerült belőle az enumokat és namespace-eket lefordító kísérleti mód. Most már pontosan tudjuk, mit kapunk natívan – és mit nem fogunk soha.
Típusok helyett whitespace
A Node nem fordítja le a TypeScriptet, hanem strippeli: a beépített Amaro nevű, SWC-re épülő komponens egyszerűen kicseréli a típusannotációkat, interface-eket és type aliasokat whitespace-re, a maradékot pedig sima JavaScriptként futtatja. Típusellenőrzés nem történik, és fordítási kimenet sincs.
A whitespace-csere nem lustaság, hanem tudatos döntés: mivel minden karakter a helyén marad, a sor- és oszlopszámok nem csúsznak el, így a stack trace-ek source map nélkül is pontosak. Ha a hiba a .ts fájl 7. sorában van, a Node a 7. sort írja ki – nincs köztes fordított fájl, amire fordítgatni kellene vissza a pozíciókat.
// szamla.ts
interface Tetel {
nev: string;
nettoAr: number;
afaKulcs: number;
}
function brutto(t: Tetel): number {
return t.nettoAr * (1 + t.afaKulcs);
}
const tetel: Tetel = { nev: "Tárhely", nettoAr: 12000, afaKulcs: 0.27 };
console.log(`${tetel.nev}: ${brutto(tetel)} Ft`);
$ node szamla.ts
Tárhely: 15240 Ft
Semmi flag, semmi konfiguráció. (Node 22.6 és 22.18 között még kellett hozzá a --experimental-strip-types kapcsoló.)
Amit a Node sosem fog futtatni
A stripping csak azzal a szintaxissal működik, ami törölhető – vagyis kivehető a fájlból úgy, hogy a maradék érvényes, azonos viselkedésű JavaScript legyen. A TypeScriptnek viszont van néhány olyan képessége, ami futásidejű kódot generál, ezért nem törölhető:
enumnamespacefutásidejű kóddal (a csak típusokat tartalmazó namespace mehet)- parameter property-k (
constructor(private x: number)) import x = require(...)típusú aliasok
Ezekre a Node nem valami rejtélyes fordítási hibát dob, hanem célzottat:
$ node enumtest.ts
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]:
TypeScript enum is not supported in strip-only mode
A 22.7-től létezett egy --experimental-transform-types flag, ami ezeket a konstrukciókat is lefordította. A Node 26.0.0 azonban semver-major változásként eltávolította, pótlás nélkül. Ez fontosabb hír, mint amilyennek látszik: a Node ezzel kimondta, hogy natívan csak a törölhető TypeScript-részhalmazt fogja valaha is támogatni. Ha a kódbázis enumokra vagy dekorátorokra épül, oda továbbra is transpiler vagy egy futtató (pl. tsx) kell.
A TypeScript-csapat ugyanebbe az irányba tart: az 5.8 óta létezik az --erasableSyntaxOnly fordítóflag, ami pontosan azokra a konstrukciókra ad fordítási hibát, amiket a Node nem hajlandó futtatni. Ha ez be van kapcsolva a tsconfigban, futásidőben már nem érhet meglepetés.
A buktatók, amikbe tényleg bele lehet lépni
1. A Node végrehajt, nem ellenőriz. A típushibás kód vígan lefut, amíg futásidőben is értelmes. A típusellenőrzés a szerkesztő és a CI dolga marad: egy tsc --noEmit a pipeline-ban ugyanúgy kell, mint eddig.
2. Az importokban kötelező a fájlkiterjesztés. A Node ESM-szabályai érvényesek, tehát import { brutto } from './szamla.ts' – a kiterjesztés nélküli './szamla' ERR_MODULE_NOT_FOUND hibával elszáll. Aki bundlerhez szokott, annak ez lesz az első pofon. A tsc a .ts kiterjesztésű importokat a TypeScript 5.7-es rewriteRelativeImportExtensions opciójával fogadja el (és írja át .js-re, ha mégis fordítunk).
3. Típusimportnál kötelező az import type. Ez a legalattomosabb. A stripper nem tudja, hogy az importált név típus-e vagy érték, ezért ha simán importálunk egy interface-t, az import megmarad a JavaScriptben – és futásidőben ezt kapjuk:
import { Felhasznalo } from './types.ts';
// SyntaxError: The requested module './types.ts'
// does not provide an export named 'Felhasznalo'
import type { Felhasznalo } from './types.ts'; // ✅ így működik
A verbatimModuleSyntax tsconfig-opcióval a tsc már fordításkor kikényszeríti a helyes formát.
4. A node_modules-ban nem strippel. A Node szándékosan megtagadja a típusok eltávolítását a node_modules alatti fájlokból, hogy senki ne kezdjen nyers TypeScript-forrást publikálni npm-re. Csomagot továbbra is fordítva adunk ki.
5. A tsconfig.json-t a futtatás ignorálja. A paths aliasok, a target és társaik csak a szerkesztőre és a tsc-re hatnak, a Node rájuk se néz. A .tsx pedig egyáltalán nem támogatott.
A hivatalos dokumentáció ajánlott tsconfigja ezeket egyben kezeli:
{
"compilerOptions": {
"noEmit": true,
"target": "esnext",
"module": "nodenext",
"rewriteRelativeImportExtensions": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true
}
}
Mire éri meg használni?
Ahol a legtöbbet hozza: buildscriptek, CLI-eszközök, CI-segédkódok, cron jobok, kisebb szerverek – minden, ahol eddig csak a TypeScript kedvéért tartottunk fenn egy build lépést vagy egy extra dev-függőséget. Egy node deploy.ts egyszerűen elindul, a stack trace pontos, és nincs mit cache-elni vagy watchban újrafordítani.
Ahol nem (még): dekorátorokra és enumokra épülő kódbázisok – tipikusan NestJS és TypeORM –, ott marad a transpile vagy a tsx. A frontend-kód pedig úgyis bundleren megy át, ott ez a kérdés fel sem merül.
A lényeg, amit érdemes hazavinni: az „erasable TypeScript” nem átmeneti állapot, hanem a Node és a TypeScript-csapat közösen kijelölt iránya. Ha új Node-projektet indítasz, az erasableSyntaxOnly + verbatimModuleSyntax párossal olyan kódot írsz, ami build lépés nélkül fut ma, és futni fog a Node 26 utáni világban is.