Grunt, Karma, Webpack und Co. – Toolchains für JavaScript-Projekte

Keine Kommentare

An JavaScript-Lösungen kommt man auch in Enterprise-Projekten kaum mehr vorbei. Die Größe und Komplexität dieser Lösungen steigt somit ebenfalls. Die JavaScript Community hat für diesen Zweck neben vielen Patterns und Best Practices eine breite Palette an Tools entwickelt, die das Entwickeln solcher Anwendungen viel komfortabler macht.

Ich würde sogar wagen zu behaupten, dass sich das JavaScript-Tooling fast auf Augenhöhe mit den Java-Pendants befindet. Leider sind diese Möglichkeiten im klassischen Java-Umfeld oftmals nicht bekannt. Zugegeben befindet sich das JavaScript-Ökosystem im Vergleich zum Java-Umfeld viel stärker im Fluss.
Wenn man versucht, sich von Null an einen Überblick zu verschaffen, stößt man zwangsläufig auf viele verschiedene Begriffe und Tools. Einige kennt man vielleicht, andere hat man mal gehört, wieder andere sind gefühlt letzte Woche erst auf der Bildfläche erschienen, aber die ganze Welt redet davon.

Ich möchte mit diesem Artikel einen kleinen Überblick über aktuell verwendete Tools und Begriffe im JavaScript-Umfeld geben, die ich (höchst subjektiv!) für erwähnenswert halte. Darüber hinaus möchte ich diese Begriffe einsortieren und Empfehlungen für die Verwendung geben.

Die Aufstellung ist natürlich nicht vollständig, und ich werde auch versuchen, nicht mit zu vielen Details zu langweilen. 🙂
Ich werde bewusst die mannigfaltigen Anwendungsframeworks und Bibliotheken außen vor lassen und mich auf Tools konzentrieren, die vielleicht schon bei einer bestehenden JavaScript-Anwendung helfen können.

Fahrplan

Anfangen möchte ich mit der Basistechnologie (Node.js) in diesem Umfeld. Dann werde ich Tools ansprechen, die dem Entwickler helfen, seine Dependencies und Artefakte zu verwalten. Anschließend werde ich einige Tools vorstellen, die den Build einer JavaScript-Anwendung vereinfachen können. Auch Möglichkeiten, eigene Anwendungen zu modularisieren, werde ich ansprechen. Als nächstes stelle ich verbreitete Tools zum Testen vor. Beschließen möchte ich den Artikel mit einer Sammlung weiterer nützlicher Tools, die das Entwicklerleben angenehmer machen können.

Grundlagen

Im JavaScript-Ökosystem verfolgen die Entwickler fast durchgängig den sogenannten Unix-Way: kleine Tools, die eine Aufgabe gut machen; für andere Aufgaben gibt es dann das nächste Tool.
Wenn man hauptsächlich im Java-Umfeld unterwegs ist, verwendet man wahrscheinlich mit Maven ein Build-Tool, welches viele Dinge auf einmal erledigen kann. Dieses verfolgt aber eher den One-size-fits-all-Ansatz. Deswegen mag der JavaScript-Ansatz für Java-Entwickler zunächst mal ungewöhnlich scheinen.

Node.js

Beginnen möchte ich meine Aufstellung mit Node.js. Node.js ist zunächst dazu gedacht, “serverseitige Netzwerkanwendungen” zu bauen und zu betreiben. Das lasse ich im Zuge dieses Artikels aber außer Acht. Spannend ist für uns hier der Aspekt, dass Node.js als JavaScript Runtime dienen kann. Alle folgenden Tools nutzen Node.js als Basis, vergleichbar mit einem JDK in der Java-Welt. Wenn man also ein JavaScript-Projekt startet, dann wird man Node.js brauchen.

nvm

nvm ist der Node Version Manager: Node.js entwickelt sich sehr schnell, und deswegen ist es sinnvoll, einen Mechanismus zu haben, der es möglich macht, mehrere Node.js-Installationen parallel installieren zu können. Auch in der Java-Welt gibt es ähnliche Ansätze: zum Beispiel hier.
Sollte man installiert haben.

Dependecies

Es gibt im JavaScript-Umfeld einige Tools, die es leichter machen, mit abhängigen Bibliotheken und Codefragmenten umzugehen. Diese Tools möchten verhindern, dass ich händisch Bibliotheken in einen Lib-Ordner im Projekt kopiere und bewirken stattdessen, dass ich Abhängigkeiten als Konfiguration definiere.

npm

npm ist der Node Package Manager: npm hat man installiert, sobald Node.js installiert ist. Somit ist dies ein sehr verbreitetes Werkzeug zur Abhängigkeitsverwaltung. Diese Funktionalität kennt man auch schon aus der Java-Welt: Diese ist Bestandteil von Maven und (um noch ein anderes Tool zu nennen) ist vergleichbar mit ivy.
Die von npm verwalteten Module sind im Default erstmal lokal zum eigenen Projekt. Es gibt aber auch die Möglichkeit, global zu speichern, wie man das von Maven kennt. Empfohlen wird die lokale Variante.
Mit npm kann auch seinen Build orchestrieren, also auch Test und Linting (siehe „Diverse Tools“) anstoßen. Da dies aber etwas unhandlich ist, gibt es dafür speziellere Tools (siehe „Taskrunner“). Für einfache Builds ist npm allerdings ausreichend.
npm wird man im Projekt verwenden.

Bower

Bower ist ebenfalls ein Paket-Manager. Bower ist dafür gedacht, clientseitigen Code zu verwalten. Unter clientseitigem Code versteht man hier z.B. jQuery oder Angular. Bower ist optimiert für die Eigenheiten von clientseitigem Code, z.B. wird der Abhängigkeitsbaum anders verwaltet.
Mit npm sollten dann nur noch serverseitige Abhängigkeiten wie z.B. express (Ein weit verbreitetes Webframework im Node.js-Umfeld) behandelt werden. Allerdings werden sehr viele Projeke auch auf npm gezogen, so dass die Bedeutung von Bower schwindet.
Ich würde zunächst mal auf npm setzen und schauen, ob man Bower wirklich benötigt.

WebJars

WebJars sind ein weiterer Mechanismus, um JavaScript-Artefakte zu verwalten. Wie der Name nahelegt, ist dies ein Weg für JVM-basierte Anwendungen. JavaScript-Bilbliotheken können z.B. per Maven verwaltet werden. Ein kleiner Nachteil ist, dass nicht alle Bibliotheken, die per npm oder Bower verfügbar sind, als WebJar vorliegen. Praktisch wird man dies wahrscheinlich nicht merken, da es nur weniger verbreitete Libraries betrifft. Im Zweifel würde ich eher von WebJars abraten, es sei denn, npm/Grunt sind wirklich kein Thema im Projekt.

Taskrunner

Mit Taskrunner sind hier Tools gemeint, die es erlauben, Arbeitsschritte zu definieren, um diese im Build automatisiert und wiederholbar auszuführen. Also Schritte wie Test, einen Testserver starten, Linting, minifying, CSS aufbereiten etc.

Grunt

Grunt ist solch ein Taskrunner. Mit Grunt kann man, vielleicht am ehesten vergleichbar mit Gradle, Build Tasks definieren und ausführen. Grunt ist vergleichweise alt und wird immer noch verwendet. Das Ökosystem ist sehr groß; so kann Grunt mit vielen Spezialtools (z. B. für das Testen) zusammenarbeiten. In letzter Zeit hat sich zwar die Aufmerksamkeit der JavaScript Community eher auf Gulp konzentriert (siehe unten). Für mich ist allerdings Grunt nach wie vor erste Wahl, da es alles kann, was ich benötige.

Gulp

Gulp ist ebenfalls ein Taskrunner. Es hat den Vorteil, dass es schneller arbeitet als Grunt. Gulp verzichtet soweit wie möglich auf Disk-IO, es hält die Zwischenergebnisse des Builds im Speicher. In meinen Projekten habe ich allerdings bisher kaum Auswirkungen bemerkt. Viele Entwickler finden die Syntax von Gulp schöner.
Mittlerweile ist Gulp auch schon länger verfügbar, so dass auch das Ökosystem bzgl. Plugins mit Grunt gleichgezogen hat. Viele Projekte setzen mittlerweile auch auf Gulp. Falls man vor der Wahl steht – Grunt oder Gulp – und beides noch nicht kennt, würde ich dazu raten, Gulp zu verwenden.

Module Bundler

Wenn die Anwendung nun größer wird, möchte man sie vielleicht in voneinander getrennte Module aufteilen. Hier gibt es nun mit AMD und CommonJS konkurrierende Systeme und mit ES2015 auch einen Standard. An dieser Stelle möchte ich nicht zu sehr in die Details dieser drei Möglichkeiten schauen. Also nur soviel: CommonJS ist der Weg, den Node.js eingeschlagen hat, um Anwendungen zu modularisieren (synchrones Laden). Asynchronous Module Definition (AMD) ist der Weg, um browserseitige Anwendungen zu modularisieren (asynchrones Laden). ES2015-Module vereinheitlichen beide Herangehensweisen. Hier und hier findet man einen tieferen Einblick in die Möglichkeiten der Modularisierung. Aber welche Tools unterstützen mich nun bei der Modularisierung?

Webpack

Webpack ist zunächst einmal “nur” ein Module Bundler, der direkt für eine Browserumgebung entwickelt wurde. Das Tool analysiert die Abhängigkeiten, die ich in meinen Modulen definiert habe (auch ES2015-Module). Und baut daraus ein minimales Set an statischen Artefakten. Es versteht dabei nicht nur JavaScript, sondern auch Sass, CSS, Grafiken etc. Auch beim Einsatz von React.js, wenn ich viele kleine Komponenten baue, hat sich Webpack bewährt.
Viele Entwickler gehen bei der Verwendung von Webpack auch noch einen Schritt weiter und stellen ihren kompletten Entwicklungsworkflow auf Webpack um. So kann es dann viele Teile (speziell den Concat/Minify Teil) von Grunt/Gulp ersetzen. Als sehr praktisch hat sich hier der Webpack-Dev-Server erwiesen, um schnellere Turnarounds zu erreichen. Betrachtet man auch diesen Aspekt, so ist es durchaus angebracht, Webpack auch im Zuge von Grunt und Gulp zu nennen.

Browserify

Browserify ist ein vergleichsweise altes Tool, um Node.js Module so zu verpacken, dass diese im Browser lauffähig sind. Sinnvoll kann dies sein, falls ich entweder ein Node.js-Backend habe und einige Dinge auch “isomorph” im Frontend verwenden möchte. Auch gibt es einige Third-Party-Bibliotheken nur als npm-Paket. Möchte ich dieses im Browser verwenden, dann brauche ich auch Browserify.

SystemJS

SystemJS ist ebenfalls eine Bibliothek, die dem Entwickler hilft, seine Anwendung zu modularisieren. SystemJS kann sowohl in Node.js-Umgebungen als auch in Browserumgebungen angewendet werden. Bislang stand SystemJS eher im Schatten von anderen Ansätzen wie Webpack und Browserify. Allerdings setzt AngularJS in der Version 2 nun auf SystemJS als Module Bundler und Loader und wird somit populärer werden.

RequireJS

Der Vollständigkeit halber möchte ich auch RequireJS nicht unerwähnt lassen. RequireJS ist eine vergleichsweise frühe Möglichkeit, Browser-basierten Code zu modularisieren. Der Kern von RequireJS ist die Möglichkeit, Module dynamisch zur Laufzeit zu laden. Damit ging allerdings eine gewisse Komplexität einher. Als Konsequenz schwenkte die JavaScript Community eher auf Build-Lösungen wie Webpack um.

Wenn ich meine Anwendung modularisieren möchte und diese basiert nicht auf AngularJS 2, ist meine Empfehlung, zunächst Webpack anzuschauen.

Testen

Da JavaScript eine dynamische Sprache ist und einige Fallstricke nicht von einem Compiler geprüft werden, bekommt hier das Testen eine sehr große Bedeutung. Leider wird das Thema Testing im Frontend oft vernachlässigt bzw. auf eher grobgranulare integrative Selenium Tests abgeschoben. Es gibt aber mittlerweile schöne Tools, die das Testen auch auf Unit-Test-Ebene unterstützen. Eine genaue Beschreibung eines Vorgehens für das (Unit-)Testen von JavaScript-lastigen Anwendungen würde auch hier wieder den Rahmen sprengen.

In der JavaScript Community unterscheidet man hier zwischen
– einer Assertion Library, die die Syntax und Sematik der Zusicherungen bestimmt und
– einem Testrunner, der die Tests ausführt
Beides wird im Java-Umfeld von JUnit abgedeckt.

Assertion Libraries

Hier gibt es in der JavaScript-Welt grob zwei Lager:
– das klassische Arange, Act, Assert (AAA)-Vorgehen, das man wahrscheinlich von JUnit kennt
– das BDD-Vorgehen, in dem man die Tests über ‘should’ oder ‘given’ beschreibt. Im Java-Umfeld ist JBehave hier ein Beispiel.

Wenn ich die Wahl habe, verwende ich gerne die Bibliothek Jasmine (http://jasmine.github.io/), die ein BDD-Vorgehen unterstützt und weit verbreitet ist.
QUnit empfehle ich, falls viel jQuery-basierter Code getestet werden muss. Es unterstützt das AAA-Vorgehen. QUnit ist allerdings momentan weniger populär.
Die Bibliothek Mocha ist ebenfalls sehr weit verbreitet und unterstützt sogar beide Vorgehen.
Auch Mocking ist ein Thema in der JavaScript-Welt (allerdings kein so großes wie in Java): Hier hat sich für mich Sinon.js bewährt.
In großen Projekt kann es auf einen Mix verschiedener Assertion Libraries hinauslaufen.
Tests basierend auf diesen Bibliotheken können direkt schon in Grunt, Gulp und auch in npm eingebunden und ausgeführt werden.

Testrunner

Schon Mocha und Jasmine beinhalten Möglichkeiten, um Tests auszuführen. Diese sind aber stark auf die Bedürfnisse von Backendtests einer Node-Anwendung zugeschnitten.
Ich bevorzuge daher den dedizierten Testrunner Karma aus dem AngularJS-Umfeld. Karma wurde für Tests in einer Frontend-/Browserumgebung konzipiert.
Für mich allerdings der wichtigste Punkt ist, dass Karma mir erlaubt, meine Tests sehr einfach in der IDE (IntelliJ/Webstorm) auszuführen und zu debuggen. Ohne Änderung meiner Konfiguration kann ich diese Tests dann auch aus dem automatisierten Build ausführen. Karma bildet dann die einheitliche Schnittstelle zu meinen Tests.

Headless Browser

Wenn ich meine Tests am Entwicklerrechner ausführe, ist es oft ausreichend, wenn die Tests im Kontext meines Systembrowsers ausgeführt werden.
Spätestens wenn ich meine Tests im automatisierten Build in einer CI-Umgebung ausführen möchte, ist ein Headless Browser sehr hilfreich. Hier hat sich PhantomJS sehr bewährt. Falls Sie über JavaScript-Tests nachdenken (was Sie sollten), dann wird PhantomJS höchstwahrscheinlich in Ihrem Stack auftauchen.

Weitere Tools

Bis zu diesem Zeitpunkt haben wir Tools kennengelernt, die den Build vereinfachen, meine Abhängigkeiten verwalten, mein Projekt modularisieren können und mir eine ordentliche Testumgebung bereitstellen. Im Folgenden möchte ich noch eine kleine Sammlung vorstellen, die das Entwicklerleben verbessert.

Entwicklungsumgebung

Anfangen möchte ich bei der IDE. Hier hat sich bei mir IntelliJ bzw. Webstorm sehr bewährt. Es bietet ordentlich Sprachunterstützung und guten Testsupport.
Viele Entwickler in diesem Umfeld gehen aber weg von einer großen IDE und vertrauen auf eine Kombination von Terminal und starkem Texteditor (wie z.B. Atom).
Vergleichsweise weniger gut aufgestellt ist in diesem Umfeld leider nach wie vor Eclipse, das ich für ernsthafte JavaScript-Entwicklung nicht empfehlen kann.

Sprachprüfer (Linter)

Während der Entwicklung von JavaScript-Code hat man zunächst mal keinen Compiler. Es gibt aber Tools, die den Entwickler dabei unterstützen, syntaktische Fehler, aber auch in Grenzen semantische Fehler aufspüren. JSHint hat sich da bei mir bewährt.
Dieses kann nicht nur im Build verwendet werden, auch aus der IDE bekommt man direkt Feedback. Sehr sinnvoll. Es sollte eins der ersten Tools sein, die im Projekt eingesetzt werden. In Atom und IntelliJ werden die Meldungen vom Linter auch direkt zur Entwicklungszeit im Sourcecode angezeigt, was ich sehr angenehm und hilfreich finde.

Aufsetzen des Projekts (Scaffolding)

Um ein JavaScript-Projekt aufzusetzen, bedarf es oft vieler kleiner Handgriffe. Ein Tool wie Yeoman schafft hier etwas Abhilfe, indem es übliche Projekt-Setups generieren kann. Dieses ist vergleichbar mit Maven Archetypes. Ich finde das Tool empfehlenswert, um ein Projekt schnell aufzusetzen. Allerdings sind die Templates von wechselnder Qualität. Wenn man ein komplexes Projekt-Setup über Yeoman aufsetzt, das viele von den oben erwähnten Tools orchestriert aufsetzt, kann das Ergebnis aber zunächst auch erschlagen.

Code-Minifizierer/Konkatenierer

Es gibt einige Gründe dafür, meinen JavaScript-Sourcecode nicht im Klartext in die Produktivumgebung auszuliefern.
Durch das Zusammenfassen spare ich vor allem Bandbreite von Browser zum Server. Ich kann auch meinen Code ein wenig verschleiern.
Sogenannte Minifier dienen dazu, JavaScript- oder CSS-Code zu verkleinern.
Allerdings macht es dem Entwickler etwas schwerer, den entstandenen Code zu debuggen.
Durch sogenannte SourceMaps wird dieses vereinfacht.
Concat geht in die gleiche Richtung. Ein Concat-Schritt im Build fasst Artefakte zu einer oder mehreren Artefakten zusammen, so dass weniger HTTP-Requests zum Laden nötig sind. Auch kann durch einen Concat-Schritt im Build erreicht werden, dass weniger JavaScript-Dateien per Script-Tag geladen werden müssen. Einige Module Bundler bringen diese Funktionalität schon mit.
Die Taskrunner Gulp und Grunt bieten hierfür sehr gute Plugins, und auch in Webpack ist dieses Konzept vorgesehen.

Cross-Compiler/Transpiler

Wie ich oben schon kurz erwähnt habe, braucht man heutzutage u.U. noch eine Möglichkeit, ES2015-Code in Code umzuwandeln, den die Zielumgebung versteht.
Hierfür ist Babel das passende Tool. Im React.js-Umfeld wird es auch angewendet, um JSX-Code zu Browser-verständlichem ES5-Code umzuwandeln.
Bei der Weiterentwicklung von JavaScript ist abzusehen, dass die Sprachentwickler nun schneller vorgehen, als dies in der Vergangenheit der Fall war. Da die Browserhersteller mit dieser Geschwindigkeit wahrscheinlich nicht ständig Schritt halten können, aber der Einsatz modernerer Sprachfeatures durchaus sinnvoll sein kann, plädiert das ECMA-Gremium dafür, ein Tool wie Babel in seinen Toolstack aufzunehmen. Somit erreicht man eine große Flexibilität.

Fazit

Hier endet mein kleiner Rundgang durch das JavaScript-Tool-Ökosystem.
Ich hoffe, ich konnte Ihnen ein paar Tools und Begriffe näher bringen. Vielleicht ist ein erster Überblick entstanden, welche Möglichkeiten das Tooling im JavaScript-Umfeld auch im Enterprise-Umfeld bietet. Wie so oft, ist es sicher nicht sinnvoll, direkt alles in diesem Artikel Genannte direkt in ein Projekt einzubauen, sondern mit Augenmaß vorzugehen. Falls Sie bei Null anfangen, empfehle ich, einen Blick auf die Möglichkeiten der Taskrunner und der Testing-Tools zu werfen.

Da erfahrungsgemäß mit der Menge an clientseitigem Code auch die Ansprüche an das Tooling steigen, kann das Tooling dann mitwachsen, und Themen wie Module Bundler werden interessant.
Ebenso, wie es mittlerweile üblich ist, den serverseitigen Build zu planen, ist es aus meiner Sicht notwendig, auch den clientseitigen Build ähnlich geplant zu betreuen und nicht einfach entstehen und wuchern zu lassen. Auch hier ist Know-how von Nöten.

Insgesamt kratzt dieser Artikel nur an der Oberfläche des Themas. Ich habe beim Verfassen selber gemerkt, wie viele spannende Seitenblicke und Detailfragen es hier noch gibt, die aber den Rahmen dieses Artikels gesprengt hätten.

Deswegen die Frage an den Leser: Worauf soll ich in Folgeartikeln näher eingehen? Ist dieses Thema interessant und relevant für Ihre Projektsituationen? Was sind Ihre Erfahrungen?
Vielen Dank fürs Lesen!

Holger Grosse-Plankermann

Holger Grosse-Plankermann ist seit 2015 für die codecentric als Senior Consultant und Senior Developer tätig. Gerne entwickelt und entwirft er gut testbare Software und bewegt sich dabei sowohl im Backend als auch im Frontend. Holger nutzt dabei oft den Java EE/Spring Stack und ist zudem ein Verfechter von JavaScript- und Web-Technologien.
Er hält immer Ausschau nach innovativen Lösungen für Kundenprobleme. Sein aktuelles Interesse gilt dem Bereich der funktionalen Programmierung, und er bleibt im Bereich JavaScript-Frameworks immer am Ball.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Artikel von Holger Grosse-Plankermann

JavaScript

React.js im Praxischeck

JavaScript

Road testing React.js

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *