Declaration Maps für bessere Entwicklererfahrung nutzen
AI generated
<T>
type
TypeScript · Declaration Maps · Tooling · IDE
Declaration Maps für bessere Entwicklererfahrung nutzen
von der .d.ts direkt zurück zum Original-Code

Wer Strg-Klick auf einen importierten Typ macht und in einer generierten .d.ts-Datei landet statt im eigenen Quellcode, verliert wertvolle Zeit beim Debugging. Declaration Maps schließen genau diese Lücke und machen aus einer TypeScript-Bibliothek eine Bibliothek, die sich in der IDE anfühlt, als läge der Quellcode direkt im eigenen Projekt.

16 Min. Lesezeit declarationMap · d.ts.map · Source Maps · Monorepo TypeScript 5.x · VS Code · WebStorm

1. Das Problem: Strg-Klick landet in der generierten .d.ts

Wer eine TypeScript-Bibliothek in einem fremden Projekt einsetzt und per Strg-Klick oder Cmd-Klick zur Definition eines importierten Typs springt, landet standardmäßig in der generierten .d.ts-Datei im dist/-Ordner der Bibliothek. Diese Datei enthält reine Typsignaturen ohne Implementierung, ohne Kommentare zum Kontext und ohne die ursprüngliche Struktur des Quellcodes. Für einfache Nachschlagevorgänge reicht das, für tiefere Recherche in der eigenen Bibliothek oder beim Debuggen eines Type-Level-Problems ist es unzureichend.

Besonders unangenehm wird es, wenn Entwickler an der eigenen TypeScript-Bibliothek arbeiten und zwischen mehreren Paketen eines Monorepos wechseln. Ohne Declaration Maps springt die IDE bei jedem Import eines internen Pakets in die kompilierte .d.ts, obwohl der Quellcode nur einen Ordner entfernt liegt. Das kostet bei jedem einzelnen Navigationsschritt Zeit und unterbricht den Denkfluss, gerade in großen Codebasen mit vielen internen Abhängigkeiten.

2. Was Declaration Maps technisch sind

Declaration Maps funktionieren nach demselben Prinzip wie klassische JavaScript-Source-Maps, nur eine Ebene höher: Statt kompiliertes JavaScript auf den ursprünglichen TypeScript-Quellcode abzubilden, bilden sie generierte .d.ts-Dateien auf den .ts-Quellcode ab, aus dem sie erzeugt wurden. Für jede index.d.ts erzeugt der Compiler eine begleitende index.d.ts.map-Datei, die Positionsinformationen im JSON-Format enthält und über einen Kommentar am Ende der .d.ts-Datei referenziert wird.

Diese Zuordnung erlaubt es IDEs, beim Klick auf "Go to Definition" nicht bei der Typsignatur stehen zu bleiben, sondern automatisch einen weiteren Sprung zur eigentlichen Implementierung im Quellcode zu machen. Das Ergebnis: Entwickler navigieren durch eine TypeScript-Bibliothek genauso, wie sie durch den eigenen Anwendungscode navigieren würden, komplett transparent, ohne den Unterschied zwischen kompiliertem Paket und Quellcode überhaupt wahrzunehmen.

3. declarationMap in tsconfig.json aktivieren

Die Aktivierung von Declaration Maps erfordert nur eine einzige zusätzliche Option in der tsconfig.json, hat aber eine Voraussetzung, die häufig übersehen wird: declaration: true muss ebenfalls gesetzt sein, denn ohne generierte .d.ts-Dateien gibt es nichts, worauf eine Declaration Map verweisen könnte. Zusätzlich muss sourceMap: true aktiv sein, damit die Kette von der Declaration Map bis zur eigentlichen .ts-Quelldatei vollständig geschlossen ist.

Ein wichtiger Praxis-Hinweis: declarationMap ohne declaration erzeugt einen Compiler-Fehler, TypeScript verweigert dann den Build komplett. Wer diese Option in einem bestehenden Projekt aktivieren möchte, sollte deshalb zuerst prüfen, ob declaration bereits gesetzt ist, bevor die neue Option ergänzt wird.


{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "dist",
    "rootDir": "src",
    "strict": true
  },
  "include": ["src"]
}

4. Zusammenspiel von declarationMap und sourceMap

Declaration Maps und klassische Source Maps lösen zwei unterschiedliche Probleme, die häufig verwechselt werden. Eine klassische Source Map bildet kompiliertes JavaScript zur Laufzeit auf TypeScript-Quellcode ab, damit Stack-Traces und Debugger-Breakpoints im Original-Code funktionieren. Eine Declaration Map dagegen bildet die statische Typinformation in der .d.ts-Datei auf den Quellcode ab, damit die IDE zur Entwicklungszeit navigieren kann. Beide Mechanismen arbeiten unabhängig voneinander, ergänzen sich aber, wenn eine TypeScript-Bibliothek sowohl zur Laufzeit debuggbar als auch zur Entwicklungszeit navigierbar sein soll.

In der generierten index.d.ts-Datei erscheint am Ende ein Kommentar wie //# sourceMappingURL=index.d.ts.map, der auf die begleitende Map-Datei verweist. Diese Map-Datei enthält wiederum einen sources-Eintrag, der relativ zur Map-Datei auf die ursprüngliche .ts-Datei zeigt. Wird eine dieser relativen Pfadangaben durch einen fehlerhaften Build-Prozess verschoben, bricht die Verkettung, und die IDE fällt stillschweigend zurück auf die reine .d.ts-Navigation, ohne eine Fehlermeldung anzuzeigen.


// dist/index.d.ts.map — generated declaration map (simplified)
{
  "version": 3,
  "file": "index.d.ts",
  "sourceRoot": "",
  "sources": ["../src/index.ts"],
  "names": [],
  "mappings": "AAAA,cAAc,EAAE,MAAM,WAAW,CAAC"
}

5. Wie IDEs Declaration Maps tatsächlich nutzen

Visual Studio Code unterstützt Declaration Maps ohne zusätzliche Konfiguration, sofern die Einstellung typescript.preferGoToSourceDefinition aktiviert oder die Standardnavigation genutzt wird. Beim ersten Sprung landet der Cursor in der .d.ts-Datei, ein zweiter Aufruf von "Go to Definition" oder die alternative Aktion "Go to Source Definition" folgt dann der Declaration Map bis zum eigentlichen Quellcode. Dieses zweistufige Verhalten ist beabsichtigt, weil manche Entwickler bewusst die öffentliche Typsignatur sehen wollen, bevor sie tiefer in die Implementierung springen.

JetBrains-IDEs wie WebStorm und PhpStorm mit dem TypeScript-Plugin lösen Declaration Maps automatisch beim ersten Navigationsversuch auf, ohne einen Zwischenschritt über die .d.ts-Datei zu erzwingen. Voraussetzung ist in beiden IDE-Familien, dass die Map-Dateien tatsächlich im veröffentlichten Paket vorhanden sind und die referenzierten Quelldateien für die IDE erreichbar sind, entweder über node_modules mit ausgelieferten Quellen oder über ein lokal verlinktes Monorepo-Paket.


// .vscode/settings.json — prefer jumping straight to source, skip the .d.ts hop
{
  "typescript.preferGoToSourceDefinition": true,
  "typescript.tsdk": "node_modules/typescript/lib"
}

6. Declaration Maps beim Bibliotheks-Publishing

Eine häufig übersehene Voraussetzung: Declaration Maps funktionieren bei extern installierten Paketen nur, wenn die ursprünglichen .ts-Quelldateien tatsächlich mit im npm-Paket enthalten sind. Wird in der files-Angabe der package.json nur dist/ veröffentlicht, verweist die Declaration Map zwar korrekt auf ../src/index.ts, aber diese Datei existiert im installierten node_modules-Verzeichnis schlicht nicht. Die IDE kann dann nicht navigieren und fällt auf die .d.ts-Ansicht zurück.

Für eine TypeScript-Bibliothek, die von der vollen Declaration-Map-Erfahrung profitieren soll, muss deshalb auch der src/-Ordner im veröffentlichten Paket enthalten sein. Der zusätzliche Platzbedarf ist in den meisten Fällen gering, weil TypeScript-Quelldateien typischerweise kleiner sind als die kompilierten Ausgaben mit zusätzlichen Kommentaren und Formatierung. Wer die Paketgröße dennoch minimal halten will, kann testweise mit npm pack --dry-run prüfen, welche Dateien tatsächlich im Tarball landen.


# Verify that src/ is actually part of the published tarball
npm pack --dry-run

# Example output should list both dist/ and src/
# npm notice ????  @mironsoft/query-builder@1.2.0
# npm notice === Tarball Contents ===
# npm notice 1.1kB dist/index.d.ts
# npm notice 0.3kB dist/index.d.ts.map
# npm notice 2.4kB dist/index.js
# npm notice 1.8kB src/index.ts

7. Stolpersteine: relative Pfade und verschobene Build-Ordner

Der häufigste Fehler bei Declaration Maps entsteht, wenn rootDir und outDir in der tsconfig.json nicht konsistent zur tatsächlichen Ordnerstruktur stehen. Wird beispielsweise ein Build-Skript verwendet, das die Ausgabe nachträglich in einen anderen Ordner verschiebt, ohne die Map-Dateien entsprechend anzupassen, zeigen die relativen Pfade in der .d.ts.map ins Leere. Die IDE zeigt dann keinen Fehler, sondern navigiert einfach nicht weiter, was das Problem schwer diagnostizierbar macht.

Ein zweiter Stolperstein betrifft Build-Tools, die Deklarationen nachträglich mit einem separaten Bundler für .d.ts-Dateien zusammenfassen, etwa um mehrere kleine .d.ts-Dateien in eine einzige Rollup-Datei zu verschmelzen. Diese Tools müssen explizit Declaration-Map-Unterstützung mitbringen, sonst gehen die Zuordnungen beim Zusammenführen verloren. Vor dem Einsatz eines solchen Tools lohnt sich ein Blick in dessen Dokumentation, ob declarationMap überhaupt unterstützt wird.

8. Monorepo-Szenario: Declaration Maps über Package-Grenzen hinweg

In einem Monorepo mit mehreren internen TypeScript-Paketen zeigen Declaration Maps ihren größten Nutzen. Über TypeScript Project References mit composite: true und aktivierten Declaration Maps navigiert die IDE beim Import eines internen Pakets direkt in den Quellcode des jeweiligen Pakets, statt in dessen kompilierten dist/-Ordner. Voraussetzung ist, dass jedes Projekt im Monorepo composite: true zusätzlich zu declarationMap: true gesetzt hat, denn Project References verlangen composite Projects als Grundvoraussetzung.

Für Teams, die täglich zwischen mehreren Paketen eines Monorepos wechseln, ist dieser Effekt spürbar: Ein Bugfix in einem gemeinsam genutzten Utility-Paket lässt sich direkt aus dem aufrufenden Paket heraus finden und bearbeiten, ohne manuell zwischen Ordnern zu wechseln oder eine Dateisuche zu starten. Declaration Maps machen ein Monorepo in der Praxis so nutzbar, wie es sich anfühlen sollte: als ein einziges, zusammenhängendes Projekt statt als Sammlung isolierter Pakete.


// packages/utils/tsconfig.json — required for cross-package navigation
{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "outDir": "dist",
    "rootDir": "src"
  }
}

9. Mit und ohne Declaration Maps im Vergleich

Der Unterschied zwischen einer TypeScript-Bibliothek mit und ohne Declaration Maps zeigt sich nicht im Laufzeitverhalten, sondern ausschließlich in der täglichen Entwicklererfahrung. Die folgende Tabelle fasst die wichtigsten Unterschiede zusammen.

Aspekt Ohne Declaration Maps Mit Declaration Maps
Go to Definition Landet in generierter .d.ts Springt bis zum Original-Quellcode
Kommentare im Quellcode Nicht sichtbar Vollständig sichtbar im Ursprung
Monorepo-Navigation Über dist/ pro Paket Direkt zwischen src/-Ordnern
Paketgröße Minimal Etwas größer durch src/ und .map

Der geringe Mehraufwand an Paketgröße steht in keinem Verhältnis zum Zeitgewinn bei der täglichen Navigation, besonders für Teams, die eine TypeScript-Bibliothek intensiv weiterentwickeln oder in einem Monorepo mit vielen internen Abhängigkeiten arbeiten.

Eine weitere Beobachtung aus der Praxis: Sobald ein Team einmal an Declaration Maps gewöhnt ist, fällt deren Fehlen in einem anderen Projekt sofort negativ auf. Der gefühlte Rückschritt bei der Navigation motiviert häufig genau die Teams, die zuvor skeptisch gegenüber dem zusätzlichen Konfigurationsaufwand waren, die Option nachträglich in weiteren internen Paketen zu aktivieren.

Mironsoft

TypeScript-Tooling, Monorepos und Entwicklererfahrung

TypeScript-Monorepo mit besserer Navigation aufsetzen?

Wir richten Declaration Maps, Project References und Build-Pipelines so ein, dass euer Team zwischen internen Paketen navigiert, als wäre alles ein einziges Projekt.

tsconfig-Audit

Bestehende Konfiguration auf fehlende Declaration Maps und Project References prüfen

Monorepo-Setup

Composite Projects und Package-übergreifende Navigation einrichten

Publishing-Check

Paket-Tarball prüfen, damit Declaration Maps auch bei Konsumenten funktionieren

10. Zusammenfassung

Declaration Maps lösen ein konkretes, alltägliches Problem: die Navigation von einer generierten .d.ts-Datei zurück zum eigentlichen Quellcode einer TypeScript-Bibliothek. Mit declarationMap: true, declaration: true und sourceMap: true in der tsconfig.json ist die Aktivierung in wenigen Zeilen erledigt. Beim Publishing muss zusätzlich der src/-Ordner mit ausgeliefert werden, sonst läuft die Zuordnung bei externen Konsumenten ins Leere.

Der größte praktische Effekt zeigt sich in Monorepos mit vielen internen Paketen, wo Declaration Maps zusammen mit Project References eine nahtlose Navigation über Package-Grenzen hinweg ermöglichen. Der geringe zusätzliche Aufwand bei Konfiguration und Paketgröße rechtfertigt sich durch die spürbar bessere Entwicklererfahrung, besonders in Teams, die täglich mit der eigenen TypeScript-Bibliothek arbeiten.

Declaration Maps — Das Wichtigste auf einen Blick

Aktivierung

declaration, declarationMap und sourceMap gemeinsam in der tsconfig.json setzen.

Publishing

Den src/-Ordner mit veröffentlichen, sonst laufen die Map-Pfade bei Konsumenten ins Leere.

IDE-Verhalten

VS Code und JetBrains-IDEs folgen der Kette von .d.ts.map bis zur .ts-Quelldatei automatisch.

Monorepo

Zusammen mit composite: true und Project References die beste Navigationserfahrung über Pakete hinweg.

11. FAQ: Declaration Maps

1Unterschied Declaration Maps vs. Source Maps?
Source Maps für Laufzeit-Debugging, Declaration Maps für IDE-Navigation von der .d.ts zurück zum Quellcode.
2Warum Fehler ohne declaration?
Ohne generierte .d.ts-Dateien gibt es nichts, worauf eine Map zeigen könnte, TypeScript verweigert die Kombination.
3Funktioniert das bei npm-Paketen?
Nur wenn src/ mit veröffentlicht wird, sonst existiert die referenzierte Quelldatei in node_modules nicht.
4Erhöht src/ die Paketgröße stark?
Kaum, TypeScript-Quelldateien sind meist kleiner als kompilierte Ausgaben. npm pack --dry-run zeigt es vorab.
5Warum landet Go to Definition trotzdem in der .d.ts?
Teils beabsichtigtes Zweistufenverhalten in VS Code, teils fehlerhafte relative Pfade in der Map-Datei.
6Was macht composite: true?
Markiert ein Projekt für Project References, Voraussetzung für Package-übergreifende Navigation in Monorepos.
7Gehen Maps beim Bundling verloren?
Ja, wenn das Bundling-Tool keine explizite Declaration-Map-Unterstützung mitbringt.
8Nur für veröffentlichte Bibliotheken relevant?
Nein, auch interne Monorepo-Pakete profitieren stark von der Package-übergreifenden Navigation.
9Unterstützen alle IDEs das gleich?
Grundlegend ja, das genaue Navigationsverhalten unterscheidet sich aber leicht zwischen VS Code und JetBrains-IDEs.
10Wie prüfe ich, ob Declaration Maps aktiv sind?
Im dist/-Ordner nach .d.ts.map-Dateien neben den .d.ts-Dateien suchen, fehlen sie, war die Option nicht aktiv.