Zum Darstellen von Modulen unter JavaScript haben sich in den letzten Jahren ein paar wenige Modulsysteme etabliert, welche derzeit nebeneinander co-existieren. Dazu gehören unter anderen CommonJS-Module, die Asynchronous Module Definition (AMD) oder das System-Format. Daneben liegt mit der Universal Module Definition (UMD) seit einiger Zeit ein Vorschlag vor, der Module in einer Art, die sowohl kompatibel zu AMD und CommonJS ist, exportiert. Das Modul-System, welches ab EcmaScript 2015 vorherrscht, wird in der Regel mittels Transpilierung in eines der zuvor genannten übergeführt, damit der geschriebene Code in handelsüblichen Browsern läuft.
CommonJS als defacto-Standard (auf der Serverseite)
Serverseitig werden vor allem CommonJS-Module genutzt. Das liegt auch daran, dass das NodeJS-Modulsystem auf diesen defacto-Standard basiert. Eine wichtige Eigenschaft von NodeJS-Modulen ist, dass diese selbstbeschreibend sind. Über die Datei package.json
beschreiben sie unter anderem ihre Abhängigkeiten. Dies versetzt den weit verbreitenden Package-Manager npm
in die Lage, sämtliche Abhängigkeiten beim Beziehen eines Paketes mitzuladen. Dies gilt auch für die Abhängigkeiten der Abhängigkeiten etc.
Für das Auflösen dieser Abhängigkeiten zur Laufzeit existieren klare Regeln, die davon ausgehen, dass sich die benötigten Bibliotheken im Ordner node_modules
des aktuellen Paketes oder eines übergeordneten Paketes befinden. Dies macht Konfigurationsdateien, aus denen die Positionen der einzelnen Abhängigkeiten hervorgehen, überflüssig. Genau so wenig muss der Anwendungsentwickler über diese Internas Bescheid wissen. Stattdessen kann er via npm geladene Bibliotheken im Sinne der OOP und Komponentenorientierung als Blackbox betrachten.
Herausforderungen bei CommonJS
Bei all dem Lobgesang auf das NodeJS-Modulsystem sollen auch seine Nachteile nicht verheimlicht werden. Eine Herausforderung, die unter Windows-Systemen zu tage tritt, sind lange Pfad-Namen. Diese ergeben sich durch hierarchische Schachtelung von Modulen. Ein Paket kann in seinem Ordner node_modules
Abhängigkeiten haben, welche wieder in ihren Ordnern node_modules
Abhängigkeiten haben können usw. Da die Pfadlänge bei Windows-Systemen leider begrenzt ist, führt dies ggf. zu einer Herausforderung. Eine weitere Herausforderung ist die Tatsache, dass das zugrundeliegende CommonJS-Module-System davon ausgeht, dass die Laufzeitumgebung Abhängigkeiten synchron laden kann. Serverseitig ist das auch kein Problem aber im Browser-Anwendungen kommt man damit nicht weit. Diese fordern nämlich weitere Pakete in der Regel asynchron an, um das Einfrieren der GUI zu vermeiden.
Für beide Herausforderungen gibt es glücklicherweise Lösungen. Die Pfadlänge versucht NodeJS seit einigen Versionen zu minimieren, indem es Abhängigkeiten möglichst weit oben in der Ordnerstruktur platziert. Das ist möglich, weil Abhängigkeiten, wie zuvor erwähnt, auch im Ordner node_modules
von übergeordneten Modulen gefunden werden. Solange es keine zwei Module gibt, die die selbe Abhängigkeit in gänzlich unterschiedlichen nicht zueinander kompatiblen Versionen benötigen, können Abhängigkeiten also auf oberster Ebene platziert werden.
Für die Diskrepanz zwischen asynchronem Laden in Browser-Anwendungen und synchronem Laden mittels CommonJS existieren zwei Lösungen: Zum einen erlauben Module-Loader das dynamische Umschreiben von Paketen, sodass diese Abhängigkeiten doch asynchron auflösen. Ein populäres Beispiel hierfür ist RequireJS. Zum anderen erlauben Werkzeuge, wie browserify und webpack, ein Bundling von NodeJS-Modulen in einer Art und Weise, die sich direkt im Browser ausführen lässt. Da moderne Web-Anwendungen aus Gründen der Performance sowieso auf Bundling setzen, stellt der Einsatz solcher Werkzeuge ohnehin eine Notwendigkeit dar.
webpack
Unter den verfügbaren Bundlern sticht webpack hervor. Es bietet einige nette Möglichkeiten, wie das Aufteilen des Bundles in mehrere Teile (Chunks). Diese können bei Bedarf von der Browser-Anwendung asynchron nachgeladen werden. Darüber hinaus spielt webpack den Bundling-Gedanken sehr weit und erlaubt sogar das Aufnehmen unterschiedlicher Dateitypen, wie HTML-Templates sowie CSS- und JavaScript-Dateien, in ein und das selbe Bundle. Möglich macht das die Umwandlung sämtlicher Formate in JavaScript. Auch das Transpilieren von TypeScript und anderen Sprachen nach JavaScript übernimmt webpack. Generell kann webpack viele Aufagaben von Build-Lösungen, wie Gulp oder Grunt, übernehmen. Während der Entwickler mit letzteren in imparativer Manier die einzelnen Aufgaben Schritt für Schritt festlegen muss, arbeitet er mit webpack auf deklarativer Weise. Dies macht die Konfiguration kürzer und einfacher. In Fällen, in denen der deklarative Ansatz nicht ausreicht, kann webpack in Gulp- und Grunt-Tasks eingebunden werden.
Zusammenfassung und Ausblick
NodeJS-Module bieten einige Vorteile:
- Sie basieren auf dem CommonJS-Standard
- Sie sind selbstbeschreibend und erlauben dem Entwickler, Pakete als Blackbox zu betrachten
- Abhängigkeiten (und deren Abhängigkeiten etc.) können via npm automatisch geladen werden
- Abhängigkeiten können aufgrund von Konventionen zur Laufzeit automatisch geladen werden
Dank Bundling kann das für die Serverseite entwickelte NodeJS-Module-System auch in Browseranwendungen genutzt werden. Ein vielversprechender Bundler ist webpack. Jene, die nun Neugierig auf den Einsatz von Angular 2 mit webpack sind, finden Infos dazu in einem meiner nachfolgenden Beiträge.