Konfiguration ohne Ratespiel

tsconfig: zehn Schalter, die im Alltag etwas verändern

Zielversion, Modulauflösung und Pfadaliasse tragen die meiste Wirkung. Der Rest der Datei ist Voreinstellung, und genau da liegen die Überraschungen nach einem Upgrade.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Seit 1997 am Markt Kleine Gruppen Präsenz und Live-Online Zertifizierte Trainer
Worum es geht

Eine kopierte tsconfig erklärt niemandem, warum das Projekt sich so verhält

Die meisten Konfigurationsdateien in Projekten sind gewachsen: ein Block aus einer Vorlage, zwei Optionen aus einem Blogeintrag, drei aus einer Fehlermeldung, die damit verschwand. Das funktioniert so lange, bis jemand einen Import nicht auflösen kann, der Build in der Pipeline etwas meldet, das im Editor nie zu sehen war, oder ein Upgrade eine Option streicht, die seit Jahren mitgeschleppt wird.

Teuer ist dabei nicht die Suche nach dem richtigen Schalter, sondern die Zeit, in der niemand dem Ergebnis traut. Wenn der Editor grün ist und die Pipeline rot, fängt das Team an, Fehler lokal nachzustellen, statt sie zu beheben. Bei mehreren Paketen in einem Repository vervielfacht sich das, weil jedes Paket seine eigene Datei hat und die geerbten Werte nicht mehr überblickbar sind.

Dazu kommt eine Verwechslung, die sich hartnäckig hält: Die tsconfig steuert die Typprüfung und, wenn du willst, die Übersetzung durch tsc. Der Bundler übersetzt in aller Regel selbst, mit esbuild oder swc, und der prüft überhaupt keine Typen. Wer das nicht trennt, sucht Fehler an der falschen Stelle.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Der Aufbau im Detail

Die Datei von oben nach unten gelesen

Die folgende Zeile ist keine gültige Konfiguration, sondern das Gerüst, auf das sich fast jede reale tsconfig zurückführen lässt. Die sechs markierten Stellen sind die, an denen Projekte sich tatsächlich unterscheiden.

Der Aufbau

tsconfig.json = { compilerOptions: { target, lib, moduleResolution, paths, noEmit }, include, references }
  1. 01 Die Sprachstufe der Ausgabe target

    Legt fest, auf welchen Stand JavaScript heruntergebrochen wird. Seit TypeScript 6.0 lautet die Voreinstellung es2025. target es5 ist dort abgekündigt und wird von TypeScript 7.0 nicht mehr angenommen, ein Ersatz dafür ist nicht vorgesehen. In Projekten mit Bundler entscheidet ohnehin meist der Bundler über die Ausgabe, dann wirkt target vor allem darauf, welche Sprachmerkmale der Compiler als gültig ansieht.

  2. 02 Die bekannten Laufzeittypen lib

    Bestimmt, welche eingebauten Typen sichtbar sind, etwa dom für Browser-APIs oder es2023 für neuere Array-Methoden. lib ist eine Behauptung über die Umgebung, keine Übersetzung: Wer hier eine neue Stufe einträgt, bekommt die Methode im Editor angeboten und im alten Browser einen Laufzeitfehler.

  3. 03 Wie Importe gefunden werden moduleResolution

    bundler passt zu Vite, webpack und Co, weil dort Dateiendungen weggelassen werden dürfen. nodenext bildet nach, wie Node selbst auflöst, inklusive der Pflicht, in ESM-Dateien die Endung .js mitzuschreiben. Die alten Werte node10 und classic sind in TypeScript 7 ein harter Fehler.

  4. 04 Aliasse auf eigene Verzeichnisse paths

    Bildet Importkürzel wie @/lib auf echte Ordner ab. Seit TypeScript 6 ist baseUrl abgekündigt, in TypeScript 7 löst es einen Fehler aus, deshalb werden die Muster relativ zur tsconfig angegeben. Wichtig bleibt: Diese Zuordnung kennt nur der Compiler.

  5. 05 Prüfen ohne Ausgabe noEmit

    Schaltet die Dateierzeugung ab, sodass tsc nur noch prüft. Das ist der Normalfall, wenn ein Bundler die Übersetzung übernimmt, und macht den Prüfschritt in der Pipeline zu einem eigenen Befehl, dessen Fehlschlag eindeutig ist.

  6. 06 Mehrere Teilprojekte references

    Verweist auf andere tsconfig-Dateien mit composite: true. Damit prüft und baut TypeScript in der richtigen Reihenfolge und kann Ergebnisse zwischenspeichern. Das lohnt sich ab dem Punkt, an dem ein Repository mehrere Pakete enthält, die voneinander abhängen.

Wenn es nicht funktioniert

Das siehst du

Der Editor zeigt einen Fehler, den der Build nicht kennt, oder umgekehrt.

Warum

Editor und Pipeline arbeiten mit unterschiedlichen TypeScript-Versionen. Der Editor bringt eine eigene mit, die Pipeline nimmt die aus den Projektabhängigkeiten.

Was hilft

In Visual Studio Code über die Befehlspalette und TypeScript: Select TypeScript Version auf die Version aus dem Projekt umstellen und die Auswahl in den Arbeitsbereichseinstellungen festhalten, damit alle im Team dieselbe nutzen.

Das siehst du

Ein Import mit dem Alias @/ funktioniert im Editor, der Build bricht mit einem nicht gefundenen Modul ab.

Warum

paths steht nur in der tsconfig. Der Bundler und der Testläufer haben ihre eigene Auflösung und wissen nichts davon.

Was hilft

Dieselbe Zuordnung in der Bundler-Konfiguration hinterlegen oder ein Plugin einsetzen, das die tsconfig ausliest. Wenn tsc selbst ausgibt, werden die Aliasse in der Ausgabe nicht ersetzt, dafür braucht es einen zusätzlichen Schritt.

Das siehst du

Eine Datei wird nie geprüft, obwohl sie offensichtlich Fehler enthält.

Warum

Sie liegt außerhalb von include oder wird von exclude erfasst. Der Editor behandelt solche Dateien mit Standardwerten, die Pipeline sieht sie gar nicht.

Was hilft

Den Geltungsbereich prüfen und im Zweifel den Compiler fragen: tsc mit der Option showConfig gibt die tatsächlich wirksame Konfiguration aus, listFiles zeigt jede einbezogene Datei.

Das siehst du

Nach dem Upgrade meldet der Compiler Optionen als abgekündigt oder als Fehler, die jahrelang funktioniert haben.

Warum

TypeScript 6.0 vom 23. März 2026 kündigt baseUrl, target es5, downlevelIteration, moduleResolution node und classic sowie die Modularten amd, umd und systemjs ab und meldet sie als Warnung. TypeScript 7.0 vom 8. Juli 2026 nimmt diese Werte nicht mehr an und bricht mit einem Fehler ab.

Was hilft

Vor dem Sprung auf 7 einen Zwischenschritt über 6.0 machen und dort ignoreDeprecations auf 6.0 setzen, solange umgebaut wird. In dieser Zeit paths ohne baseUrl schreiben, moduleResolution auf bundler oder nodenext stellen und target auf eine Stufe ab es2015 heben. Danach fällt der Schalter wieder weg.

Das siehst du

Das Projekt läuft im Browser, aber eine Methode fehlt zur Laufzeit, obwohl der Compiler zufrieden war.

Warum

lib beschreibt nur, was der Compiler als vorhanden annimmt. TypeScript liefert keine Polyfills und prüft auch nicht, was der Zielbrowser wirklich kann.

Was hilft

lib auf den Stand setzen, den die Zielumgebung sicher beherrscht, und alles Neuere über ein Polyfill oder eine Browserliste im Bundler absichern.

Vier Fragen, und die halbe Datei steht

  1. 01 Wo läuft der Code, im Browser oder in Node? Das setzt target, lib und module.
  2. 02 Wer löst Importe auf, ein Bundler oder Node? Danach richtet sich moduleResolution.
  3. 03 Wer prüft, wer übersetzt? Bundler übersetzt, tsc prüft mit noEmit.
  4. 04 Welche Dateien gehören dazu? include und exclude ziehen die Grenze des Projekts.
  5. 05 Verlässt Code das Haus als Paket? Erst dann lohnen declaration und Projektverweise.
Was du mitnimmst

Was du nach diesem Text an der Datei entscheiden kannst

Die Konfiguration wird handhabbar, sobald du sie in vier Fragen zerlegst: Für welche Laufzeit wird übersetzt, wie werden Importe gefunden, wer prüft und wer übersetzt, und welcher Teil des Projekts gehört überhaupt zu dieser Datei. Alles andere ordnet sich darunter ein.

target und lib auseinanderhalten

target bestimmt, in welche Sprachstufe TypeScript übersetzt, lib bestimmt, welche eingebauten Typen bekannt sind. Ein hohes lib ohne passende Laufzeit heißt: Der Compiler kennt die Methode, im Browser fehlt sie trotzdem. Polyfills liefert TypeScript nicht mit.

Die Modulauflösung passend zur Umgebung setzen

Für Anwendungen, die durch einen Bundler laufen, ist bundler die richtige Einstellung, für Code, der direkt in Node läuft, nodenext. Die alten Werte node10 und classic gibt es seit TypeScript 7 nicht mehr.

Pfadaliasse an zwei Stellen eintragen

paths kennt nur der Compiler. Der Bundler und der Testläufer brauchen dieselbe Zuordnung noch einmal in ihrer eigenen Konfiguration, sonst löst der Editor den Import auf und der Build bricht ab.

Prüfen und Übersetzen trennen

In einem Projekt mit Bundler steht noEmit auf true, und die Typprüfung läuft als eigener Schritt mit tsc. Damit ist auch klar, welcher Befehl in der Pipeline fehlschlagen muss, wenn ein Typfehler durchrutscht.

Den Geltungsbereich der Datei kennen

include und exclude entscheiden, welche Dateien zum Projekt gehören. Was außerhalb liegt, wird im Editor mit Standardwerten behandelt und in der Pipeline womöglich gar nicht geprüft, was die häufigste Ursache für abweichende Urteile ist.

Deklarationsdateien nur erzeugen, wenn jemand sie liest

declaration und declarationMap braucht, wer ein Paket veröffentlicht oder in einem Monorepo über Projektverweise arbeitet. In einer Anwendung, die am Ende nur ausgeliefert wird, kosten sie Bauzeit ohne Gegenwert.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt

target und lib sind zwei verschiedene Versprechen

target sagt, wie TypeScript deinen Code umschreibt. Steht dort eine niedrige Stufe, werden neuere Sprachmerkmale in älteres JavaScript übersetzt, etwa optionale Verkettung oder Klassenfelder. lib sagt dagegen, welche Bibliotheksfunktionen als bekannt gelten. Beides hat nichts miteinander zu tun: Ein Aufruf von Array.prototype.at wird nicht übersetzt, sondern entweder existiert er in der Laufzeit oder nicht.

In Projekten mit Bundler ist target oft die weniger wichtige Angabe, weil der Bundler selbst eine Zielliste führt und die Ausgabe erzeugt. Wirksam bleibt target trotzdem, denn der Compiler leitet daraus auch ab, welche Sprachmerkmale er überhaupt erlaubt. Wenn du also nicht sicher bist, warum eine Syntax bemängelt wird, obwohl der Bundler sie versteht, lohnt der Blick auf diese Zeile.

Die Voreinstellungen haben sich mit TypeScript 6 verschoben: target liegt jetzt bei es2025, module bei esnext, strict steht auf true und types ist ein leeres Array, sodass nicht mehr automatisch alle installierten Typpakete geladen werden. Wer nach einem Upgrade plötzlich fehlende globale Typen sieht, findet die Ursache meist genau dort und trägt die benötigten Pakete explizit ein.

Modulauflösung: bundler, nodenext und der Rest der Geschichte

moduleResolution beantwortet die Frage, wie aus einem Importpfad eine Datei wird. bundler bildet nach, was Vite, webpack, Rollup und esbuild tun: Endungen dürfen fehlen, Ordner mit index-Datei funktionieren, und die exports-Angaben aus der package.json werden ausgewertet. nodenext bildet Node nach, mit dem entscheidenden Unterschied, dass in echten ES-Modulen die Dateiendung im Import stehen muss, und zwar als .js, auch wenn die Quelldatei .ts heißt.

Aus dieser Regel entsteht die häufigste Verwirrung in Projekten, die serverseitigen Code enthalten: Ein Import ohne Endung ist unter bundler korrekt und unter nodenext falsch. Wenn ein Repository beides enthält, gehört das in getrennte Konfigurationen, nicht in eine gemeinsame mit Kompromisswerten.

Zwei kleinere Schalter gehören in dieselbe Ecke. isolatedModules stellt sicher, dass jede Datei für sich übersetzbar ist, was genau die Voraussetzung ist, unter der esbuild und swc arbeiten. verbatimModuleSyntax macht sichtbar, welche Importe in der Ausgabe stehen bleiben: Was nur Typen holt, muss mit import type geschrieben werden, sonst bleibt der Import erhalten und lädt zur Laufzeit ein Modul, das niemand braucht.

Pfadaliasse funktionieren nur, wenn alle Werkzeuge mitziehen

paths ist eine reine Compilerangabe. Sie sorgt dafür, dass der Editor einen Import wie @/lib/format auflöst und dir die richtigen Typen anzeigt. Der Bundler weiß davon nichts, der Testläufer ebenfalls nicht, und tsc ersetzt die Aliasse beim Erzeugen von JavaScript auch nicht durch echte Pfade. Wer nur die tsconfig anfasst, bekommt genau die Situation, in der die Entwicklungsumgebung zufrieden ist und der Build abbricht.

Sauber wird das, wenn dieselbe Zuordnung im Bundler steht, entweder von Hand oder über ein Plugin, das die tsconfig ausliest. Für Bibliotheken, die tsc selbst ausgibt, kommt ein Nachbearbeitungsschritt dazu, der die Aliasse in der Ausgabe auflöst. Vor dem Einbau lohnt die ehrliche Frage, ob es die Aliasse braucht: Bei flachen Projekten sind relative Importe kürzer als die Erklärung, warum drei Werkzeuge dieselbe Tabelle führen müssen.

Seit TypeScript 6 ist baseUrl abgekündigt und in TypeScript 7 ein Fehler. Muster in paths werden deshalb relativ zur Konfigurationsdatei angegeben. Falls in deinem Projekt noch Importe existieren, die ohne Alias funktionieren, weil baseUrl auf das Quellverzeichnis zeigte, gehören diese Importe vor dem Upgrade umgeschrieben, denn sie brechen sonst geschlossen weg.

Warum Editor und Build unterschiedlich urteilen

Es gibt vier Ursachen, und drei davon haben nichts mit der Konfiguration zu tun. Die erste ist die Version: Der Editor bringt eine eigene TypeScript-Fassung mit, die Pipeline nimmt die aus node_modules. Umstellen lässt sich das in Visual Studio Code über die Befehlspalette mit TypeScript: Select TypeScript Version, und die Auswahl gehört in die Arbeitsbereichseinstellungen, damit sie für alle gilt.

Die zweite ist der Geltungsbereich. Der Editor prüft die Datei, die gerade offen ist, notfalls mit Standardwerten, wenn sie zu keinem Projekt gehört. Die Pipeline prüft, was include beschreibt. Vorlagen wie die von Vite verteilen die Einstellungen bewusst auf mehrere Dateien, etwa eine für den Anwendungscode und eine für Konfigurationsdateien, die in Node laufen. Wer nur eine davon anpasst, ändert für die andere Hälfte des Projekts nichts.

Die dritte ist skipLibCheck. Steht die Option auf true, werden Typfehler in fremden Deklarationsdateien übergangen. Das ist meistens richtig, verdeckt aber echte Konflikte zwischen zwei Paketen, die verschiedene Versionen derselben Typen mitbringen. Die vierte betrifft aktuell den Sprung auf TypeScript 7: Für Vue, Svelte, Astro, MDX und Angular-Templates empfiehlt das TypeScript-Team, vorerst bei 6.0 zu bleiben, weil diesen Werkzeugen noch die stabile Programmierschnittstelle fehlt. In einem solchen Projekt kann der Editor also mit gutem Grund eine andere Version fahren als der Rest der Kette.

Wer prüft, wer übersetzt, und wo das in der Pipeline steht

Vite, Next.js und die meisten anderen Werkzeuge übersetzen TypeScript mit esbuild oder swc. Diese Übersetzer entfernen die Typen und geben JavaScript aus, ohne irgendetwas zu prüfen. Das ist der Grund, warum die Anwendung im Entwicklungsmodus fröhlich läuft, während der Code Typfehler enthält. Die Prüfung ist ein eigener Vorgang, der im Editor mitläuft und in der Pipeline explizit aufgerufen werden muss.

Daraus folgt eine einfache Aufteilung: noEmit auf true, ein Skript mit tsc für die Prüfung, ein Skript für den Build. In Projekten mit Vue, Svelte oder Astro übernimmt das jeweilige Prüfwerkzeug diese Rolle, weil in Einzeldateikomponenten auch Vorlagen geprüft werden müssen. Wichtig ist nur, dass genau ein Befehl in der Pipeline rot wird, wenn ein Typfehler existiert, und dass alle im Team wissen, welcher das ist.

Wenn die Prüfung zu lange dauert, hilft in großen Repositories die Aufteilung in Teilprojekte mit references und composite, weil TypeScript dann nur neu prüft, was sich geändert hat. Seit TypeScript 7 ist der Compiler selbst deutlich schneller, weil er nach Go portiert wurde. Das Team gibt für vollständige Durchläufe achtfache bis zwölffache Beschleunigung gegenüber Version 6 an.

Dazu passende Kurse

Bei cmt gehst du an einem eigenen Projekt durch, welche Schalter der Konfigurationsdatei zählen , statt eine fremde Vorlage weiterzureichen.

Wer Module, Auflösung und Sprachstufen wirklich verstehen will, kommt an der Ebene darunter nicht vorbei, und dafür gibt es die JavaScript-Grundlagen unter der Typprüfung .

Wissen prüfen

Wie sicher bist du beim Thema wirklich?

Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.

Ein sehr gutes, praxisorientiertes und nachhaltiges Seminar. So sollte es immer sein. Vielen Dank.
LimeSurvey - Anwendertraining Teil 1 (Grundlagen)
Die Schulung ist gerade für Einsteiger oder Entwickler*innen mit eingestaubtem Basiswissen sehr hilfreich.
Vue.js 3 Grundkurs
Ich bin sehr glücklich Franz als Trainer gehabt zu haben. Er hat offensichtlich unglaublich viel Wissen zu den Themen, und schafft es, dieses auch interaktiv und verständlich weiterzugeben.
React Komplettausbildung

Häufige Fragen

Brauche ich mehrere tsconfig-Dateien in einem Projekt?
Sobald in einem Repository Code für verschiedene Laufzeiten liegt, ja. Anwendungscode für den Browser und Konfigurationsdateien, die in Node laufen, brauchen unterschiedliche Werte bei lib und moduleResolution. Der übliche Aufbau ist eine schlanke Basisdatei, die nur auf die Teilprojekte verweist, und je eine Datei pro Bereich, die über extends erbt.
Was macht skipLibCheck genau, und darf das an bleiben?
Die Option überspringt die Prüfung fremder Deklarationsdateien in node_modules. In den allermeisten Projekten bleibt sie an, weil du Fehler in fremden Typen ohnehin nicht reparierst und die Prüfung sonst spürbar länger dauert. Wenn du allerdings merkwürdige Konflikte zwischen zwei Paketen jagst, schalte sie zum Suchen einmal ab, denn dann werden genau diese Widersprüche sichtbar.
Wie finde ich heraus, welche Werte wirklich gelten?
Mit tsc und der Option showConfig gibt der Compiler die aufgelöste Konfiguration aus, inklusive alles Geerbten. listFiles zeigt zusätzlich jede Datei, die er einbezieht. Das beantwortet die beiden Fragen, die bei Abweichungen zwischen Editor und Build fast immer dahinterstecken: Welcher Wert gilt hier, und gehört diese Datei überhaupt zum Projekt?
Muss ich sofort auf TypeScript 7 wechseln?
Nein, aber der Weg dorthin führt über 6.0. Dort tauchen dieselben geänderten Voreinstellungen auf, während abgekündigte Optionen noch mit einem Schalter benutzbar bleiben. Wenn dein Projekt Vue, Svelte, Astro oder Angular-Templates enthält, prüf vorher, ob die eingesetzten Werkzeuge Version 7 unterstützen, denn das Team empfiehlt für diese Fälle vorerst ausdrücklich 6.0.
Persönlich für dich da

Deine Ansprechpartner

Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.

Yves Hoppe

Yves Hoppe

Weiterbildung & Beratung

Hilft dir, aus dem Webentwicklung-Programm den passenden Kurs für deinen Stand zu finden.

Norbert Jansen

Norbert Jansen

Beratung & Inhouse

Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.

Eine Konfiguration, die im ganzen Team dasselbe bedeutet

In den TypeScript-Angeboten von cmt richtest du eine Konfiguration an einem echten Projekt ein und siehst dabei, an welchen Stellen Editor, Bundler und Pipeline auseinanderlaufen.