TypeScript-Projekte wachsen. Dateien vervielfachen sich. Abhängigkeiten verstricken sich. Und schließlich stößt Ihr Build an eine Wand, die nichts mit der Komplexität Ihrer Logik zu tun hat, sondern allein damit, dass der Compiler das gesamte Universum lesen muss, bevor er eine einzige Deklarationsdatei schreiben kann.

TypeScript 6.0 adressiert dies mit isolatedDeclarations. Das Feature überdenkt die Entstehung von .d.ts-Dateien. Anstatt die Erzeugung von Deklarationen an die vollständige Type-Checking-Pipeline zu binden, ermöglicht es dem Compiler, diese Dateien zu erstellen, indem er jede Quelldatei isoliert betrachtet. Das Ergebnis ist ein Build-Prozess, der parallel über Tausende von Dateien hinweg laufen kann, anstatt sich mühsam Schritt für Schritt durch Ihren Abhängigkeitsgraphen zu arbeiten.

Der wahre Flaschenhals

Derzeit ist das Erzeugen von Deklarationsdateien eine serielle Operation. Wenn Sie --declaration aktivieren und den Compiler ausführen, kann TypeScript keine .d.ts-Datei für ein bestimmtes Modul erstellen, bis es jeden Typ, den dieses Modul verwendet, vollständig verstanden hat. Wenn utils.ts Typen aus types.ts importiert und types.ts etwas aus api.ts bezieht, muss der Compiler diese Kette auflösen, bevor er beschreiben kann, was utils.ts exportiert.

In einem großen Monorepo ist diese Kaskade brutal. Eine einzige Datei nahe der Wurzel Ihres Import-Graphen kann die Erzeugung von Deklarationen für hunderte nachgelagerte Dateien blockieren. Ihre CPU hat acht Kerne, aber sieben davon liegen brach, während TypeScript mühsam die Form jedes Interfaces über Paketgrenzen hinweg rekonstruiert. Der Compiler leistet zwar notwendige Arbeit, aber die Kopplung zwischen Type Checking und der Erzeugung von Deklarationen bedeutet, dass Sie den vollen Preis für die dateiübergreifende Analyse zahlen, selbst wenn Sie nur die Typen der öffentlichen Schnittstelle auf die Festplatte schreiben wollen.

Wie IsolatedDeclarations die Regeln ändert

isolatedDeclarations bricht diese Kopplung auf. Wenn das Flag aktiviert ist, verspricht der Compiler, eine .d.ts-Datei für eine Quelldatei zu erstellen, ohne eine andere Datei zu fragen, was etwas bedeutet. Dies geschieht durch einen einfachen Vertrag: Jedes exportierte Symbol muss an der Stelle, an der es deklariert wird, eine explizite, sichtbare Typannotation tragen.

Wenn der Compiler den vollständigen Typ direkt in der Quelle sehen kann, muss er keine Typinferenz durchführen. Er muss keine Imports verfolgen. Er muss nicht wissen, ob der Bezeichner User in einer anderen Datei ein Interface, ein Type Alias oder eine Klasse ist. Er gibt einfach genau das aus, was Sie geschrieben haben.

Das bedeutet, dass Datei A und Datei B ihre Deklarationen gleichzeitig generieren können. Ein Build-Orchestrator kann jede Datei an einen separaten Thread übergeben. Schnelle Transpiler, die die .d.ts-Generierung zuvor übersprungen haben, weil ihnen ein vollständiger Type Checker fehlte, können nun ebenfalls Deklarationsdateien erzeugen, da die Arbeit rein syntaktischer Natur wird.

Der Kompromiss: Schreiben Sie es auf

Geschwindigkeit ist nicht umsonst. Sie müssen aufhören, sich bei allem, was Sie exportieren, auf die Typinferenz zu verlassen. Jede öffentliche Funktion, Klasse, Variable und Konstante benötigt eine explizit ausgeschriebene Typangabe. Wenn TypeScript den Typ berechnen muss, indem es eine Return-Anweisung betrachtet oder ein generisches Argument auflöst, wird isolatedDeclarations einen Fehler ausgeben.

So sieht das in der Praxis aus. Ohne das Flag könnten Sie schreiben:

export function fetchUser(id: number) {
  return fetch(`/users/${id}`).then(r => r.json());
}

TypeScript leitet den Rückgabetyp ab, indem es fetch untersucht, dann Promise.prototype.then und schließlich die anonyme Funktion, die r.json() zurückgibt. Um eine .d.ts zu erstellen, muss der Compiler all diese Analysen durchführen.

Wenn isolatedDeclarations aktiviert ist, müssen Sie den Export annotieren:

interface User {
  id: number;
  email: string;
}

export function fetchUser(id: number): Promise<User> {
  return fetch(`/users/${id}`).then(r => r.json());
}

Jetzt sieht der Compiler sofort Promise<User>. Er erstellt die Deklaration und macht weiter.

Diese Regel gilt allgemein. Exportierte Arrays benötigen explizite Typen, anstatt dass diese aus ihren Elementen abgeleitet werden. Exportierte Objekte benötigen explizite Typannotationen, wenn ihre Struktur für die Konsumenten wichtig ist. Generische Funktionen benötigen sichtbare Rückgabetypen und Constraints an der Deklarationsstelle. Sie können das Ergebnis eines komplexen Mapped Types nicht exportieren, ohne ihm einen benannten Type Alias zu geben, der vollständig ausgeschrieben ist.

Der Vorteil ist, dass Ihre öffentliche API selbstdokumentierend wird. Konsumenten – und der Compiler – müssen Ihre Absicht nicht mehr aus den Implementierungsdetails zurückentwickeln. Die Typen sind ein bewusster Vertrag.

Wo die Zeit bleibt

In einer großen Codebasis ist die Auswirkung unmittelbar spürbar. Build-Zeiten, die sich über Minuten hinziehen, können auf Sekunden sinken, weil die Erzeugung von Deklarationen nicht mehr der limitierende Faktor ist. Jede Datei wird unabhängig emittiert, sodass der Prozess mit der Anzahl der verfügbaren Kerne skaliert und nicht mit der Tiefe Ihres Import-Graphen.

Dies ändert auch, welche Tools Sie verwenden können. Transpiler wie esbuild und swc sind bereits blitzschnell darin, TypeScript in JavaScript umzuwandeln, aber viele Teams führen tsc immer noch separat aus, nur um .d.ts-Dateien zu erzeugen. Mit isolatedDeclarations können diese schnellen Tools beide Aufgaben übernehmen. Sie müssen nicht das gesamte Typsystem von TypeScript replizieren, um Deklarationen zu generieren; sie müssen lediglich die Syntax parsen und die von Ihnen bereitgestellten expliziten Typen kopieren. Das macht End-to-End-TypeScript-Builds mit alternativen Toolchains weitaus praktikabler.

Verteilte und inkrementelle Builds werden ebenfalls einfacher. In der Continuous Integration kann ein Remote-Cache oder ein Sharded Build Deklarationen für ein Paket ausgeben, ohne zuvor dessen gesamten transitiven Abhängigkeitsgraphen herunterzuladen. Wenn die Typen in der Quelle explizit sind, verfügt der Build-Shard über alles, was er benötigt.

Was gleich bleibt

Die Einschränkung gilt nur für Exports. Innerhalb eines Moduls läuft alles wie gewohnt weiter. Lokale Variablen, private Klassenmitglieder und nicht exportierte Helferfunktionen können sich weiterhin auf die vollständige Typinferenz verlassen. TypeScript wird den Typ einer Schleifenvariable oder eines Closure-Parameters ohne Murren inferieren.

export function calculateTotal(items: Item[]): number {
  // Local variable: inference is fine
  const taxRate = 0.08;
  
  // Private class member inside a local class: inference is fine
  class Helper {
    private cache = new Map();
  }
  
  return items.reduce((sum, item) => sum + item.price * (1 + taxRate), 0);
}

Nur die exportierte Funktionssignatur benötigt eine Annotation. Die interne Mechanik bleibt flexibel und ausdrucksstark. Dies hält den Aufwand bei der Erstellung erträglich. Sie wechseln nicht überall zu einem vollständig expliziten Stil; Sie formalisieren lediglich den Vertrag an der Grenze jedes Moduls.

Ist es das Richtige für Ihre Codebasis?

Die Einführung von isolatedDeclarations verschiebt den Ort, an dem Sie Ihre Zeit verbringen. Sie investieren ein paar zusätzliche Tastenanschläge beim Schreiben eines Exports, und im Gegenzug zahlen Sie keine „Zinsen“ mehr bei jedem Build. Für Bibliotheksautoren ist dies oft ein leicht zu vermittelndes Argument. Öffentliche APIs sollten ohnehin wahrscheinlich annotiert werden. Für Anwendungsentwickler, die in einem geschlossenen Monorepo arbeiten, können sich die Vorabkosten wie unnötiger Aufwand anfühlen. Aber wenn Ihr Team die Build-Zeit in Kaffeepausen misst, wird der Tausch schnell attraktiv.

Sie können es inkrementell einführen. Aktivieren Sie das Flag, führen Sie den Compiler aus und beheben Sie die Fehler, die bei exportierten Symbolen auftreten. Die Fehlermeldungen sagen Ihnen genau, welche öffentlich zugänglichen Typen implizit sind. Beheben Sie diese, lassen Sie das Interne in Ruhe und beobachten Sie, wie sich Ihr Deklarationsschritt beschleunigt.

Eines ist zu beachten: Dieses Flag macht den TypeScript-Typechecker selbst nicht schneller. Wenn Sie schnelleres Feedback in Ihrem Editor oder schnellere tsc --noEmit-Durchläufe wünschen, benötigen Sie weiterhin Project References, eine striktere Dateieinbindung oder andere architektonische Korrekturen. isolatedDeclarations zielt spezifisch auf die Erzeugung von .d.ts-Dateien ab. Es ist eine Build-Optimierung, keine Optimierung der Typprüfung.

Das eigentliche Fazit

isolatedDeclarations verlangt von Ihnen, Ihre öffentlichen Typen als erstklassige Artefakte zu behandeln. Hören Sie auf, den Compiler sie ableiten zu lassen. Schreiben Sie sie auf. Sobald Sie das tun, hört der Compiler auf, jedes Mal Ihren gesamten Abhängigkeitsgraphen zu durchforsten, wenn er eine Deklarationsdatei generieren muss. Er emittiert parallel, Tools wie esbuild und swc übernehmen vollständige TypeScript-Workflows, und Ihre Monorepo-Builds laufen nicht mehr so schleppend.

Die Kosten verschieben sich von der Build-Zeit zur Authoring-Zeit. Für die meisten wachsenden Teams ist das ein lohnenswerter Tausch.