© Excellent backgrounds/Shutterstock.com
Teil 3: Test und Build

Das Ziel ist das Ziel


Die Entwicklung von Single Page Applications (SPA) stellt den Entwickler vor viele Aufgaben, um eine produktionsreife Applikation zu erhalten. Dabei sollte auch das automatisierte ­Testen nicht zu kurz kommen.

Um aus den einzelnen Dateien eines Angular-Projekts eine lauffähige Software für den Browser zu erhalten, sind einige Schritte notwendig. Für diese Aufgabe eignet sich der Module Bundler webpack [1] besonders gut. Durch einen definierten Einstiegspunkt wie etwa main.ts oder vendor.ts einer typischen Angular-Anwendung löst das Tool alle referenzierten Dateien (Module) durch alle Ebenen auf, die dann verarbeitet, transformiert sowie optimiert und als Bundle bereitgestellt werden.

Durch die Nutzung von Frameworks wie Angular und seiner Konzepte wandert natürlich auch immer mehr Logik in den Clientteil der Applikation. Manuelles Testen reicht nach kurzer Zeit nicht mehr aus, um die fortlaufende Stabilität der eigenen Anwendung zu gewährleisten und deren Komplexität zu kontrollieren. Automatisierte Tests helfen einem jedoch nicht nur dabei, Funktionalität schnell zu prüfen, sie beeinflussen auch positiv die Art, wie Programmcode geschrieben wird. Dieser Artikel beschreibt anhand eines Beispiels [2] den Umgang mit webpack sowie die Nutzung von Karma und Jas­mine, um Unit-Tests zu erstellen und daraus Reports für CI-Umgebungen wie Jenkins zu generieren. Außerdem gehen wir darauf ein, wie diese Tasks über den Node-Package-Manager npm in einen Maven-Build-Prozess integriert werden können, sodass am Ende eine fertige JAR-Datei zur weiteren Verarbeitung vorliegt.

Projekte mit webpack

Als Module werden nicht nur TypeScript-Dateien angesehen, sondern sämtliche Dateien, die über Referenzen erkennbar sind. So kann webpack neben den üblichen, wie etwa import oder require, auch beispielsweise src: url('…') in CSS-Dateien oder <img src="…" /> in HTML auflösen. Damit webpack weiß, wie gefundene Dateien verarbeitet werden müssen, ist es notwendig, so genannte Loader zu definieren. Diese reagieren anhand einer selbst definierten Regex auf passende Dateien.

Oft wird webpack als Task-Runner wie Grunt oder gulp missverstanden. Task-Runner arbeiten konfigurierte Tasks wie das Kopieren oder Komprimieren von Dateien nacheinander ab. Das Gesamtergebnis der einzelnen Tasks ist für den Task-Runner nicht relevant. webpack kann in diesem Zusammenhang als ein Task angesehen werden. Obwohl das Tool durch Plug-ins erweiterbar ist und somit teilweise einen Task-Runner imitiert, besteht die primäre Aufgabe darin, ein lauffähiges Bundle aus einzelnen zusammenhängenden Modulen zu erhalten.

Listing 1 zeigt die notwendigen Node Packages sowie den Task build. Damit lässt sich ein Bundle über webpack erstellen. Loader sind nicht in webpack enthalten und müssen somit immer extra installiert werden. Das Gleiche gilt für die meisten Plug-ins.

Listing 1: Notwendige Node-Packages und build-Task

{ "name": "angular-build-test", "scripts": { "build": "webpack" }, "dependencies": { […] }, "devDependencies": { "@types/webpack": "2.2.7", "awesome-typescript-loader": "3.0.8", "html-webpack-plugin": "2.28.0", "webpack": "2.2.1", "webpack-dev-server": "2.4.1" } }

Einstiegspunkte festlegen

Wie zu Beginn erwähnt, benötigt webpack einen Einstiegspunkt. Für die Beispielanwendung ist das die Datei main.ts. Als Bootstrap für Angular verbindet sie auch für webpack alle relevanten Dateien. Über die darin enthaltenen import-Statements (Listing 2) kann so ein Dependency-Graph aufgebaut und aufgelöst werden. Der zweite Einstiegspunkt vendor.ts enthält import-Statements externer Libraries sowie Polyfills. Dadurch kann webpack selbst erstellten Code von externen trennen und ihn als eigenes Bundle ausliefern.

Listing 2: Dependency-Graph

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { enableProdMode } from '@angular/core'; import { AppModule } from './app/app.module'; if (ENV === 'prod') { enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule);

Um diese Einstiegspunkte nutzen zu können, wird zunächst eine Konfigurationsdatei im Projekthauptverzeichnis für webpack erstellt. Der Standardname ist hier webpack.config.js. In Listing 3 sind die beiden Dateien nun unter entry konfiguriert. webpack erwartet sich hier ein Node-Modul, das über module.exports angegeben ist. Entweder kann ein Objekt oder – wie hier – eine Funktion zurückgeliefert werden. Der Vorteil einer Funktion liegt darin, dass so über das Kommandozeilentool eine Umgebungsvariable verwendbar ist. Dazu später mehr. Der Bereich output legt fest, wo webpack das Ergebnis speichert. Der Platzhalter [name] steht für die unter entry definierten Keys app und vendor. Der Pfad /target/classes/META-INF/resources stellt sicher, dass Maven die Dateien später auch gleich weiterverarbeiten kann. Die Konfiguration extensions im Bereich resolve weist webpack an, bei Referenzen, die keinen Dateityp aufweisen, nach Dateien mit den Werten des Arrays als Endung zu suchen. Somit muss bei Anweisungen wie import { AppModule } from './app/app.module'; nicht '…module.ts'; angegeben werden.

Listing 3: Konfigurationsdateien erstellen

module.exports = function (env) { return { entry: { 'app': './src/main.ts', 'vendor': './src/vendor.ts' }, output: { path: __dirname + '/target/classes/META-INF/resources', filename: '[name].js' }, resolve: { extensions: ['.ts', '.js'] }, […] } };

Loader und Plug-ins definieren

Das Ausführen von npm run build löst bereits jetzt die Abhängigkeiten auf und legt die Dateien app.js und vendor.js an. Der Durchlauf führt jedoch zu Fehlern in der Konsole, und auch der Inhalt der gebauten Dateien ist nicht korrekt. Das liegt daran, dass noch keine Loader konfiguriert sind. Somit weiß webpack nicht, wie mit gefundenen Dateien wie TypeScript, aber auch CSS, HTML, Bildern oder Fonts umzugehen ist. JavaScript- und JSON-Dateien versteht webpack auch ohne Loader. Über den Bereich module.rules kann ein Array mit Loader-Konfigurationen angelegt werden. Wie in Listing 4 zu sehen ist, benötigt die Konfiguration dabei immer zwei Parameter. Über den Parameter test ist eine Regex angegeben. Hier kann nicht nur auf die Dateiendung, sondern auch auf den gesamten Namen sowie Pfad abgefragt werden. Alle gefundenen TypeScript-Dateien kann somit der Loader awesome-typescript-loader verarbeiten, definiert durch den Parameter use. Für jede Datei, die webpack findet, muss eine entsprechende Loader-Definition existieren.

Listing 4: Ein Array mit Loader-Konfigurationen anlegen

module.exports = function (env) { return { […] module: { rules: [ { test: /\.ts$/, use: [ 'awesome-typescript-loader', 'angular2-template-loader' ], exclude: /node_modules/ } ] }, […] }; };

Im Unterschied zu einem Loader haben Plug-ins Zugriff auf den gesamten Ablauf und können so auch allgemeine Funktionalität einbringen. Einige davon kommen direkt mit webpack, bei anderen muss der Entwickler das entsprechende npm-Paket installieren. Das Modul selbst wird dabei über require in die Konfiguration geladen. Plug-ins können auch Loader zur Verfügung stellen, falls die Anforderung dies notwendig macht. Durch den Einsatz von CommonsChunkPlugin (Listing 5) wird webpack angewiesen, alle Referenzen, die über den Einstiegspunkt vendor definiert sind, nur in vendor.js zu integrieren, jedoch nicht nochmals in anderen Dateien wie app.js. Um alle Dateien zu einer lauffähigen Anwendung zu verbinden, brauchen wir zudem noch die...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang