Webpack und Angular 2: Module Loading und Build-Workflows ohne Stress und Kopfzerbrechen

In meinem letzten Beitrag habe ich gezeigt, warum das NodeJS-Modul-System die Arbeit mit Frameworks und Bibliotheken drastisch vereinfacht. Daneben habe ich den Einsatz von webpack zum Bundling von NodeJS-Modulen motiviert. Dieses Bundling führt die NodeJS-Module auch in eine Form, die eine direkte Ausführung im Browser ermöglicht, über.

Dieser Beitrag zeit nun, wie webpack gemeinsam mit Angular 2 genutzt werden kann. Das dazu genutzte Beispiel findet sich hier. Ein einfaches Starterkit für den Einsatz von Angular 2 und webpack findet man hier. Ein komplexeres Starterkit ist hingegen hier zu finden.

Erste Schritte

Damit TypeScript beim Kompilieren den Quellcode in das von NodeJS genutzte Common-JS-Modulformat überführt, erhält in der Datei tsconfig.json die Eigenschaft module den Wert commonjs:

{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "removeComments": true,
    "sourceMap": true
  },
  "exclude": [
      "node_modules",
      "dist"
  ]
}

Darüber hinaus legt hier die Eigenschaft exclude fest, dass beim Kompilieren die Ordner node_modules und dist zu ignorieren sind. Letzterer beherbergt die generierten Bundles.

Webpack kann, wenig überraschend, über npm bezogen werden. Die nachfolgenden Anweisungen installieren neben webpack auch den webpack-dev-server, der u. a. bei Dateiänderungen das Bundling neu durchführt und daraufhin die Anwendung im Browser aktualisiert. Darüber hinaus installieren diese Anweisungen auch Typescript sowie den TypeScript-Loader für webpack, welcher im Zuge des Bundlings TypeScript-Dateien kompiliert:

npm install webpack -g
npm install webpack-dev-server -g
npm install typescript --save-dev
npm install ts-loader --save-dev

Zur Vereinfachung geht der vorliegende Text davon aus, dass webpack und webpack-dev-server mit dem Schalter -g global installiert wurden. Um Konflikte zwischen Projekten zu vermeiden bietet es sich jedoch an, diese lokal zu installieren und über einen npm-Task zu starten.

Das Konfigurieren von webpack übernimmt die Datei webpack.config.js im Root der Anwendung. Die Kommentare im nachfolgenden Listing beschreiben die einzelnen Eigenschaften:

module.exports = {
  // Source-Maps zum Debuggen von TypeScript im Browser
  // generieren.
  devtool: 'source-map',
  debug: true, 
  // Einstiegspunkt in die Anwendung 
  // Erweiterung .js bzw. ts. wird weggelassen
  entry: {
    app: "./app/app"
  },
  // Legt fest, dass das Bundle im Ordner dist abzulegen
  // ist. Der Platzhalter [name] wird durch den Namen des
  // Einstiegspunktes (app) ersetzt.
  output: {
    path: __dirname + "/dist", 
    publicPath: 'dist/', 
    filename: "[name].js" 
  },
  // Dateien mit den nachfolgenden Erweiterungen werden
  // von webpack ins Bundle aufgenommen
  resolve: {
    extensions: ['', '.js', '.ts']
  },
  // Hier wird der TypeScript-Loader konfiguriert. Er gibt
  // an, dass alle Dateien mit der Endung ts mit diesem Loader
  // zu kompilieren sind. Das Ergebnis dieses Vorgangs wird
  // ins Bundle aufgenommen. Die Eigenschaft test verweist
  // auf einen regulären Ausdruck, der die zu kompilierenden
  // Dateien identifiziert. Die Eigenschaft exclude gibt an,
  // das die Bibliotheken im Ordner node_modules nicht zu 
  // kompilieren sind.
  module: {
    loaders: [
        { test: /\.ts$/, loaders: ['ts-loader'], exclude: /node_modules/}
    ]
  },
  // Gibt an, dass der webpack-dev-server ein Code in die
  // in die Bundles aufnimmt, welcher den Browser nach einer
  // Änderung des Bundles aktualisiert.
  devServer: {
    inline: true
  }
};

Nun genügt ein Aufruf von webpack um das Bundle zu erzeugen:

>webpack 
ts-loader: Using typescript@1.7.5 and [...]tsconfig.json
Hash: f1eceadb9a18128980b5
Version: webpack 1.12.9
Time: 5468ms
 Asset     Size  Chunks             Chunk Names
app.js  4.85 MB       0  [emitted]  app
    + 276 hidden modules

Das bereitgestellte Beispiel verwendet standardmäßig eine etwas umfangreichere Konfiguration. Die hier für den Einstieg hier vorgestellte Konfiguration stellt es jedoch über die Datei webpack-simple.config.js zur Verfügung. Damit webpack diese nutzt, ist der Schalter --config zu verwenden: webpack --config webpack-simple.config.js

Webpack beginnt mit dem Bundling bei jener Datei, die den Einstiegspunkt darstellt, und nimmt sämtliche direkte und indirekte Abhängigkeiten in das Bundle auf. Diese ermittelt es aufgrund der Verweise in den Quellcodedateien, welche der Entwickler bei Nutzung von TypeScript bzw. EcmaScript 6+ mit import-Anweisungen festlegt.

Handelt es sich bei den Abhängigkeiten um andere Dateien des Projektes, findet webpack sie über die relativen Pfadangeben in den import-Anweisungen. Bibliotheken, wie Angular 2, referenziert der Entwickler hingegen über vordefinierte Namen. Diese löst webpack unter Berüscksichtigung der Regeln des NodeJS-Module-Systems auf. Das bedeutet, vereinfacht ausgedrückt, dass webpack sie im Ordner node_moduels sucht. Das selbe gilt für Abhängigkeiten dieser Bibliotheken. Da npm heruntergeladene Bibliotheken ohnehin nach diesen Regeln im Ordner node_modules ablegt, muss sich der Entwickler darüber in der Regel nicht den Kopf zerbrechen.

Somit entsteht im betrachteten Fall ein Bundle app.js, welches den eigenen Quellcode inkl. aller referenzierten Bibliotheken beinhaltet sowie direkt im Browser lauffähig ist. Dieses Bundle ist lediglich in die Web-Anwendung aufzunehmen. Das nachfolgende Beispiel veranschaulicht das. Es bindet neben dem Bundle app.js auch die Datei angular2-polyfills.js ein, da sich diese nicht im Bundle befindet. Um sie ins Bundle aufzunehmen, müsste der Einstiegspunkt sie lediglich mit einer import-Anweisung referenzieren.

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Angular2 Demo</title>
    <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
</head>

<body>
    <app>Loading...</app>
</body>

<script src="/node_modules/angular2/bundles/angular2-polyfills.js"></script>

<script src="dist/app.js"></script>

</html>

Das aus dem Quickstart-Sample von angular.io bekannte Einbinden der einzelnen Angular-2-Bundles sowie das Laden des eigenen Quellcodes über System.import entfällt hier. Weitere Bibliotheken lassen sich einfach per npm laden sowie über import-Anweisungen referenzieren - Webpack übernimmt sie automatisch ins Bundle.

Bundle für den Produktionsbetrieb generieren

Bundles für den Produktionsbetrieb sollen möglichst klein sein, um die Ladezeiten zu verbessern. Dies wird u. a. durch das Weglassen von Source-Maps sowie durch Minifizieren erreicht. Um Webpack anzuweisen, ein solches Bundle zu generieren, ist lediglich der Schalter -p (für Production) zu nutzen:

webpack -p

Hat ein Entwicklungs-Team für unterschiedliche Szenarien unterschiedliche Webpack-Konfigurationsdateien, kann es beim Aufruf von webpack über den Schalter --config angeben, welche Konfigurationsdatei zu nutzen ist:

webpack --config webpack-simple.config.js

Dieser Schalter lässt sich auch bei der Generierung von Produktions-Bundles nutzen:

webpack --config webpack-simple.config.js -p

Kompilierung durch IDE verhindern

Da hier webpack über den TypeScript-Loader das Kompilieren von TypeScript übernimmt, macht es keinen Sinn, TypeScript auch noch zusätzlich über die IDE zu kompilieren. Trotzdem ist es wichtig, dass die Entwicklungsumgebung mit dem TypeScript-Compiler den geschriebenen Code auf Korrektheit prüft. Um dies zu erreichen, kann der Entwickler den TypeScript-Kompiler anweisen, im Zuge der Kompilierung keine Dateien zu generieren. Dazu bietet der Kompiler den Schalter --noemit an.

Beim Einsatz von Visual Studio Code ist dieser Schalter in der Datei .vscode\tasks.json, welche die einzelnen Build-Tasks beschreibt, innerhalb der Eigenschaft args zu hinterlegen:

{
    "version": "0.1.0",
    "command": "tsc",
    "isShellCommand": true,
    "showOutput": "silent",
    "args": ["--noemit"],
    "problemMatcher": "$tsc"
}

Entwicklungs-Server nutzen

Der webpack-dev-server kümmert sich um das Bundling beim Start der Anwendung und generiert bei Dateiänderungen das Bundle neu. Danach benachrichtigt er den Browser, sodass sich dieser die Web-Anwendung neu lädt.

Zum Starten des Servers ist lediglich im Root der Anwendung die folgende Anweisung aufzurufen:

webpack-dev-server

Standardmäßig nutzt der Server das Port 8080. Schalter und Einträge in der webpack.config.js geben jedoch die Möglichkeit, auch andere Ports zu nutzen.

Um die zeitliche Verzögerung für das erneute Generieren des Bundles zu minimieren, macht es Sinn, die einzelnen Bundles möglichst klein zu halten. Dazu bietet webpack die Möglichkeit, die einzelnen Dateien auf mehrere Bundles aufzuteilen. So kann der Entwickler beispielsweise die Bibliotheken, die sich im Rahmen der Entwicklung nicht ändern, in ein eigenes Bundle aufnehmen. Der eigene Quellcode landet somit in einem kleineren Bundle, das webpack bei Änderungen rascher neu generieren kann. Damit eine Anwendung nicht sämtliche Bundles beim Start laden muss, bietet webpack die Möglichkeit, diese erst bei Bedarf nachzuladen. Dieses Thema werde ich in einem der nächsten Beiträge beschreiben.

Fazit und Ausblick

Webpack erlaubt die Nutzung des NodeJS-Modulsystems und erleichtert somit das Einbinden von Bibliotheken sowie den Umgang mit Abhängigkeiten enorm. Im Zuge des Bundlings transformieren konfigurierte Loader die Quelldateien. Auf diese Weise lässt sich beispielsweise eine Kompilierung von TypeScript-Dateien durchführen. Daneben existieren zahlreiche weitere Loder, die zum Beispiel das Aufnehmen von HTML-Templates oder CSS-Dateien in das Bundle erlauben. Dazu wandeln Sie den Inhalt dieser Dateien in JavaScript-Strings um. Informationen dazu bietet ein nachfolgender Beitrag zu diesem Thema.

Im Gegensatz zu Lösungen wie Gulp oder Grunt arbeitet webpack nicht imperativ sondern deklarativ. Das bedeutet, dass der Entwickler die gewünschten Aufgaben nicht Schritt für Schritt beschreiben muss, sondern lediglich das gewünschte Endresultat definiert. Für Standardaufgaben bringt das eine Erleichterung beim Konfigurieren. Kommt der Entwickler damit nicht aus, kann er Webpack über Gulp oder Grunt anstoßen.

Webpack kommt auch mit einem einfachen Web-Server, der das Entwickeln mit webpack vereinfacht. Er kümmert sich um ein Rebundling nach Dateiänderungen und aktualisiert danach den Browser. Damit das Rebundling nicht zu lange dauert, kann webpack die einzelnen Dateien auf mehrere Bundles aufteilen. Somit muss der Server nur das jeweils betroffene Bundle neu generieren und in den Browser laden. Auch ein verzögertes Laden einzelner Bundles zur Laufzeit ist möglich. Auf diesen Aspekt werde ich in einem weiteren Beitrag eingehen.

 

 
Sie wollen mehr zum Thema Webpack und Angular 2: Module Loading und Build-Workflows ohne Stress und Kopfzerbrechen wissen? Hier können Sie eine Anfrage für eine unverbindliche Schulung ode Beratung bzw. einen Workshop erstellen.
 
Unverbindliche Anfrage
 
 

Schulung und Beratung

Angular 2

Datenbindung, Formulare, Validierung, Routing, HTTP, Komponenten, ...

Details

Migration auf Angular 2

Bestehende Projekte auf Angular 2 migrieren, ngUpgrade, ...

Details

Angular 2: Deep Dive

Erweiterte Aspekte von Angular 2

Details

Progressive Web-Apps mit Angular 2

InHouse-Schulung und/oder Beratung maßgeschneidert für Ihre Lernziele

Details

Angular 2 Review

Feedback und klärung offener Fragen, weiterführende Themen

Details

Angular 2 Workshop

Start ohne Umwege

Details

Weitere Schulungen ...