Einführung in webpack

Vielleicht hast du schonmal was von webpack gehört oder versuchst gerade zu verstehen, was das eigentlich ist. Ich will dir damit mal ein bisschen unter die Arme greifen und führe dich durch die Grundlagen, damit du die Welt retten kannst, falls webpack dazu mal nötig sein sollte (die Wahrscheinlichkeit ist gering).

Wofür brauche ich webpack?

Wenn du Schwerter schmiedest oder Erze am Hochofen verarbeitest, hast du vermutlich wenig mit webpack zu tun, denn das benötigt man für die Frontend-Entwicklung, Stichwort HTML, CSS und JavaScript.

Natürlich kannst du all diese Dateien selbst programmieren und genauso dem Browser zur Verfügung stellen, wie du sie geschrieben hast, aber das hat ein paar Nachtteile:

  • Dein Code kann noch komprimiert werden (z.B. Entfernung von Leerzeichen).
  • Du hast u.U. viel zu viele Dateien, gerade wenn du externe Libraries einsetzt.

Schön wäre also ein Tool, das deine CSS- und JS-Dateien “zusammenpackt” und schön mit Schleifchen für den Benutzer verschnürt – dir beim Entwickeln aber hilft.

Achtung Spoiler: Und genau das macht webpack.

Übliche Einsatzorte

webpack solltest du immer da benutzen, wo du mit CSS- und JS-Dateien arbeitest. Denn damit fällt auch der Einsatz von anderen Tools, z.B. Sass oder Bootstrap, sehr viel einfacher. Wenn du nicht weißt, was das ist, kein Problem. Das gehen wir gleich Schritt für Schritt durch.

Voraussetzungen schaffen

Einmal tief durchatmen, denn wenn du was Neues lernst, brauchst du Geduld.

Zunächst einmal müssen wir webpack installieren. Dazu benötigen wir aber folgendes:

  • NodeJS: Sozusagen die Basis für alles, was in JavaScript geschrieben wurde und auf Servern laufen soll (wie z.B. webpack).
  • Yarn: Ein Paket-Manager, der dir das Installieren von Libraries unendlich einfach macht.
  • Noch weiteres Zeugs, das wir aber nach und nach über Yarn installieren werden.

NodeJS gibt es für viele Betriebssysteme. Ich konzentriere mich hier mal auf Ubuntu 18.0 und die NodeJS Version 12.

# Curl installieren
sudo apt update
sudo apt install curl

# Node JS 12 installieren
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt install -y nodejs

# Yarn installieren
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update && sudo apt-get install yarn

Wenn alles funktioniert, gibt dir yarn -v eine Versionsnummer zurück.

Anmerkung: Solltest du Probleme haben, die richtige Version von Node zu installieren (node -v), dann nutze am Besten den Node Version Manager. Hier kannst du über einfache Befehle die Node-Version wechseln (z.B. nvm install 12, nvm ls, nvm use 12, nvm alias default 12). Dann solltest du aber auch den Ordner node_modules gleich mitlöschen und über yarn install neu installieren, da dein Projekt u.U. nicht kompiliert werden kann.

webpack mit Yarn installieren

Halt Stopp! Bevor wir webpack installieren, müssen wir uns über die Verzeichnisstruktur des Projekts ein paar Gedanken machen. Sorry. Geht auch schnell.

  • index.html: Die Datei, die wir nachher im Browser aufrufen.
  • /src: Unsere Dateien, aus denen webpack die Pakete schnürt.
  • /dist: Die kompilierten Dateien, die der Browser aufruft.

Das sollte dir aber nicht als Vorbild für deine echten Projekte dienen. Achte darauf, dass du die src-Dateien niemals auf dem Live-Web-Server speicherst (es sei denn, du kompilierst die Dateien dort, was wir aber in diesem Beispiel nicht tun).

Jetzt bist du dran: Erstelle einen Ordner mit einem laufenen Web-Server. Tipps dazu findest du hier. Entwickle auch schon ein HTML-Grundgerüst in der Datei index.html – hier fügen wir gleich die CSS- und JS-Dateien hinzu.

Wechsle jetzt in diesen Ordner. Alle Libraries (inkl. webpack), die wir über yarn installieren, landen automatisch im Ordner node_modules. Das bedeutet, das wir nichts global installieren, sondern alles, was wir für dieses Projekt benötigen, lokal zu finden ist.

Falls du yarn noch nicht so gut kennst: Wenn du ein Paket über Yarn installierst, merkt es sich, welche Version du benutzt (in der Datei yarn.lock). Wenn dein Kollege deinen Source-Code bekommt, führt er den Befehl yarn install aus und nutzt damit die gleiche Version wie du. Tipp für Git: Das Verzeichnis node_modules solltest du also immer ignorieren, sonst sammeln sich hier Trilliarden Dateien an…

Jetzt passiert es aber endlich: Wir installieren webpack!

# webpack über Yarn installieren
yarn add webpack webpack-cli --dev

Ziemlich unspektakulär. Deine Verzeichnisstruktur sollte jetzt so aussehen:

webpack einrichten und JS-Datei kompilieren

Damit webpack läuft, benötigt es die Datei webpack.config.js – also legen wir die mal an:

const path = require('path');

module.exports = {
	mode: 'development',
	entry: {
		global: './src/global.js',
	},
	output: {
	    filename: '[name].min.js',
	    path: path.resolve(__dirname, 'dist')
	}
};

Diese Datei sagt webpack: Schaue in die Datei src/global.js und erstelle daraus eine Datei dist/global.min.js.

Also erstellen wir erstmal die Datei src/global.js:

// Einfach mal hallo sagen
console.log('Hallo Welt');

Um den Vorgang zu starten, weisen wir Yarn an, webpack zu nutzen:

yarn webpack

Wenn du das erfolgreich ausgeführt hast, solltest du die Datei dist/global.min.js vorfinden.

Das ist auch die Datei, die wir in die Datei index.html einbinden:

<script src="dist/global.min.js"></script>

Das Ergebnis sieht dann so aus:

Wenn du dir die Datei global.min.js jetzt mal anguckst, fällt dir auf, dass da viel mehr steht, als du eigentlich wolltest. Das liegt an zwei Gründen:

  • webpack schafft sich eine eigene Variablen-Umgebung.
  • Die Datei wurde im Development-Modus generiert.

Bei webpack (oder auch anderen Entwicklungsumgebungen) hast du üblicherweise zwei “Environments”: development und production. Beide spucken andere Ergebnisdateien aus – die einen helfen dir beim Debuggen, die anderen nicht (und sollten deshalb bei Live-Umgebungen eingesetzt werden).

In diesem Fall siehst du in der Datei webpack.config.js, dass der Development-Mode genutzt wird.

Jetzt kompileren wir das ganze mal im Production-Mode:

yarn webpack --mode=production

Der Parameter überschreibt die Default-Werte in der Config-Datei. Wenn du deine Seite neu lädst, sieht die JS-Datei schon ganz anders aus:

Im Production-Mode wird automatisch das Terser-Plugin genutzt. Das bedeutet, dass dein JavaScript ein bisschen umgeschrieben und verkürzt wird. Ein Beispiel:

Das Terser-Plugin ersetzt deine Variablennamen z.B. durch kürzere. In diesem Beispiel ist der Production-Code verschönert, d.h. in der Realität findest du hier keine Absätze und Einrückungen.

Tipp für Zwischendurch: watch

Möchtest du jedesmal, wenn du deinen Browser aktualisierst (Achtung: Cache deaktivieren) vorher den Aufruf über webpack machen? Ja? Falsch, das war eine rethorische Frage. Du willst das natürlich nicht. webpack kann im Hintergrund laufen und direkt kompilieren, während du speicherst:

yarn webpack --watch

Probier’s mal aus!

Ein bisschen was zur Erklärung: Entries

Gucken wir uns die Config-Datei nochmal genauer an. Hier gibt es das Objekt entry, in dem wir global definieren und diesem eine Datei zuweisen.

Entries sind die Einstiegspunkte (Überraschung), um die finalen Dateien zu kompilieren. In unserem Fall dient [name] als Platzhalter für den Entry-Namen. Es liegt an dir, wie und in welchen Dateien du dein Projekt organisierst.

Ein bisschen was zur Erklärung: Variablen (Scope)

Teste mal folgendes: Setze eine Variable in der Datei global.js und versuche diese in der HTML-Datei auszugeben, ungefähr so:

Mööööööp! Geht nicht. Das liegt daran, dass meinTest nur in der Datei global.js gültig ist. Damit es trotzdem funktioniert, gibt es viele Möglichkeiten.

Die einfachste: global. hinzufügen.

jQuery hinzufügen

jQuery braucht man ja irgendwie öfter, deshalb nutzen wir das mal als Beispiel, wie man externe JS-Libs in sein Projekt lädt.

Als erstes schauen wir auf yarnpkg.com nach, wie das Paket heißt. Auf der rechten Seite findest du die Installationsanweisung, die ganz einfach so lautet:

yarn add jquery

Das legt alle Dateien in node_modules ab. Jetzt musst du sie nur noch in deiner global.jsüber import importieren.

Ich screenshotte bewusst, damit du tippen musst. Das reimt sich.

Den Befehl import nutzt du immer dann, wenn du JS-Libs einbindest. Alternativ geht auch require, aber dann muss man sich damit auseinandersetzen, was der Unterschied ist. Laaaaaangweilig.

CSS und Sass zur Party einladen

webpack kann aber noch mehr als JS verpacken: CSS und Sass! Dazu müssen wir noch ein paar Pakete installieren:

yarn add node-sass sass-loader css-loader mini-css-extract-plugin

Jetzt zur Theorie: SCSS-Dateien bindest du per require über die jeweiligen JS-Dateien ein. Jo. Erstmal sacken lassen.

In unserer folgenden Konfiguration ist es so, dass alle Styles, die in der Datei global.js eingebunden werden, in einer Datei global.min.css gespeichert werden (also dem Entry-Namen). Das sieht in der Praxis so aus:

Dazu solltest du natürlich auch eine style.scss angelegt haben.

Bevor webpack das aber kompilieren kann, muss die Datei webpack.config.js angepasst werden, dass sie auf .scss-Dateien (und andere) reagiert.

const path = require('path');

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
	mode: 'development',
	entry: {
		global: './src/global.js',
	},
	output: {
		filename: '[name].min.js',
		path: path.resolve(__dirname, 'dist')
	},
	plugins: [
		new MiniCssExtractPlugin({
			filename: '../dist/[name].min.css'
		}),
	],
	module: {
		rules: [
			{
				test: /\.(sa|sc|c)ss$/,
				use: [
				  {
					loader: MiniCssExtractPlugin.loader,
					options: {
						hmr: process.env.NODE_ENV === 'development',
					},
				  },
				  'css-loader',
				  'sass-loader',
				],
			}
		]
	}
};

Mit yarn webpack solltest du nun die Datei style.css im Ordner dist vorfinden. Diese kannst du in dein HTML einbinden.

Zusatzinfo: Das Plugin MiniCssExtractPlugin sorgt dafür, dass die CSS-Klassen in einer eigene Datei gespeichert werden. Ansonsten würde webpack diese einfach mit in die JS-Dateien packen. Das erzeugt allerdings einen kleinen Overhead und macht nicht immer Sinn.

Das war’s im Groben!

Externe URLs in CSS-Dateien

Ein bisschen komplizierter wird es, wenn du in deiner SCSS-Datei eine externe URL referenzierst, ungefähr so:

Legst du die JPG-Datei in den Ordner src, dann gibts Mega-Ärger vom Compiler:

Böse böse! Das liegt daran, dass es mehrere Möglichkeiten gibt, diese JPG-Datei einzubinden:

  • Das scheinbar Eingängigste: Der Pfad in url(..) ist relativ zum Basepath, d.h. die Datei liegt schon auf dem Web-Server und webpack soll das auch bitteschön so 1:1 in die CSS-Datei speichern.
  • Die Datei befindet sich im src-Ordner und soll beim Komplieren in den Dist-Ordner kopiert werden. Das macht Sinn, wenn die Datei von webpack noch bearbeitet werden soll (z.B. zerschnitten, etc.), ist aber auch sonst ein guter Standard.
  • Die Datei soll direkt als Base64 in die CSS-Datei eingebunden werden. Ja, das geht auch. Anstatt den Browser eine JPG-Datei laden zu lassen, ist diese komplett in der CSS-Datei vorhanden. Das macht aber nur bei vielen kleinen Dateien Sinn.

Nehmen wir mal das mittlere als Beispiel: Alle externen Grafiken liegen im src-Ordner und sollen beim Kompilieren kopiert werden. Alle anderen Möglichkeiten kannst du dann selbst recherchieren (sorry, Stichwort url-loader und resolve-url-loader).

Hierzu brauchst du den geeigneten Loader, genauso wie der bekannte sass-loader:

yarn add file-loader

Den baust du so in die Config ein:

Hier kannst du auch schon sehen, dass der Reg-Ex-Test dafür sorgt, welcher Loader geladen wird. Probiere es mal aus – lege die JPG Datei in den Ordner src und starte webpack!

webpack hat sich die Original-Datei gegriffen und den Dateinamen selbständig in einen Hash-Code umgewandelt. Das ist ganz praktisch, denn wenn sich die Datei ändert, ändert sich auch der Dateiname und du hast überhaupt keine Cache-Probleme. Allerdings bleiben die alten Dateien noch bestehen – man kann diese natürlich auch über das Plugin clean-webpack-plugin löschen.

Möchtest du den Namen beibehalten, erweitere den Loader um Options:

Das hat natürlich den Nachteil, dass der Browser die Datei aus dem Cache lädt und nicht unbedingt frisch vom Server. Das führt uns zu…

Versioning

Das berühmte Problem: Der Browser-Cache. Natürlich kannst du den über einen HTTP-Header unterdrücken, aber das beste ist, die Dateinamen anzupassen. Ohne viel Aufwand geht das so:

Das erzeugt eindeutige Dateien, die man allerdings natürlich auch so in den Browser einbinden muss. Damit das automatisch geht, hilft das Plugin webpack-manifest-plugin.

So wird eine manifest.json erzeugt, die du z.B. mit PHP auslesen kannst.

Es gibt aber verschiedene Varianten dieses Asset-Managements. Du musst dir die raussuchen, die zu dir passt. Beispiele sind:

  • Symfony nutzt die eigene Library Encore, um webpack ein bisschen umgangsfreundlicher zu machen. Hier werden direkt Funktionen in der Template-Engine Twig zur Verfügung gestellt.
  • In WordPress nutzt du die Funktionen wp_register_script(), bzw. wp_register_style(), die einen Parameter für die Versionierung (z.B. Dateizeit) beim Aufruf anhängen.

Ich hoffe, du hast jetzt erstmal einen guten Start in die Welt von webpack erhalten – es gibt noch viel zu entdecken! Doch Achtung: Fokussiere dich auf das Produkt – verliere dich nicht in der Optimierung.

Björn Falszewski
3. Juni 2020
Disclaimer
Alle meine Artikel entstehen mit bestem Wissen und Gewissen, sind aber nicht perfekt und sollten immer nur als Ausgangspunkt für deine eigenen Recherchen bilden.

Sollte dir etwas Fehlerhaftes auffallen, freue ich mich über deine Nachricht!