Frontend Dependency-Management mit Bower

Paket Management hat sich in so ziemlich jedem populären Ökosystem der Softwareentwicklung in irgendeiner Form breit gemacht. Und das nicht zu Unrecht: In den seltensten Fällen wird in einem Software-Projekt bei null angefangen. Ob Frameworks oder kleinere Bibliotheken, ein Software-Projekt besteht in der Regel aus vielen einzelnen Bausteinen, die zu einem größeren Ganzen zusammen gesetzt werden. Die Abhängigkeiten von Bausteinen zu Anderen lassen sich bis zu einem bestimmten Grad zwar manuell verwalten, mit zunehmender Größe eines Projekts ist dies auf lange Sicht aber weder praktikabel noch wartbar. Genau an dieser Stelle helfen Dependency-Management Tools dabei Bausteine neu hinzuzufügen, bestehende zu aktualisieren oder deren Abhängigkeiten aufzulösen.

Maven, RubyGems oder npm sind nur einige Beispiele für Paketmanager verschiedenster Plattformen und Programmiersprachen. Seit dem ersten Release 2012 reiht sich das Tool Bower in diese Liste ein und bietet „einen Paketmanager für das Web“. Mittlerweile hat Bower eine beachtliche Popularität erreicht und ist zum de facto Standard bei der Entwicklung von Webapplikationen einer gewissen Größe geworden.

Gründe für diesen Erfolg gibt es genug: Bower ist ein relativ kleines, übersichtliches Tool und daher auch leicht zu verstehen und zu benutzen. Es ist generisch und Paket agnostisch. Das bedeutet, es trifft keine Annahmen darüber, wie ein Paket strukturiert ist und welche Bestandteile es enthält. Dies macht es Autoren von Frameworks und Bibliotheken aus dem Web-Umfeld besonders leicht ihre Projekte auch als Bower Pakete zur Verfügung zu stellen, ohne dafür einen großen Teil ihrer Codebasis verändern zu müssen. Darüber hinaus stehen bereits viele beliebte Frontend-Komponenten wie jQuery, HTML5 Boilerplate oder Bootstrap als Bower Pakete zur sofortigen Verwendung zur Verfügung, was die Akzeptanz und Reichweite natürlich noch weiter erhöht.

Installation und Setup

Um Bower auf dem eigenen System zu verwenden wird npm und git benötigt. So lässt sich Bower nach der Installation von node.js relativ leicht mit

$ npm install -g bower

auch für die lokale Entwicklung von Webprojekten verwenden. Um nun Pakete in einem Projekt zu installieren und zu verwalten, muss zunächst eine Konfigurationsdatei angelegt werden. Diese kann manuell erzeugt oder interaktiv mit Hilfe des init Befehls generiert werden.

$ cd path/to/myWebProjectRoot/
$ bower init

Dabei werden in wenigen Schritten einige Meta-Daten zum Projekt, wie Name, Version, Beschreibung, Schlagworte, Autoreninformationen und weitere erfasst und in der Datei bower.json gespeichert.

Wie auch immer die Konfigurationsdatei angelegt wird, wichtig ist, dass sie im Wurzelverzeichnis des Projekts liegt. Ein Beispiel für eine bower.json Datei könnte z.B. wie folgt aussehen:

{
  "name": "Name des Projekts",
  "version": "0.0.1",
  "main": "dist/main.css",
  "description": "Beschreibung",
  "authors": ["Autorenname und Email"],
  "homepage": "https://example.com",
  "private": true,
  "ignore": [
    "**/.*"
  ],
  "dependencies": {

  },
  "devDependencies": {

  }
}

Pakete und Versionen

Wie man an den Meta-Daten vielleicht erkennen kann, ist ein Projekt, dass eine solche Konfigurationsdatei besitzt, streng genommen selbst auch ein Bower Paket. Bei Projekten, die privat sind und nicht als installierbares Paket fungieren sollen, muss das Flag private in der Bower Datei gesetzt werden. Andernfalls ist es möglich, dass das Projekt bei der Ausführung des Befehls bower register als Paket in der bower.io Registry landet und wie jedes andere Paket auch über das Netz installierbar ist. Grundvorraussetzung dafür ist allerdings, dass die Codebasis in einem git Repository liegt und dieses Repository in den Meta-Daten erfasst ist.

Bower Pakete folgen per Konvention den Gepflogenheiten der semantischen Versionierung. Eine detaillierte Spezifikation, wie Pakete korrekt versioniert werden, findet sich auf http://semver.org. Erwähnenswert an dieser Stelle ist jedoch, dass die Versionsangabe von Paketen, die im bower.json eigetragen sind, häufig wie Operatoren anmutende Zeichen vor der eigentlichen Versionsnummer besitzen. Diese dienen zur Einschränkung der gültigen Versionsbereiche eines Pakets. Eine Übersicht über die Möglichkeiten von Versionsbereichen, sowie einige Beispiele dazu lassen sich gut in diesem Blogpost nachlesen.

Pakete für verschiedene Umgebungen

In der oben abgebildeten bower.json Datei fällt auf, dass es zwei Abschnitte für Abhängigkeiten gibt: dependencies und devDependencies. Ersteres wird für die Definition von regulären Anwendungsabhängigkeiten benutzt, letzteres für die Definition von sogenannten Entwicklungsabhängigkeiten. Ein Beispiel für eine solche Entwicklungsabhängigkeit wäre ein JavaScript Test-Framework wie Jasmine, welches später natürlich nicht von der Anwendung mit ausgeliefert werden sollte.

Pakete finden und installieren

Mittlerweile sind rund 22000 Bower Pakete registriert und zur weiteren Verwendung verfügbar. Um dem eigenen Webprojekt nun einzelne dieser Pakete hinzuzufügen, muss der exakte Name eines Pakets bekannt sein. Auffinden kann man Pakete mit Hilfe des bower search Befehls, auf der Seite http://bower.io/search/ oder dem passenden Suchbegriff auf Google.

Sobald der Name des Pakets bekannt ist, kann es mit dem Befehl

$ bower install typeahead.js --save

oder

$ bower install jasmine --save-dev

in das lokale Projekt installiert werden. Das Flag --save bewirkt dabei, dass das Paket nicht nur installiert sondern auch als reguläre Abhängigkeit in die bower.json Datei geschrieben wird. Mit dem Flag --save-dev wird ein Paket hingegen als Entwicklungsabhängigkeit definiert.

{
  "dependencies": {
    "typeahead.js": "~0.10.5"
    },
  "devDependencies": {
    "jasmine": "~2.1.3"
  }
}

Bei der Ausführung des install Befehls wird im Hintergrund die neuste Version des Pakets aus seinem git Repository gezogen und in den Ordner bower_components kopiert. Falls gewünscht, kann dem install Befehl auch eine spezifische Version als Parameter mit übergeben werden. Ist der Ordner bower_components vorher noch nicht vorhanden, wird er im Wurzelverzeichnis des Projekts angelegt. Hierbei wird auch deutlich, dass installierte Pakete exklusiv für ein Projekt sind und nicht wie beispielsweise im Falle von Maven in eine globale Registry gelegt werden, die von allen Projekten geteilt wird.

Pakete müssen nicht wie oben beschrieben einzeln installiert werden. Es besteht auch die Möglichkeit sämtliche Abhängigkeiten manuell in der bower.json zu editieren und im Anschluss mit einem einfachen

$ bower install

ohne weitere Parameter zu installieren.

Abhängigkeiten auflösen

Charakteristisch für Bower ist, dass es neben dem eigentlichen Paket auch alle Abhängigkeiten, die es mit sich bringt, in den Ordner bower_components kopiert. Im Gegensatz zu beispielsweise node.js Modulen werden Abhängigkeiten aber nicht ineinander geschachtelt und rekursiv aufgelöst sondern „flach geklopft“. Das hat zur Folge, dass bei zwei Paketen, die ein drittes Paket als Abhängigkeit haben, nur diejenige Version des dritten Pakets in bower_components installiert wird, die auch von den beiden anderen Paketen genutzt werden kann. Das unten stehende Beispiel verdeutlicht diesen Unterschied.

project-root
├── node_modules
│   ├── module_1
│   │   ├── module_5
│   ├── module_2
│   │   ├── module_4
│   ├── module_3
│       ├── module_4
│       ├── module_5
├── ...
project-root
├── bower_components
│   ├── package_1
│   ├── package_2
│   ├── package_3
│   ├── package_4
│   ├── package_5
├── ...

Hintergrund dieses Konzepts ist unter Anderem, dass für die Geschwindigkeit von Webanwendungen besonders entscheidend ist, dass nur so wenig Ressourcen wie nötig ausgeliefert werden. Insbesondere beim Ausliefern von JavaScript wäre es darüber hinaus fatal, wenn jede Komponente, die beispielsweise jQuery erfordert seine eigene Version von jQuery mit sich bringen würde.

Konflikte beheben

Besonders interessant wird es, wenn Paket Abhängigkeiten nicht mehr automatisch aufgelöst werden können und im Konflikt zueinander stehen. Ein einfaches Beispiel für diesen Fall sind zwei jQuery Plugins, die wie oben beschrieben zwei unterschiedliche, nicht kompatible jQuery Versionen benötigen – 1.x.x und 2.x.x. In diesem Fall muss bei der Installation mit bower install zwingend eine Version durch den Benutzer ausgewählt werden. Damit diese Entscheidung nicht jedes Mal aufs Neue eingegeben werden muss, gibt es die Möglichkeit diese Entscheidung in der bower.json Datei im Abschnitt resolutions zu hinterlegen.

Pakete aktualisieren

Bei der Entwicklung ist es von Zeit zu Zeit wünschenswert bestimmte Pakete zu aktualisieren – sei es weil neue Features verfügbar, offene Bugs behoben oder Sicherheitslücken geschlossen worden sind. Um zunächst einmal alle installierten Pakete eines Projekts anzuzeigen, eignet sich der Befehl

$ bower list

Dieser zeigt neben allen installierten Paketen samt Versionen praktischerweise auch an, ob diese aktualisiert werden können. Falls gewünscht lassen sich im Anschluss alle Pakete auf einen Schlag mit Hilfe des Befehls

$ bower update

aktualisieren. Wird hinter diesem Befehl noch der Name eines bestimmten Pakets als Parameter übergeben, wird auch nur dieses eine Paket und ggf. seine Abhängigkeiten aktualisiert.

Pakete einfrieren

Da bei der Verwendung von Bower sämtliche Abhängigkeiten eines Projekts in der bower.json Datei eigetragen sind, besteht zumindest in der Theorie keine Notwendigkeit, die installierten Pakete, die unter bower_components installiert sind mit in das Versionsmanagement-System der Wahl einzuchecken. Wann dies sinnvoll ist und wann vielleicht auch nicht, erklärt dieser Blogpost sehr gut im Detail.

Spätestens wenn eine halbwegs fertig entwickelte Webanwendung in Produktion gehen soll, empfiehlt es sich aber eigentlich immer sämtliche benutzten Pakete auf eine bestimmte Version einzugrenzen und auf die Verwendung von Versionsbereichen weitestgehend zu verzichten. Gleiches gilt auch für alle transitiven Abhängigkeiten. Dieses Einfrieren der Versionen ist besonders sinnvoll, wenn die Webanwendung über einen Continuous Integration Server gebaut, getestet und im Anschluss automatisiert in der Produktionsumgebung deployt wird. Ansonsten kann es je nach Konfiguration und Testszenario dazu kommen, dass ungewollt Aktualisierungen auf Paketen oder deren Abhängigkeiten ausgeführt werden. Das wiederum führt schlimmstenfalls zu Fehlern während des Build-Prozesses oder sogar zum Versagen der in Produktion deployten Anwendung.

Bis zu jetzigen Zeitpunkt ist leider kein Bower Befehl vorhanden, der einem das Einfrieren von Versionen automatisiert abnehmen könnte. Es ist aber nicht auszuschließen, dass Bower dieses Feature in Zukunft noch erhalten wird.

Bower konfigurieren

Nicht in jedem Projekt-Setup ist es wünschenswert, dass Bower die benötigten Pakete im Ordner bower_components/ im Wurzelverzeichnis ablegt. Dies und viele andere Dinge können mit Hilfe einer weiteren Datei .bowerrc für jedes Projekt individuell konfiguriert werden. So lässt sich beispielsweise im Falle einer Railsanwendung der Pfad für die bower_components auch nach vendor/assets/bower_components legen, also an die Stelle, an der das Webframework auch Bibliotheken von Drittanbietern erwartet.

Fazit

Trotz seiner zunehmenden Verbreitung ist Bower noch immer nicht in allen Bereichen der Webentwicklung angekommen. Besonders in Webprojekten im Enterprise-Bereich ist dieses Tool häufig nicht einmal bekannt. Dieser Blogpost sollte deshalb einen kurzen Überblick über die Möglichkeiten von Bower geben und hat hoffentlich auch Lust zum Ausprobieren gemacht.

In einem folgenden Blogpost werden detaillierte Strategien für die Intergration von Bower in bestehende Webanwendungen am Beispiel des Webframeworks Rails gezeigt.

TAGS

Kommentare

Um die Kommentare zu sehen, bitte unserer Cookie Vereinbarung zustimmen. Mehr lesen