Multi-Package-Repositories mit Lerna

Tales from the Web side  –  0 Kommentare

Ein wesentliches Design-Prinzip unter Node.js ist es, den Code getreu dem Motto "small is beautiful" in möglichst kleine, wiederverwendbare Packages zu strukturieren. Bei komplexeren Projekten kann das jedoch schnell unübersichtlich werden, wenn für jedes Package ein eigenes Git-Repository zu verwalten ist. Das Tool "Lerna" verspricht Abhilfe.

Wer unter Node.js oder allgemein in JavaScript ein komplexes Projekt entwickelt und seinen Code getreu dem vorgenannten Motto in viele kleine Packages strukturiert, landet recht schnell bei 50, 100 oder noch mehr Packages. Verwaltet man jedes davon in einem eigenen Git-Repository, artet das schnell in viel Konfigurationsarbeit aus: sei es, um Abhängigkeiten untereinander zu verwalten, Build-Prozesse zu organisieren oder das Deployment zu npm zu steuern.

Aus diesen Gründen strukturieren Entwickler bekannter Frameworks wie Angular, React, Meteor und Ember oder bekannter Tools wie Babel und Jest ihren Code mittlerweile in sogenannten Monorepositories, kurz Monorepos oder auch Multi-Package-Repositories. Die Idee: Statt jedes Package in einem eigenen Git-Repository vorzuhalten, bündelt man zusammengehörige Packages in einem einzelnen Repository.

Die Vorteile liegen auf der Hand: Zum einen muss man sich nicht mit mehreren Git-Repositories "herumschlagen", zum anderen lässt sich der Build-Prozess für alle Module stark vereinfachen und, wie wir gleich sehen werden, auch das Deployment zur npm-Registry. Ein Tool, das hierbei hilft, ist Lerna.

Installation

Ursprünglich Teil von Babel, ist Lerna mittlerweile ein eigenständiges Node.js-Package und kann wie gewohnt über den Package Manager npm als globale Abhängigkeit installiert werden:

$ npm install -g lerna

Anschließend kann das Tool über den Befehl lerna aufgerufen werden, wobei eine Reihe verschiedener Parameter zur Verfügung stehen:

  • init: Initialisierung eines Multi-Package-Repositories
  • bootstrap: Installation aller Abhängigkeiten der Packages
  • publish: Veröffentlichen aller Packages auf npm
  • updated: Überprüfen, welche Packages seit dem letzten Release geändert wurden
  • import: Importieren eines Package aus externem Repository
  • clean: Entfernen aller node_modules-Verzeichnisse in allen Packages
  • diff: Vergleich von Packages mit vorigem Release
  • run: Ausführen eines npm-Skripts in jedem Package
  • exec: Ausführen eines Kommandozeilenbefehls in jedem Package
  • ls: Auflisten aller Module

Initialisierung und Struktur

Ein Multi-Package-Repository definiert sich im Wesentlichen durch seine Struktur und über zwei globale Konfigurationsdateien: Zum einen über eine package.json-Datei, die Meta-Informationen für alle verwalteten Packages enthält, zum anderen über eine Konfigurationsdatei namens lerna.json, die wiederum Lerna-spezifische Meta-Informationen enthält. Die einzelnen Packages wiederum werden standardmäßig in einem Unterverzeichnis mit Namen packages einsortiert, sodass die Gesamtstruktur wie folgt aussieht:

multirepo/
node_modules/
packages/
package1/
node_modules/
src/
index.js
package.json
package2/
package3/
package4/
package5/
package6/
package.json
lerna.json

Diese Struktur kann man zwar manuell erzeugen. Alternativ steht aber auch wie oben schon erwähnt der Befehl lerna init zur Verfügung, der zumindest die beiden Konfigurationsdateien automatisch generiert:

$ git init multirepo
$ cd multirepo
$ lerna init

Dadurch wird zum einen das Modul Lerna als Abhängigkeit zu der package.json-Datei hinzugefügt (sofern dort noch nicht vorhanden) und zum anderen die Konfigurationsdatei lerna.json erzeugt. Zu Anfang sieht die Datei wie folgt aus und enthält die Versionsnummer der verwendeten Lerna-Bibliothek, eine Angabe darüber, unter welchem Verzeichnis die Packages liegen sowie eine Versionsnummer, die global für alle Packages gilt. Welche weiteren Konfigurationsmöglichkeiten es hier gibt, entnimmt man am besten der offiziellen Dokumentation.

{
"lerna": "2.0.0-beta.38",
"packages": [
"packages/*"
],
"version": "0.0.0"
}

Die Struktur der einzelnen Packages in einem Multi-Package-Repositories unterscheidet sich nicht von einem Package, das als Single-Package-Repository verwaltet wird. Das heißt beispielsweise, dass jedes Package weiterhin über seine eigene package.json-Datei verfügt und darüber zum Beispiel auch seine eigenen Abhängigkeiten definiert.

Für Abhängigkeiten hingegen, die nur während der Entwicklung benötigt werden (Eintrag "devDependencies" in package.json) ist es in den meisten Fällen sinnvoll, diese in der globalen package.json eines Multi-Package-Repositories anzugeben.

Das hat mehrere Vorteile: zum einen ist auf diese Weise sichergestellt, dass alle Packages die gleiche Version einer verwendeten Abhängigkeit haben. Zum anderen reduzieren sich dadurch sowohl die Installationszeit als auch der verwendete Speicherplatz für die entsprechende Abhängigkeit, da sie – logisch – nur einmal für alle Packages (und nicht einmal für jedes Package) installiert wird.

Bootstrapping und Deployment

Ein ebenfalls nützlicher Befehl ist lerna bootstrap. Dieser sorgt dafür, dass die Abhängigkeiten aller Packages installiert werden. Mit anderen Worten: Lerna ruft für jedes Package den Befehl npm install aus. Aber nicht nur das. Zusätzlich werden für alle Packages im Multi-Package-Repository, die als Abhängigkeit von einem anderen Package im Repository verwendet werden, symbolische Links erzeugt, was – Node.js-Entwickler werden das bestätigen – bei der Entwicklung enorm hilfreich ist. Zu guter Letzt wird dann noch für jedes Package der Befehl npm prepublish aufgerufen, wodurch die in den package.json-Dateien definierten "prepublish"-Skripte ausgeführt werden.

Nimmt einem Lerna bis hierhin schon viel Arbeit ab, wird es bezüglich Deployment beziehungsweise Publishing noch besser. Der Befehl lerna publish sorgt dafür, dass die Versionsnummer für alle Packages, die sich seit dem letzten Release geändert haben, entsprechend hochgezählt wird. Dabei kann über einen kleinen Kommandozeilendialog ausgewählt werden, ob es sich um einen "Patch", einen "Minor Change", einen "Major Change" oder um einen "Custom Change" handelt (nett: Die jeweils neue Version wird in Klammern hinter der jeweiligen Auswahl angezeigt).

Aber nicht nur das: lerna publish sorgt weiterhin dafür, dass entsprechende Tags und Commits für die neue Version in Git erzeugt und alle Packages separat bei npm publiziert werden.

Fazit

Die Bibliothek Lerna hilft bei komplexen JavaScript-Projekten, die aus mehreren zusammengehörigen Packages bestehen, den Überblick zu behalten: Sie erleichtert die Konfiguration des Build-Prozesses, die Verwaltung über Git und das Publishing zu der npm-Registry. Als Empfehlung sollte jeder Node.js-Entwickler, der mit komplexen Projekten oder einer komplexen Package-/Repository-Struktur zu "kämpfen" hat, bei Gelegenheit mal einen Blick riskieren. Eventuell lohnt sich ja der Umstieg.