Start / Seminare / Modern TypeScript 7

Modul

ESM, Module und moderne Toolchains

9 Kapitel in diesem Modul-Video · Laufzeit

Vertonung mit synthetischer Stimme · Inhalte redaktionell verantwortet · © 2026 HECKER CONSULTING, alle Rechte vorbehalten

Transkript

Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.

ESM, Module und moderne Toolchains

0:00 Dies ist das konfigurationslastigste Modul des Seminars, und ich sage das ohne Entschuldigung — denn die Belohnung ist beträchtlich. Wenn Sie ehrlich zurückblicken: Wie viele Stunden Ihres Berufslebens haben Sie mit unverständlichen Importfehlern verbracht? Die stammen fast nie aus Ihrem Code. Sie stammen aus der Konfiguration, und zwar aus einer Handvoll immer gleicher Verwechslungen.

0:22 Am Ende dieses Moduls können Sie solche Meldungen lesen, statt sie zu suchen — und das ist eine Fähigkeit, die Sie über jede Version hinweg begleitet.

ESM, Module und moderne Toolchains

0:31 Der Leitsatz für dieses Modul: Die meisten unverständlichen Fehlermeldungen stammen aus der Konfiguration, nicht aus dem Code. Wir gehen die beiden Modulsysteme durch, klären die drei Schalter, die ineinandergreifen, und sehen uns die Projektdatei als Modulvertrag an. Dazu kommen zwei Themen mit praktischer Sprengkraft: das Veröffentlichen von Bibliotheken und die Trennung von Prüfen und Übersetzen.

0:53 Am Ende reparieren Sie ein absichtlich kaputtes Projekt — mit Diagnose statt Ausprobieren.

ECMAScript Modules und CommonJS

0:59 Zwei Modulsysteme mit verschiedenem Ladeverhalten leben in der JavaScript-Welt nebeneinander — historisch gewachsen, beide legitim. Wer hier sauber unterscheidet, spart sich später jede Diskussion über kaputte Importe. Denn die meisten Fehler sind Vermischungsfehler. Der ältere Weg lädt synchron und löst Abhängigkeiten zur Laufzeit auf — er holt sich das Nötige in dem Moment, in dem er es braucht.

1:24 Der standardisierte Weg lädt statisch: Was gebraucht wird, steht vor der Ausführung fest. Ein Bild: Der eine geht während des Kochens einkaufen, der andere schreibt vorher eine Liste. Beide funktionieren. Beide leben nebeneinander. Und die schwer verständlichen Fehlermeldungen in Node-Projekten stammen fast immer aus ihrer Vermischung — nicht aus TypeScript.

1:45 Das ist wichtig, weil man sonst am falschen Ort sucht. Vier Unterscheidungsmerkmale, und die dritte Zeile ist die praktisch folgenreichste: die Dateiendung im Import. Im älteren System optional, im neueren je nach Auflösung erforderlich. Genau dort entsteht der Fehler, der alle einmal erwischt — es übersetzt anstandslos, und beim Ausführen findet Node die Datei nicht.

2:07 Die letzte Zeile ist der schönste praktische Unterschied: Warten auf der obersten Ebene eines Moduls geht nur im neueren System, weil dort die Auflösung vorher stattgefunden hat. Wer das gebraucht hat, weiß, wie wertvoll es ist. Der zweite Punkt ist der häufigste und tückischste: die Dateiendung im Import weglassen, weil es beim Übersetzen ja funktioniert.

2:28 Der Compiler ist da nachsichtig, die Laufzeit nicht — und Sie merken es erst beim Ausführen. Der erste Punkt ist der Klassiker aus der Übergangszeit: ein modernes Paket aus dem älteren System heraus einbinden wollen. Und der dritte ist eine Unterlassung mit Folgen: Das Modulsystem in der Projektdatei nicht festlegen und dann beide Welten gleichzeitig bedienen.

2:49 Dann rät jedes Werkzeug für sich, und die Räte gehen auseinander.

Type-only Imports

2:53 Ein kleines Sprachmittel mit größerer Wirkung, als der erste Blick vermuten lässt. Die Regel dahinter ist einfach: Was nur ein Typ ist, darf nicht im erzeugten Ergebnis landen. Und warum das mehr als Kosmetik ist, sehen wir gleich. Ein Import, der ausschließlich einen Typ holt, hat im erzeugten JavaScript nichts verloren.

3:13 Dort zeigt er auf ein Modul, das zur Laufzeit womöglich gar nicht geladen werden soll — vielleicht existiert es dort nicht einmal. Type-only Imports machen die Absicht ausdrücklich, statt sie dem Compiler zu überlassen. Und das ist der Kern: Der Compiler kann in vielen Fällen raten, und er rät meistens richtig. Aber „meistens richtig" ist bei Ladevorgängen eine unangenehme Grundlage — vor allem, wenn andere Werkzeuge im Spiel sind, die gar nicht raten können.

3:40 Oben die gemischte Fassung: Typ und Funktion in einer Zeile, der Compiler muss entscheiden, was davon übrig bleiben soll. Unten die getrennte Fassung — der Typ verschwindet zuverlässig, die Funktion bleibt. Zwei Zeilen statt einer, und dafür bekommen Sie Eindeutigkeit. Die Fußzeile nennt den Schalter, der diese Trennung verbindlich macht: Was im Quelltext steht, steht auch im Ergebnis. Keine Umschreibung, kein Raten.

4:05 Für Projekte, die mit mehreren Werkzeugen arbeiten, ist das eine der lohnendsten Einstellungen überhaupt. Vier Gründe, und der zweite ist der, der Menschen wirklich Zeit kostet. Ein Modul, das nur Typen liefert, soll zur Laufzeit gar nicht geladen werden — sonst importieren Sie Code in ein Bündel, den niemand braucht. Nebenwirkungen beim Import werden sonst zufällig ausgelöst oder verschluckt; das sind die Fehler, die je nach Bündelreihenfolge auftreten und wieder verschwinden.

4:34 Werkzeuge, die nur Typen entfernen, können die Absicht nicht erraten — sie sehen den Rest des Programms nicht. Und schließlich dokumentiert der Import, was Vertrag ist und was Implementierung.

Die drei Schalter der Modulauflösung

4:46 Jetzt zum Kern des Moduls: drei Einstellungen, die ineinandergreifen und gemeinsam entscheiden, ob Ihre Importe funktionieren. Meine Empfehlung vorweg und sie steht in der Tagline: ausdrücklich setzen — auch wenn ein Vorgabewert existiert. Drei Schalter, drei Zuständigkeiten. Der erste bestimmt, welche Modulsyntax ausgegeben wird. Der zweite bestimmt, wie Importe gefunden werden.

5:10 Und der dritte bestimmt, ob der Compiler Importe umschreiben darf. Sie greifen ineinander, und genau deshalb sind sie der häufigste Ort widersprüchlicher Konfiguration: Jeder für sich sieht sinnvoll aus, zusammen ergeben sie Unsinn. Wenn Sie sich eine Sache aus diesem Kapitel merken: Diese drei entscheidet man gemeinsam, nicht nacheinander.

5:31 Diese Tabelle ist Ihre Aufräumliste. Rechts stehen die entfernten Werte — die sehen Sie in älteren Projekten und in Vorschlägen von Assistenten regelmäßig noch. Die alten Modulformate aus der Zeit vor den Standardmodulen sind weg. Die alten Auflösungsstrategien ebenfalls. Und das Zusammenfassen der Ausgabe in eine Datei ist heute Sache eines Bundlers, nicht des Compilers.

5:54 Die Fußzeile nennt eine Neuerung, die vielen entgangen ist: Die moderne Auflösung lässt sich seit Version 6.0 mit dem älteren Ausgabeformat kombinieren. Das ist für Altbestände sehr praktisch. Der erste Punkt ist eine Haltung, die ich Ihnen empfehle: Verlassen Sie sich nicht auf einen Vorgabewert, den Sie nicht benennen können.

6:14 Nicht, weil Vorgabewerte schlecht wären, sondern weil Sie beim nächsten Fehler wissen müssen, was gilt. Der zweite ist der Hauptfehler dieses Kapitels: die beiden Hauptschalter unabhängig voneinander wählen — sie gehören zusammen entschieden. Und der dritte: Konfiguration aus einem Artikel übernehmen, der vor Version 6.0 entstanden ist. Das ist die Mehrheit der Artikel im Netz.

Welche Einstellung wofür

6:36 Jetzt wird es konkret und angenehm einfach. Die Wahl richtet sich nach dem Ziel, nicht nach dem Geschmack — und wenn Sie das Ziel benennen können, ist die Einstellung keine Frage mehr, sondern eine Ableitung. Vier Zeilen, vier Ziele, jeweils eine Antwort. Läuft der Code direkt in Node, nehmen Sie die Node-Auflösung — beide Schalter gleich, das ist bewusst so.

6:58 Geht er durch einen Bundler, überlassen Sie diesem die Arbeit und behalten die Syntax bei. Bei einer veröffentlichten Bibliothek gilt wieder die Node-Auflösung, weil die Konsumenten sie brauchen. Und beim Altbestand haben Sie Spielraum. Die Fußzeile ist mir wichtig: Die Release Notes empfehlen für die Migration zwei benannte Kombinationen — als Plan, nicht als Zufallsfund.

7:20 Zwei Projekte derselben Firma, zwei völlig verschiedene Konfigurationen. Der Dienst läuft direkt in Node, die Oberfläche geht durch einen Bundler. Und die Fußzeile enthält den Satz, den ich Ihnen mitgeben möchte: Beides ist richtig — für jeweils ein Ziel. Falsch wird es erst, wenn dasselbe Projekt beide Annahmen zugleich trifft.

7:39 Genau das passiert in gewachsenen Projekten schleichend: Ein Teil geht durch den Bundler, ein anderer wird direkt ausgeführt, und die Konfiguration versucht, beiden gerecht zu werden. Dann ist sie für keines von beiden richtig. Alles hängt an einer einzigen Frage, und sie steht ganz oben: Wer führt den Code am Ende aus — Node selbst oder ein Bündelergebnis?

8:01 Dazu kommen zwei Nebenfragen: Wird das Paket veröffentlicht oder nur intern verwendet? Muss die Ausgabe in einer alten Laufzeit funktionieren? Und dann der Satz, der das Kapitel zusammenfasst: Alles Weitere folgt daraus. Ohne diese Antwort ist jede Wahl geraten — und geratene Konfiguration funktioniert manchmal, was schlimmer ist als gar nicht, weil sie dann jahrelang bleibt.

package.json als Modulvertrag

8:24 Jetzt zu drei Feldern in der Projektdatei, die viele nie bewusst gesetzt haben und die große Wirkung entfalten. Anlass, sich damit zu beschäftigen, ist der Wegfall des alten Basispfads — und der Ersatz ist kein Compilerschalter, sondern ein Laufzeitmechanismus. Drei Felder, drei Aufgaben. Das erste legt das Modulsystem des Pakets fest.

8:44 Das zweite bestimmt, was von außen erreichbar ist — und schließt damit alles andere aus; das ist eine Grenze, keine Empfehlung. Das dritte erlaubt interne Kurzpfade mit einem Rautenpräfix. Und jetzt kommt der entscheidende Satz: Diese löst Node selbst auf, nicht nur der Compiler. Genau das unterscheidet sie von den alten Pfad-Aliassen, die nur beim Übersetzen existierten und zur Laufzeit ins Leere zeigten.

9:09 Drei Felder, sieben Zeilen, und ein vollständiger Modulvertrag. Das Paket ist ein Modul im modernen Sinn. Von außen ist genau ein Einstiegspunkt erreichbar — alles andere ist privat, ohne dass jemand darum bitten musste. Und intern gibt es einen Kurzpfad für die Domäne. Die Fußzeile nennt den entscheidenden Vorzug: Dieser Kurzpfad wird von Node aufgelöst, ist also auch zur Laufzeit gültig.

9:32 Damit funktioniert er in Tests, im Betrieb und in Werkzeugen gleichermaßen — ohne dass irgendwo eine zweite Konfiguration dieselbe Abbildung nachbauen muss. Der erste Punkt fasst den Kernfehler dieses Kapitels zusammen: Pfad-Aliasse einsetzen ohne Bundler oder Loader. Der Compiler löst sie auf, die Laufzeit nicht — es übersetzt also, und dann findet Node nichts.

9:54 Der zweite hat eine Sicherheitsdimension: das Ausfuhrfeld weglassen und damit den gesamten Dateibaum öffentlich machen. Dann hängt sich jemand an eine interne Datei, und Sie können sie nie mehr umbenennen. Und der dritte ist der Migrationsfehler: alte Basispfade eins zu eins übernehmen, statt auf die Kurzpfade umzustellen.

Bibliotheken veröffentlichen

10:14 Ein kurzes, aber wichtiges Kapitel — vor allem, wenn Sie interne Pakete für andere Teams bereitstellen. Das gilt nämlich auch als Veröffentlichung. Die Kernbotschaft: Typen sind Teil der Auslieferung, nicht Beiwerk. Eine veröffentlichte Bibliothek liefert ihre Deklarationen mit — sonst ist sie für TypeScript-Konsumenten praktisch wertlos.

10:35 Sie sehen dann eine namenlose Blackbox, bekommen keine Vervollständigung und keine Prüfung. Und der zweite Punkt betrifft den Ernstfall: Die Quellzuordnungen entscheiden darüber, ob ein Fehler im Betrieb auf den Originalquelltext zeigt oder auf übersetzte Zeilen, die niemand lesen will. Wer schon einmal um drei Uhr nachts einen Stacktrace ohne Zuordnung entziffert hat, weiß, wie viel das wert ist.

10:58 Diese Gegenüberstellung nimmt den Faden aus Modul 1 wieder auf. Die zweite Zeile trägt den eigentlichen Unterschied: In einer Anwendung sind öffentliche Signaturen intern änderbar — passt es nicht mehr, ändern Sie es morgen. In einer Bibliothek sind sie eine Zusage über Jahre, denn fremde Projekte hängen daran. Deshalb darf eine Anwendung pragmatisch konfiguriert sein und eine Bibliothek nicht.

11:21 Und die letzte Zeile ist der Aufwandstreiber, den viele unterschätzen: Eine Bibliothek muss oft beide Modulwelten bedienen, eine Anwendung nur eine. Vier Merkmale, und der dritte ist der, der am häufigsten schiefgeht: Interne Typen sind nicht versehentlich Teil einer öffentlichen Signatur. Das passiert leicht — eine Funktion gibt ein Objekt zurück, dessen Typ eigentlich intern gedacht war, und schon ist er Teil Ihrer Zusage.

11:46 Die Übung aus Modul 1 hat genau darauf gezielt. Die anderen drei: Deklarationen liegen bei und stimmen mit der Ausgabe überein, das Ausfuhrfeld beschreibt genau die öffentliche Oberfläche, und die Quellzuordnungen zeigen auf lesbaren Quelltext.

Prüfen und Übersetzen trennen

12:00 Jetzt ein Thema, das in modernen Projekten fast überall auftaucht und dessen Falle selten benannt wird. Die Kurzfassung: Ein schneller Build ist kein Nachweis. Und der Mechanismus dahinter ist wichtiger als die Frage, wie oft das vorkommt. In modernen Setups übersetzt ein schnelles Werkzeug, während der Compiler nur noch prüft. Das ist deutlich schneller und völlig legitim.

12:23 Die Falle steckt im Mechanismus: Ein Werkzeug, das Typen nur entfernt statt sie zu prüfen, baut auch fehlerhaften Code erfolgreich. Es sieht die Typen ja gar nicht an — es radiert sie weg. Damit ist der grüne Build kein Nachweis mehr, sondern nur noch eine Aussage über die Syntax. Wer das nicht weiß, hält eine erfolgreiche Pipeline für eine geprüfte.

12:44 Drei Skripte, und das dritte zeigt, wie es zusammengehört: Die Pipeline ruft erst die Prüfung, dann die Tests, dann den Build. Nacheinander, und jeder Schritt kann scheitern. Die Fußzeile schlägt einen Bogen nach vorn, den ich unterstreichen möchte: Ohne eigenes Prüfskript kann weder die Pipeline noch ein Coding Agent die Prüfung einzeln aufrufen.

13:04 In Modul 11 wird genau das zur Voraussetzung — ein Agent, der seine eigene Arbeit kontrollieren soll, braucht einen benannten Befehl dafür. Was keinen Namen hat, kann niemand aufrufen. Der dritte Punkt ist der menschlichste und passiert in jedem Projekt unter Termindruck: Prüfung und Build in ein Skript legen — und dann streicht jemand den langsamen Teil, weil es eilig ist.

13:26 Danach läuft die Pipeline schneller und prüft nichts mehr. Der erste ist der bekannte: nur im Editor prüfen, nicht in der Pipeline. Und der zweite ist die Annahme, über die wir gerade gesprochen haben: ein erfolgreicher Bündellauf bedeute geprüfte Typen. Er bedeutet, dass sich der Code übersetzen ließ. Mehr nicht.

Monorepos und Project References

13:45 Zum Abschluss ein Thema für größere Vorhaben: mehrere Pakete in einem Repository. Es geht darum, Grenzen zu ziehen, damit inkrementell gebaut werden kann — und die Frage ist vor allem, wann sich der Aufwand lohnt. Projektreferenzen zerlegen ein großes Repository in einzeln übersetzbare Teilprojekte mit ausdrücklich erklärten Abhängigkeiten.

14:05 Der Gewinn ist doppelt: inkrementelle Builds, weil nur das Betroffene neu gebaut wird, und klare Grenzen, weil die Abhängigkeiten hingeschrieben werden müssen. Der Preis ist eine Konfiguration, die verstanden werden will — und die im Team nicht nur einer verstehen sollte. Es ist eines der Werkzeuge, bei denen die Einführung leichter fällt als die Pflege.

14:26 Drei gute Anlässe und eine klare Absage. Mehrere Pakete, die sich unabhängig voneinander ändern. Lange vollständige Builds, bei denen meist nur ein Teil betroffen ist — hier zahlt sich die Inkrementalität unmittelbar aus. Architekturgrenzen, die im Code gelten sollen und nicht nur im Wiki stehen; das ist der unterschätzte Gewinn, denn Referenzen machen Abhängigkeiten verbindlich.

14:49 Und die Absage: nicht lohnend bei einem Paket, egal wie groß es ist. Größe allein ist kein Grund — Unabhängigkeit ist einer. Der erste Punkt macht den ganzen Aufwand zunichte: Referenzen so schneiden, dass fast jede Änderung fast alles neu baut. Dann haben Sie Konfiguration ohne Gewinn. Der zweite ist die schärfere Variante — Zyklen zwischen Teilprojekten, und die Inkrementalität ist weg. Beides sind Schnittfehler, keine Werkzeugfehler.

15:17 Und der dritte ist eine Erwartung, die enttäuscht wird: Projektreferenzen ersetzen die Modulauflösung nicht. Die drei Schalter aus Kapitel 3 müssen trotzdem stimmen — Referenzen ordnen die Projekte, nicht die Importe.

Übung — eine kaputte Auflösung reparieren

15:31 Jetzt reparieren Sie ein absichtlich kaputtes Projekt. Und die Überschrift ist zugleich der Hinweis: Die Ursache steht in der Konfiguration, nicht im Code. Suchen Sie also nicht in den Quelldateien nach dem Fehler — sondern in den Dateien, die niemand liest. Das Lernziel ist Diagnose: eine Fehlermeldung zur Modulauflösung auf ihre Ursache in der Konfiguration zurückführen und die Einstellung begründet wählen.

15:55 Achten Sie auf das Erfolgskriterium, denn es ist ungewöhnlich streng: Das Projekt muss übersetzen und laufen — und die gewählte Einstellung muss mit dem Ausführungsziel begründet sein, nicht mit Ausprobieren. Wer durch Herumprobieren zum grünen Ergebnis kommt, hat die Übung nicht bestanden, auch wenn es funktioniert. Wer früh fertig ist, ersetzt die verbliebenen Pfad-Aliasse durch Kurzpfade und prüft, ob es zur Laufzeit noch funktioniert.

16:20 Der erste Schritt spart die meiste Zeit, wie die Fußzeile sagt: Lesen Sie die Meldung und entscheiden Sie, ob sie vom Compiler oder von der Laufzeit kommt. Ein Laufzeitfehler wird von keiner Compileroption behoben — das erspart Ihnen eine halbe Stunde Suche in der falschen Datei. Danach bestimmen Sie das Ausführungsziel, setzen die beiden Hauptschalter passend und ausdrücklich und prüfen die Pfad-Aliasse mit der Frage: Gibt es sie zur Laufzeit überhaupt?

16:45 Und im letzten Schritt schreiben Sie die Begründung auf — für die nächste Person, die diese Datei anfasst. Vier Sätze zum Mitnehmen. Die meisten Importfehler sind Vermischungsfehler zweier Modulsysteme — suchen Sie dort zuerst. Die beiden Hauptschalter setzen Sie ausdrücklich und passend zum Ausführungsziel, nicht nach Geschmack.

17:05 Kurzpfade über die Projektdatei gelten zur Laufzeit, alte Pfad-Aliasse nur beim Übersetzen. Und: Ein grüner Bündellauf ist kein Nachweis geprüfter Typen. Im nächsten Modul geht es um Architektur, Qualität und Tests — also darum, wie sich diese Bausteine in einem größeren Projekt zusammenfügen.

Lieber mit Trainer? Dieses Modul ist Teil unserer Team-Schulungen — mit Übungen, Ihrem eigenen Code und Fragen, die ein Video nicht beantwortet. Mehr erfahren →