native Compiler-Geschwindigkeit ohne den tsc-Flaschenhals
Der offizielle TypeScript-Compiler tsc ist in JavaScript geschrieben und für Typsicherheit optimiert, nicht für Geschwindigkeit. esbuild und swc kompilieren denselben TypeScript-Code in Go beziehungsweise Rust und erreichen dabei zehn- bis zwanzigfach kürzere Build-Zeiten, verzichten dafür aber bewusst auf die vollständige Typprüfung während der Transformation.
Inhaltsverzeichnis
- 1. Warum tsc für große Projekte zum Flaschenhals wird
- 2. Das Grundprinzip: Transpilieren statt Typprüfen
- 3. esbuild im Detail: Go-Performance für TypeScript-Transforms
- 4. swc im Detail: Rust-basierte Kompilierung mit Plugin-System
- 5. isolatedModules: die notwendige tsconfig-Absicherung
- 6. Typprüfung getrennt ausführen: tsc --noEmit
- 7. Integration in Vite, Jest und Build-Pipelines
- 8. Grenzen: was esbuild und swc nicht können
- 9. esbuild, swc und tsc im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum tsc für große Projekte zum Flaschenhals wird
Der offizielle TypeScript-Compiler tsc ist selbst in TypeScript beziehungsweise JavaScript implementiert und muss bei jedem Build den vollständigen Typprüfungsprozess durchlaufen: Er löst Modulabhängigkeiten auf, erstellt einen Typbaum für das gesamte Projekt und prüft jede Zuweisung, jeden Funktionsaufruf und jede Schnittstellenimplementierung gegen dieses Typsystem. Bei kleinen Projekten mit wenigen hundert Dateien ist das kein Problem, aber bei großen Codebasen mit mehreren tausend Dateien kann ein einzelner tsc-Build mehrere Minuten dauern, was in einer CI-Pipeline mit hunderten Builds pro Tag erhebliche Wartezeiten summiert.
Die Erkenntnis, die zur Entwicklung von esbuild und swc führte, ist simpel: Die eigentliche Transformation von TypeScript-Syntax zu JavaScript, also das Entfernen von Typannotationen und das Übersetzen neuerer Sprachfeatures, ist rechnerisch trivial und benötigt keine vollständige Typprüfung. Nur die Typprüfung selbst ist der aufwendige Teil. Werden beide Schritte getrennt, kann die reine Transformation in einer nativ kompilierten Sprache wie Go oder Rust um Größenordnungen schneller laufen als in JavaScript, während die Typprüfung weiterhin bei Bedarf separat mit tsc läuft.
2. Das Grundprinzip: Transpilieren statt Typprüfen
Sowohl esbuild als auch swc arbeiten nach demselben Grundprinzip, das TypeScript selbst als transpileModule-API anbietet: Jede Datei wird für sich genommen geparst und die Typannotationen werden entfernt, ohne den Typ tatsächlich gegen andere Dateien im Projekt zu validieren. Ein Aufruf einer nicht existierenden Methode auf einem Objekt wird also anstandslos kompiliert, solange die Syntax selbst gültiges TypeScript ist. Der Fehler würde erst zur Laufzeit als TypeError sichtbar, nicht schon beim Build.
Dieser fundamentale Unterschied zu tsc ist kein Bug, sondern das bewusste Design-Ziel: esbuild und swc wollen so schnell wie möglich aus TypeScript-Quellcode lauffähiges JavaScript erzeugen, während die Aufgabe der Typsicherheit an einen separaten Prozess delegiert wird. In der Praxis bedeutet das, dass ein produktives Setup fast immer zwei Werkzeuge parallel einsetzt: einen schnellen Transpiler für den eigentlichen Build und tsc ausschließlich für die Typprüfung, meist als eigener CI-Schritt oder als IDE-Integration.
3. esbuild im Detail: Go-Performance für TypeScript-Transforms
esbuild, geschrieben in Go von Evan Wallace, ist in erster Linie ein Bundler mit integriertem TypeScript-Transpiler, der Parsen, Transformieren und Bundling in einem einzigen, hochparallelisierten Prozess vereint. Die Performance-Gewinne kommen aus mehreren Quellen gleichzeitig: Go kompiliert zu nativem Maschinencode ohne die Interpreter-Overhead von Node.js, esbuild nutzt alle verfügbaren CPU-Kerne parallel für unabhängige Dateien, und der interne AST wird speichereffizient als kompakte Datenstruktur statt als generisches JavaScript-Objektgraph gehalten.
Für TypeScript-Projekte ist die esbuild-API sowohl über die CLI als auch programmatisch über Node.js nutzbar, was sie zur bevorzugten Wahl für Custom-Build-Skripte macht. Die Konfiguration bleibt bewusst minimal im Vergleich zu Webpack, verzichtet aber auch auf komplexere Features wie ein Plugin-Ökosystem in vergleichbarem Umfang. Für reine Transpilations- und Bundling-Aufgaben in TypeScript-Projekten ist esbuild deshalb oft die pragmatischste Wahl, wenn keine tiefen Custom-Transformationen des Codes nötig sind.
// build.mjs — esbuild programmatic build script for a TypeScript service
import * as esbuild from 'esbuild';
const result = await esbuild.build({
entryPoints: ['src/main.ts'],
bundle: true,
platform: 'node',
target: 'node20',
format: 'esm',
outfile: 'dist/main.js',
sourcemap: 'linked',
minify: process.env.NODE_ENV === 'production',
external: ['pg-native'], // native modules stay external
metafile: true,
});
// Optional: inspect bundle composition for size regressions
console.log(await esbuild.analyzeMetafile(result.metafile));
4. swc im Detail: Rust-basierte Kompilierung mit Plugin-System
swc (Speedy Web Compiler), geschrieben in Rust, verfolgt einen ähnlichen Ansatz wie esbuild, unterscheidet sich aber in zwei wichtigen Punkten: Es bietet ein umfangreicheres, WASM-basiertes Plugin-System für benutzerdefinierte AST-Transformationen, und es wird von größeren Frameworks wie Next.js als Ersatz für Babel eingesetzt, wodurch es tiefer in bestehende Transform-Pipelines integrierbar ist. Während esbuild primär als eigenständiger Bundler auftritt, positioniert sich swc eher als austauschbare Compiler-Engine innerhalb anderer Tools.
Für TypeScript-Projekte, die zusätzlich zur reinen Transpilierung benutzerdefinierte Codetransformationen brauchen, etwa das automatische Entfernen bestimmter Decorators oder das Einfügen von Instrumentierungscode für Observability, bietet swc über sein Plugin-System deutlich mehr Flexibilität als esbuild. Diese Flexibilität kommt mit einer etwas komplexeren Konfiguration über die .swcrc-Datei, die im Vergleich zu esbuilds JavaScript-Konfigurationsobjekt eine eigene JSON-Struktur mit eigenen Konventionen mitbringt.
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": true
},
"target": "es2022",
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"keepClassNames": true
},
"module": {
"type": "commonjs"
},
"minify": false,
"sourceMaps": true
}
5. isolatedModules: die notwendige tsconfig-Absicherung
Weil esbuild und swc jede Datei isoliert transpilieren, ohne den Kontext anderer Dateien im Projekt zu kennen, müssen bestimmte TypeScript-Features vermieden werden, die auf dateiübergreifende Typinformationen angewiesen sind. Das wichtigste Beispiel ist const enum, das der vollständige tsc-Compiler zur Kompilierzeit über alle Dateien hinweg durch Literalwerte ersetzt, während ein isolierter Transpiler diese Ersetzung ohne den Gesamtkontext nicht zuverlässig durchführen kann.
Die Flag "isolatedModules": true in tsconfig.json aktiviert genau die Prüfungen, die sicherstellen, dass der TypeScript-Code auch ohne dateiübergreifenden Kontext korrekt transpiliert werden kann. Sie warnt zum Beispiel vor Re-Exporten von reinen Typen ohne das export type-Schlüsselwort, da ein isolierter Transpiler nicht unterscheiden kann, ob ein re-exportierter Bezeichner ein Typ oder ein Wert ist, und im Zweifel einen Laufzeit-Import erzeugen würde, der zur Laufzeit fehlschlägt. Jedes Projekt, das esbuild oder swc statt tsc für den eigentlichen Build einsetzt, sollte isolatedModules zwingend aktivieren.
// isolatedModules requires explicit "export type" for pure type re-exports
// WRONG: ambiguous for an isolated transpiler — is UserRole a type or a value?
export { UserRole } from './types';
// RIGHT: explicit type-only re-export, safe for esbuild and swc
export type { UserRole } from './types';
// const enum is NOT allowed with isolatedModules — needs cross-file inlining
// WRONG:
// const enum Status { Active, Inactive }
// RIGHT: regular enum works fine with isolated transpilers
enum Status {
Active = 'ACTIVE',
Inactive = 'INACTIVE',
}
6. Typprüfung getrennt ausführen: tsc --noEmit
Da esbuild und swc bewusst keine Typprüfung durchführen, bleibt tsc mit der Option --noEmit der Standardweg, um weiterhin vollständige Typsicherheit im Projekt zu gewährleisten, ohne dass tsc selbst die tatsächliche Build-Ausgabe erzeugt. Dieser Befehl durchläuft den kompletten Typprüfungsprozess, meldet alle Typfehler, erzeugt aber keine .js-Dateien, weil die eigentliche Transpilierung bereits vom schnelleren Tool übernommen wird.
In der Praxis läuft tsc --noEmit meist als separater CI-Schritt parallel zum eigentlichen Build, oder als Pre-Commit-Hook lokal beim Entwickler, während der schnelle Transpiler für den täglichen Entwicklungs-Loop und das eigentliche Deployment zuständig ist. Diese Aufteilung bedeutet, dass ein Typfehler den lokalen Entwicklungs-Server nicht sofort blockiert, was viele Teams als akzeptablen Kompromiss werten, solange der CI-Build den Merge in den Hauptbranch bei Typfehlern zuverlässig verhindert.
#!/usr/bin/env bash
# ci.sh — fast build with esbuild, type safety verified in parallel
set -euo pipefail
# Run both steps concurrently, fail the pipeline if either one fails
node build.mjs &
BUILD_PID=$!
npx tsc --noEmit &
TYPECHECK_PID=$!
wait "$BUILD_PID" && wait "$TYPECHECK_PID"
echo "[OK] Build and type check both passed"
7. Integration in Vite, Jest und Build-Pipelines
Vite nutzt esbuild bereits standardmäßig für die Transpilierung von TypeScript-Dateien während der Entwicklung, was einer der Hauptgründe für Vites deutlich kürzere Kaltstartzeiten im Vergleich zu Webpack-basierten Setups ist. Für Produktions-Builds wechselt Vite standardmäßig zu Rollup für das eigentliche Bundling, verwendet aber weiterhin esbuild für die reine TypeScript-zu-JavaScript-Transformation der einzelnen Module, bevor Rollup sie zusammenführt.
Für Testframeworks wie Jest ersetzt @swc/jest den standardmäßig langsameren ts-jest-Transformer und beschleunigt insbesondere Testsuiten mit vielen kleinen Testdateien erheblich, da jede Datei ohne den Overhead einer vollständigen Typprüfung transpiliert wird. Vitest nutzt intern ebenfalls esbuild für die Transformation, wodurch es von Haus aus eine der schnellsten Optionen für TypeScript-Testsuiten ist, ohne dass zusätzliche Konfiguration nötig wäre.
{
"scripts": {
"build": "node build.mjs",
"typecheck": "tsc --noEmit",
"test": "jest",
"ci": "npm run typecheck && npm run build && npm run test"
},
"devDependencies": {
"esbuild": "^0.23.0",
"@swc/core": "^1.7.0",
"@swc/jest": "^0.2.36",
"jest": "^29.7.0",
"typescript": "^5.6.2"
},
"jest": {
"transform": {
"^.+\\.tsx?$": ["@swc/jest"]
}
}
}
8. Grenzen: was esbuild und swc nicht können
Neben der fehlenden Typprüfung haben sowohl esbuild als auch swc weitere Einschränkungen, die für bestimmte TypeScript-Projekte relevant werden. Declaration Files (.d.ts) können beide Tools nicht erzeugen, da das Generieren korrekter Typdefinitionen eine vollständige Typprüfung voraussetzt. Projekte, die als npm-Bibliothek mit Typdefinitionen veröffentlicht werden, brauchen deshalb weiterhin einen separaten tsc-Lauf mit "declaration": true, selbst wenn der eigentliche JavaScript-Output von esbuild oder swc kommt.
Ein weiterer Unterschied betrifft experimentelle Decorators und Metadaten-Reflection, wie sie etwa NestJS oder TypeORM nutzen. swc unterstützt decoratorMetadata explizit über seine Konfiguration und ist für solche Frameworks meist die verlässlichere Wahl, während esbuild experimentelle Decorators zwar transpiliert, aber ohne die vollständige Metadaten-Emission, die manche Dependency-Injection-Frameworks zur Laufzeit benötigen. Vor dem Wechsel von tsc zu einem der beiden nativen Compiler lohnt sich deshalb ein Test der gesamten Decorator-Kette im jeweiligen Projekt.
9. esbuild, swc und tsc im direkten Vergleich
Die Wahl zwischen diesen drei Werkzeugen hängt von den konkreten Anforderungen des Projekts ab. Die folgende Tabelle vergleicht die wichtigsten Eigenschaften für TypeScript-Build-Setups.
| Eigenschaft | tsc | esbuild | swc |
|---|---|---|---|
| Build-Geschwindigkeit | Basis (1x) | 10 bis 20x schneller | 10 bis 20x schneller |
| Vollständige Typprüfung | Ja | Nein | Nein |
| .d.ts-Generierung | Ja | Nein | Nein |
| Plugin-System | Nein (Transformer-API) | Einfach, JS-basiert | Umfangreich, WASM-basiert |
| Decorator-Metadaten | Vollständig | Eingeschränkt | Vollständig unterstützt |
| Typische Nutzung | Typprüfung, Bibliotheks-Builds | Bundling, Vite, schnelle Skripte | Next.js, Jest-Transform, NestJS |
In der Praxis schließen sich diese drei Werkzeuge nicht gegenseitig aus, sondern ergänzen sich in einem gut konfigurierten TypeScript-Setup: esbuild oder swc übernehmen die eigentliche, häufig wiederholte Transformation während Entwicklung und Deployment, während tsc --noEmit als separater, seltener laufender Schritt die Typsicherheit garantiert. Für Bibliotheken mit veröffentlichten Typdefinitionen bleibt zusätzlich ein dedizierter tsc-Lauf mit aktivierter declaration-Option notwendig.
Mironsoft
Build-Performance, CI-Optimierung und TypeScript-Tooling
Minutenlange TypeScript-Builds spürbar beschleunigen?
Wir analysieren eure bestehende tsc-basierte Build-Pipeline, führen esbuild oder swc gezielt ein und richten eine saubere Trennung zwischen schnellem Build und separater Typprüfung ein.
Build-Audit
Messung aktueller Build-Zeiten und Identifikation der größten Engpässe
Compiler-Migration
Umstellung auf esbuild oder swc inklusive isolatedModules-Absicherung
CI-Integration
Separater, paralleler tsc --noEmit-Schritt für zuverlässige Typsicherheit
10. Zusammenfassung
esbuild und swc lösen ein reales Performance-Problem produktiver TypeScript-Projekte, indem sie die Transformation von TypeScript zu JavaScript von der aufwendigen Typprüfung trennen und in einer nativ kompilierten Sprache um Größenordnungen schneller ausführen als der JavaScript-basierte tsc. Diese Geschwindigkeit hat einen Preis: Beide Tools transpilieren Dateien isoliert, ohne dateiübergreifende Typprüfung, und benötigen deshalb "isolatedModules": true in tsconfig.json sowie einen separaten tsc --noEmit-Schritt für vollständige Typsicherheit.
esbuild eignet sich besonders für Bundling und schnelle Custom-Build-Skripte, während swc mit seinem umfangreicheren Plugin-System und der vollständigen Decorator-Metadaten-Unterstützung die bessere Wahl für Frameworks wie NestJS oder Next.js ist. Beide Tools ersetzen tsc nicht vollständig, sondern ergänzen ihn: Der schnelle Compiler übernimmt den täglichen Build, tsc bleibt für Typprüfung und Declaration-File-Generierung zuständig.
TypeScript mit esbuild und swc — Das Wichtigste auf einen Blick
Transpilieren statt Typprüfen
esbuild und swc entfernen Typannotationen isoliert pro Datei, ohne cross-file Typprüfung durchzuführen.
isolatedModules Pflicht
Verhindert const enum und mehrdeutige Type-Re-Exporte, die isolierte Transpiler nicht sicher auflösen können.
tsc --noEmit separat
Läuft als eigener CI-Schritt für vollständige Typsicherheit, ohne den schnellen Build zu blockieren.
Werkzeugwahl
esbuild für Bundling und Vite, swc für Plugin-Bedarf und Decorator-lastige Frameworks wie NestJS.