TypeScript mit esbuild und swc: Compiler-Alternativen im Build-Vergleich
AI generated
<T>
type
TypeScript · esbuild · swc · Build-Performance
TypeScript mit esbuild und swc
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.

18 Min. Lesezeit isolatedModules · tsc --noEmit · esbuild · @swc/core TypeScript 5.x · Node.js 20/22

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.

11. FAQ: TypeScript mit esbuild und swc

1Prüfen esbuild/swc Typen?
Nein, beide entfernen Typannotationen isoliert. Vollständige Typsicherheit braucht separaten tsc --noEmit Lauf.
2Warum isolatedModules Pflicht?
Verhindert Features wie const enum, die dateiübergreifenden Kontext benötigen, den isolierte Transpiler nicht haben.
3Erzeugen sie .d.ts-Dateien?
Nein, dafür ist weiterhin ein separater tsc-Lauf mit declaration: true nötig.
4Wie viel schneller sind sie?
Zehn- bis zwanzigfach, dank nativer Implementierung und entfallender Typprüfung.
5Wann swc statt esbuild?
Bei Decorator-Metadaten-Bedarf wie NestJS oder wenn ein umfangreicheres Plugin-System nötig ist.
6Blockiert Typfehler den Dev-Server?
Nein bei esbuild, aber CI sollte über einen separaten tsc-Schritt trotzdem absichern.
7Vite mit esbuild in Produktion?
esbuild transformiert Module, Rollup übernimmt standardmäßig das finale Bundling.
8@swc/jest schneller als ts-jest?
Ja, deutlich, da ts-jest bei jedem Lauf echte Typprüfung durchführt.
9const enum mit esbuild/swc?
Nicht möglich, isolatedModules meldet einen Fehler, reguläre enum nutzen.
10tsc komplett entfernen?
Nein, bleibt für Typprüfung und Declaration Files unverzichtbar.