Webpack ist ein universaler Module-Bundler für Webprojekte und seit 2017 das mit Abstand meist heruntergeladene Tool – trotz Konkurrenz von rollup und anderen (Quelle: npm trends 2021). Es ermöglicht, verschiedene Dateien und deren Abhängigkeiten in einen statischen Output zusammenzufassen. Außerdem bietet es verschiedene Zusätze an, um die Entwicklung zu unterstützen.
Anstelle vieler einzelner Imports von JavaScript-Zusätzen in der index.html, kann das erzeugte Bundle referenziert werden. Somit muss diese Datei nicht immer wieder angepasst werden, und es können weniger Probleme durch fehlende Imports oder Tippfehler entstehen.
Webpack ist hauptsächlich für JavaScript gedacht, erlaubt aber auch verschiedenste Transformationen von z.B. Schriftarten und Bildern.
Warum es Sinn macht, sich mit Webpack auseinander zu setzen
Webpack ist einer der etabliertesten Frontend-Bundler und daher in den meisten Projekten im Einsatz, auch wenn dies vielleicht nicht direkt ersichtlich ist. Ein Beispiel dafür ist die Verwendung der Vue CLI. Diese basiert auf Webpack, aber übernimmt sehr viele Konfigurationen für den/die Entwickler*in. Dadurch muss diese(r) sich nicht zwangsläufig mit Webpack auseinandersetzen.
Doch, obwohl es für jedes Framework verschiedene, sehr gute vorgefertigte Webpack-Konfigurationen gibt, sind die wenigsten Projekte damit vollständig abgedeckt. Oftmals werden verschiedene Funktionalitäten wie z.B. das Bundeln von SVG-Dateien benötigt. Diese müssen je nach Preset nachkonfiguriert werden. Daher ist es hilfreich zu wissen, wie die verschiedenen Phasen beim Ausführen eines Webpack-Scripts aufgebaut sind und in welche entsprechend eingegriffen werden muss.
Allgemeiner Ablauf
Wenn Webpack für das Projekt installiert wurde, können über die webpack.config.js Projekt-relevante Details ergänzt werden. Wird ein Tool wie die Vue CLI verwendet, gibt es meist alternative Konfigurationsmethoden. In diesem Fall würde keine webpack.config.js angepasst werden, sondern die vue.config.js erweitert.
Egal, ob Webpack direkt verwendet wird oder ein entsprechendes Tool Teile abkapselt, der Ablauf bleibt gleich. Die in der Konfiguration definierten Schritte werden in verschiedenen Phasen ausgeführt.
Erstellen eines Dependency Graphs
Zunächst wird von Webpack immer ein Dependency Graph der Anwendung erstellt. Das Bedeutet, dass Webpack analysiert, welche Dateien eine andere referenzieren. Hierbei kann es sich um Imports von anderen Projektdateien, Bibliotheken oder auch Ressourcen wie z.B. Bildern handeln. Der Graph wird ausgehend vom angegebenen Startpunkt erstellt, hierbei handelt es sich in den meisten Fällen um die index.js-Datei des Projektes. Von dort aus werden Schritt für Schritt alle Abhängigkeiten in den Baum hinzugefügt. Das Erstellen des Dependency Graphs sowie das eigentliche Bundeling sind Kern-Schritte des Ablaufes, während Transformationen und Plugin-Erweiterungen optional sind.
Transformieren
Nachdem der Dependency Graph erstellt wurde, können über die Webpack-Konfiguration verschiedene Transformationen definiert werden. Standardmäßig verarbeitet Webpack nur JavaScript- und JSON-Dateien. Für alle anderen Dateien müssen entsprechende Loader zur Transformation bereitgestellt werden. Diese werden als Dependency installiert und im Transformationsschritt angegeben. Jede Transformation wird über eine Regel definiert, welche den zu verwendeten Loader und die Datei-Formate enthält, die transformiert werden sollen.
Bundling
Der Bundling-Schritt wird anhand des erstellten Dependency Graphs und den definierten Transformationen des Projekts zusammengefasst. Meistens entsteht dabei nur eine einzelne Datei, die dann in der index.html referenziert werden kann.
Plugins
Die Verwendung von Plugins ist die letzte Phase. Hierbei können durch verschiedene Plugins zusätzliche Schritte auf das erstellte Bundle ausgeführt werden. Klassische Anwendungsfälle sind die Optimierung der Dateigröße des Bundles und das Steuern von Environment Variablen für den Produktions- und Entwicklungsmodus.
Getting started
Zunächst muss in das vorhandene Projekt Webpack und der zugehörige Command Line Interpreter (CLI) installiert werden, und zwar mit:
Das Bundling kann dann mit diesem Befehl durchgeführt werden:
Sofern nicht anders konfiguriert, wird nun ein Ordner mit dem Namen dist erstellt. Dieser erhält die Bundle-Datei mit dem Namen main.js, die jetzt in der index.html referenziert werden kann.
Aufbau der webpack.config.js
Jede Konfigurationsdatei kann folgende, grundlegende Optionen beinhalten:
- Startpunkt der Anwendung festlegen
- Transformationsregeln definieren
- Speicherort des generierten Bundles definieren
- Anzuwendende Plugins definieren
Beim Schreiben einer Konfigurationsdatei empfiehlt es sich für eine bessere Lesbarkeit, sich bei der Struktur auch an die Ablaufphasen zu halten. Dadurch wird es später einfacher und schneller verständlich, da zum Beispiel bei Anpassungen in den Plugins direkt am Ende der Konfiguration gesucht werden kann. Wichtig ist jedoch zu wissen, dass alle diese Teile optional sind, da die Konfiguration entweder auf bestimmte Defaults zurückfällt oder schlichtweg nicht zwangsläufig etwas konfiguriert werden muss.
1. Startpunkt der Anwendung festlegen
Der Einstiegspunkt der Anwendung muss nun über die webpack.config.js konfiguriert werden, da von diesem Punkt aus der Dependency Graph erstellt wird.
Danach kann der ursprünglich definierte Eintrag für den Startpunkt der Anwendung in der package.json-Datei entfernt werden:
2. Transformationsregeln definieren
Eine Transformationsregel definiert, wie bestimmte Dateien von Webpack verarbeitet werden sollen. Jede Regel besteht aus zwei Bestandteilen: einem regulären Ausdruck (RegEx), der definiert welche Dateien transformiert werden sollen, und der Referenz eines Loaders. Ein Loader ist eine Dependency, die die gewünschte Transformation durchführen kann. Beispiele für häufig verwendete Loader sind der ts-loader, um TypeScript-Dateien zu transformieren, oder der sass-loader, um .scss-Dateien zu transformieren.
Die RegEx wird unter test
hinterlegt, und der Loader kann mit use
eingebunden werden.
Wenn mehrere Loader für dieselben Dateien benötigt werden, kann hier auch ein Array hinterlegt werden. Ein klassisches Beispiel hierfür ist das Transformieren von .css-Dateien.
Der reguläre Ausdruck, um Dateien nach der Endung .css zu selektieren, ist /\.css$/
.
Als nächstes muss der entsprechende Loader für diese Dateien installiert werden. Für dieses Beispiel ist das der css-loader:
Damit das CSS automatisiert in den DOM injiziert wird, kann der style-loader ebenfalls integriert werden.
Bei der Verwendung von mehreren Loadern ist es wichtig zu wissen, dass die Reihenfolge der Ausführung rückwärts zur Definition ist, d.h. der zuletzt genannte Loader wird zuerst angewendet.
Da wir zuerst die .css-Dateien auflösen wollen, muss der css-loader als letztes genannt werden.
3. Speicherort des generierten Bundles festlegen
Als nächstes kann der Speicherort des generierten Bundles definiert werden. Hierbei wird unter dem Punkt output
der entsprechende Pfad angegeben. Mit filename
kann der Name der generierten Datei festgelegt werden.
Wird in der Webpack-Konfiguration kein Output spezifiziert, wird die Datei standardmäßig main.js benannt und in den dist-Ordner abgelegt. Ist dieser nicht vorhanden, wird er automatisch generiert.
4. Anzuwendende Plugins definieren
Eines der meistgenutzten Plugins ist das HtmlWebpackPlugin. Dieses generiert eine index.html, die automatisch in einem Script-Tag die vorher generierten Webpack-Dateien referenziert.
Zunächst muss auch das Plugin, wie gewohnt, installiert werden:
Danach kann es über require
in der webpack.config.js referenziert und unter Plugins eingebunden werden.
5. Bundling Modus festlegen
Mit Webpack kann außerdem definiert werden, ob die Anwendung gerade für einen lokalen Test oder die Produktionsumgebung verpackt werden soll. Das hat den Vorteil, dass z.B. für das Produktionsbundle irrelevante Informationen wie Debug-Logs und Kommentare aus dem Bundle entfernt werden können. Dadurch kann die Größe des Bundles für die Endbenutzer reduziert werden. Gültige Werte für den Modus sind production, development und none.
Es gibt verschiedene Wege, diesen Modus festzulegen. Die statische Variante ist, es direkt in der webpack.config.js zu hinterlegen:
Der Nachteil ist jedoch, dass dann verschiedene Konfigurationen für die unterschiedlichen Anwendungsfälle benötigt werden. Bei sehr spezifischen Anwendungsfällen kann dies dennoch eine Option sein. Ein mögliches Beispiel hierfür wäre, dass dieselbe Anwendung statisch gehostet, aber auch als Desktop-Anwendung verpackt werden soll.
Soll auf mehrere Konfigurationsdateien verzichtet werden, bietet es sich an, das Setzen des Modus über die Umgebungsvariablen zu steuern.
Mit diesem ternären Ausdruck wird geprüft, ob die Umgebungsvariable NODE_ENV auf production gesetzt wird. Ist dies nicht der Fall, wird der development-Modus verwendet.
Als nächstes kann die Environment Variable beim Ausführen des Webpack-Builds mit übergeben werden. Spätestens an diesem Punkt sollte in der package.json ein scripts-Eintrag für den Build-Prozess definiert werden. Dieser kann ebenfalls genutzt werden, um den Build über eine CI/CD-Pipeline anzustoßen.
Wird unter Windows gearbeitet, kann es nun zu folgendem Fehler in der Konsole kommen:
Der Befehl „NODE_ENV“ ist entweder falsch geschrieben oder konnte nicht gefunden werden.
In diesem Fall muss das Keyword set vor der Definition ergänzt werden:
Das erstellte Skript mit gesetzter Umgebungsvariable kann jetzt mit diesem Befehl ausgeführt werden:
Auf diese Weise müssen zum Starten der verschiedenen Umgebungen nicht mehrere Konfigurationsdateien angelegt werden.
Webpack in der Entwicklung
Webpack eignet sich nicht nur für das Bundling des Outputs für die Produktionsumgebung, sondern kann bereits in der Entwicklung unterstützen.
Um die Anwendung zu testen, ohne für jede Änderung einen neuen Build ausführen zu müssen, kann der webpack-dev-server (kurz: DevServer) verwendet werden. Zunächst muss dieser installiert werden:
Wurde bereits die Webpack CLI installiert, kann der DevServer so gestartet werden:
Nach dem Starten ist im Terminal zu sehen, auf welchem Port der Server gestartet wurde:
[webpack-dev-server] Loopback: http://localhost:8080/
Sofern nicht anders definiert, startet der Server immer auf Port 8080. Wichtig zu wissen ist, dass Änderungen bei laufendem DevServer nur In Memory durchgeführt werden. Ein bereits bestehendes Bundle wird hierbei nicht aktualisiert.
Hot Module Replacement
Das Hot Module Replacement (kurz: HMR) erlaubt es, während die Anwendung läuft, neue Module hinzuzufügen oder bestehende zu entfernen, ohne dass neu kompiliert werden muss. Das ist besonders dann von Vorteil, wenn man eine Anwendung entwickelt, die sehr viel Zustandsinformationen beinhaltet. Gibt es zum Beispiel mehrere Seiten, die Schritt für Schritt befüllt und durchgeklickt werden müssen, dann würde ohne HMR bei Änderungen dieser Zustand verloren gehen. Um Änderungen auf Seite 6 zu testen, müsste jedesmal der Pfad erneut durchgeklickt werden.
Wird eine webpack-dev-server-Version verwendet, die v4.0.0 oder neuer entspricht, dann ist das HMR bereits per Default aktiviert und muss nicht konfiguriert werden.
Vorteile von Tooling
Sollte es für die Technologie des Frontends ein entsprechendes Tooling geben, dass die Webpack-Konfiguration abkapselt, dann kann es durchaus von Vorteil sein, dieses zu verwenden. Selbst dann, wenn ausreichend Wissen über Webpack im Team vorhanden ist.
Das klingt zunächst erstmal kontraintuitiv, da in wirklich den meisten Fällen (und sowieso in der vue.config.js), weitere Webpack-Schritte definiert werden müssen.
Im Fall der Vue CLI übernimmt das Tooling ebenfalls die Standardkonfiguration von Babel. Hierbei handelt es sich um einen JavaScript-Compiler, der es erlaubt, modernes JavaScript auch für ältere Browser aufzubereiten. In dieser Standardkonfiguration sind dann ebenfalls Optimierungen für die Dateigröße enthalten, wie z. B. dass nur verwendete ES5-Funktionalitäten tatsächlich im finalen Bundle landen. Wenn die Entwicklung spezifische Anpassungen erfordert, kann die Standardkonfiguration jederzeit in der Dokumentation nachgelesen werden, in diesem Beispiel unter Browser Compatibility. Diese kann dann entsprechend erweitert oder adaptiert werden.
Wird dasselbe Vue-Projekt nun komplett selbst konfiguriert, müsste das Team also nicht nur das Wissen über Webpack und den aktuellsten Stand, sondern auch den babel-loader selbst integrieren und eine entsprechende Babel-Standardkonfiguration hinterlegen.
Zu guter Letzt ist zu bedenken, dass ein fertiges Tooling-Paket sich darum kümmert, dass alles aktuell bleibt und die neuesten Features von z.B. Webpack 5 oder Babel 7 automatisch anbindet und alles kompatibel hält.
Konfiguration von Webpack – Zusammenfassung
Auch wenn Webpack-Konfigurationen zu Beginn etwas abschreckend wirken können, sind sie eigentlich sehr klar und einfach strukturiert. Wichtig hierbei ist es, die verschiedenen Schritte des Module-Bundlers zu kennen, um schnell zu verstehen, was in welchem Schritt gemacht wird oder was ergänzt werden muss. Wenn Bilddateien oder Schriftarten nicht richtig dargestellt werden, sind diese nicht richtig geladen worden. Somit kommt man schnell zu dem Schluss, dass ein entsprechender Loader hinzugefügt oder in der Konfiguration angepasst werden muss.
Sollen Optimierungen z.B. an der finalen Dateigröße vollzogen werden, dann ist dies der letzte Schritt in der Webpack-Konfiguration und den Plugins zugehörig. Mit diesem Wissen und Ziel kann also ein entsprechendes Plugin gesucht und eingebunden werden.
Weiterhin ist es wichtig zu wissen, dass Webpack verschiedene Konfigurationen für die Produktion oder die Entwicklung erlaubt. Dadurch können speziell für die Entwicklung bestimmte Tools, wie der DevServer, eingebunden werden.
Sind einmal die Grundlagen verinnerlicht, kann das Wissen auch auf andere Tools, die auf Webpack basieren, übertragen werden.
Happy Web Packing!
Titelmotiv: Pixabay
- Erste Schritte für den Einstieg in die Softwarearchitektur - 10. Juli 2023
- Vuex vs. Pinia: Ein direkter Vergleich - 16. November 2022
- Eine Einführung in den Module-Bundler Webpack mit ersten Konfigurationsschritten - 15. April 2022