Yarn2 – schneller, stabiler und moderner

Die neue Version des Paketmanagers Yarn bietet zahlreiche Neuerungen und Verbesserungen für Entwickler, allerdings auch einige Breaking Changes.

In Pocket speichern vorlesen Druckansicht 19 Kommentare lesen
Yarn2 – schneller, stabiler und moderner

(Bild: Shutterstock.com)

Lesezeit: 15 Min.
Von
  • Sebastian Springer
Inhaltsverzeichnis

Nach einiger Entwicklungszeit ist mit Yarn2 eine neue Version des JavaScript-Paketmanagers erschienen. Er ist aktuell die populärste Alternative zum Platzhirsch NPM und folgt bei seinen Releases dem Semantic-Versioning-Ansatz. Die Veröffentlichung der Version 2 bringt folglich Breaking Changes mit. Allerdings bietet die Version ebenso zahlreiche neue Funktionen und Verbesserungen. Es lohnt sich, zunächst einen Blick auf die Entwicklung von Yarn zu werfen.

Yarn sollte ursprünglich das Ziel verfolgen, die Probleme zu beheben, die NPM zum damaligen Zeitpunkt hatte:

  • Geschwindigkeit: Yarn führte eine Caching-Ebene für die Installation von Paketen sowie die Möglichkeit von parallelen Downloads ein, um die Installation von Paketen und ihre Abhängigkeiten zu beschleunigen.
  • Sicherheit: Bis zur Einführung von Yarn war es für Angreifer möglich, die Inhalte von Paketen bei der Installation zu manipulieren. Bei gleichbleibender Versionsnummer ist die Veränderung nicht aufgefallen. Bei der initialen Installation eines Pakets erstellt Yarn sowohl für das Paket als auch für sämtliche Abhängigkeiten je eine Prüfsumme und speichert sie in der "yarn.lock"-Datei. Bei jeder folgenden Installation kommt es zur Prüfung. Das schließt schadhafte Manipulationen aus.
  • Zuverlässigkeit: Yarn enthält in seiner "lock"-Datei nicht nur die Prüfsummen der Pakete, sondern auch die exakten Versionsnummern und die Links zu den Paketquellen. Das stellt sicher, dass jede Installation gleich ist und es nicht durch die Änderung einer Abhängigkeit zu unerwünschten Nebeneffekten kommt.

Die Entwickler von NPM haben diese Funktionen ebenfalls übernommen, sodass sich mittlerweile ein Kopf-an-Kopf-Rennen zwischen den beiden Paketmanagern entwickelt hat. NPM hat in der Zwischenzeit mit Features wie npx nachgelegt. Das ist ein Package Runner zum Ausführen von NPM-Paketen. Das ist vor allem für ausführbare Pakete relevant, also für viele Werkzeuge auf Node.js-Basis. npx prüft, ob eine lokale Version des Pakets vorhanden ist und führt sie aus. Ist das nicht der Fall, lädt er das Paket herunter, führt es aus und verwirft es anschließend wieder. Eine weitere neue Funktion ist der Security-Befehl npm audit zum Überprüfen der installierten Abhängigkeiten auf bekannte Sicherheitsprobleme.

Nachdem NPM und Yarn mittlerweile annähernd das gleiche Maß an Zuverlässigkeit und Sicherheit erreicht haben, ist das ausschlaggebende Unterscheidungskriterium die Installationsgeschwindigkeit. Die Entwickler von Yarn haben bereits in Version 1 das Feature Plug'n'Play eingeführt, kurz pnp. Es sorgt dafür, dass der letzte Schritt der Paketinstallation entfällt, also das Kopieren der heruntergeladenen Dateien in das lokale "node_modules"-Verzeichnis. Die aktuelle Version des Paketmanagers greift das Feature auf und setzt es standardmäßig ein.

Die Breaking Changes in Yarn2 führen zu einer Reihe von Problemen, die die Verteilung und Adaption des Paketmanagers etwas beeinträchtigen. Das ist der Hauptgrund, warum die Verteilung der neueren Version über die globale Installation mit NPM begann.

Entwickler können die neue Version mit npm install -g yarn@berry installieren. @berry steht hierbei für den Release-Namen. Mit Werkzeugen wie dem Node-Version-Manager nvm können Anwender gleichzeitig mehrere Versionen von Yarn auf einem System betreiben. Die weiteren Installationsmöglichkeiten wie einen Windows Installer, Homebrew, Chocolatey und Linux-Pakete, verspricht das Yarn-Team zeitnah.

Die grundlegenden Kommandos haben sich zwischen den Versionen nicht geändert:

  • init: Initialisiert ein Projekt, indem der Befehlt eine minimale Version einer "package.json"-Datei erzeugt. Eine Änderung zur Vorgängerversion besteht darin, dass an die Stelle eines interaktiven Prozesses zur Erstellung der "package.json"-Datei ein automatisierter Prozess getreten ist, der lediglich den Namen der Applikation setzt.
  • add: Installiert ein Paket. Yarn fügt es in die "package.json"-Datei und in die "yarn.lock"-Datei ein. Außerdem lädt der Paketmanager den Quellcode herunter und fügt ihn dem Yarn-Cache hinzu.
  • remove: Entfernt ein installiertes Paket und seine Abhängigkeiten aus der Applikation. Das manuelle Entfernen eines Pakets ist nicht empfehlenswert, da Entwickler das Paket und seine Abhängigkeiten löschen und aus den Konfigurationsdateien entfernen müssen.

Die erste Abweichung zur vorherigen Hauptversion fällt direkt beim Verwenden der Kommandozeile auf. Wo Version 1 noch mit zahlreichen Emojis zur Auflockerung gearbeitet hat, ist die Ausgabe der neuen Version deutlich nüchterner gestaltet. Der Grund hierfür ist, dass die Entwickler den Fokus auf Benutzbarkeit und Klarheit gelegt haben. Deshalb gibt auch jede Zeile einen Code an. Diese Fehlercodes sollen es einfacher machen, die Lösung eines Problems in der Dokumentation nachzuschlagen.

Installationsprozess eines Pakets (Abb. 1)

Weitere Änderungen sind für den Benutzer des Paketmanagers nicht direkt ersichtlich. Allerdings haben die Yarn-Entwickler beispielsweise die gesamte Codebasis vom Flow-Typsystem auf TypeScript umgestellt. Das macht die Werkzeugunterstützung für die Entwicklung des Paketmanagers und von Plug-ins deutlich komfortabler.

Eine weitere strukturelle Änderung betrifft die Lock- und Konfigurationsdateien von Yarn. Bisher waren sie in einem Format, das YAML zwar ähnlich war, aber dennoch nicht dem Standard folgte. Die nun erfolgte Anpassung erleichtert es Entwicklern, die Konfigurationsdateien zu ändern, da sie nicht mehr den gesonderten YAML-Dialekt beachten müssen. Das vereinfacht ebenfalls die Verarbeitung der Dateien durch andere Applikationen, die nun standardkonforme YAML-Parser einsetzen können.

Das als strikte Paketgrenzen bezeichnete Feature kann beim Umstieg auf die neue Version von Yarn zu Problemen führen. Dahinter verbirgt sich die Tatsache, dass ein Paket lediglich Pakete verwenden darf, die es explizit als Abhängigkeiten aufgeführt hat. Während der Entwicklung erscheint es als selbstverständlich, ein Paket vor der Verwendung zu installieren. Hat ein anderes Paket das benötigte jedoch bereits installiert, kann die Applikation es über das Modulsystem laden – es befindet sich schließlich schon im aktuellen Projekt.

Oft nennt man solche Pakete PeerDependencies, da Entwickler sie ohne die Abhängigkeiten nicht sinnvoll betreiben können. Greift ein Paket auf eine nicht aufgeführte Abhängigkeit zu, wirft Yarn einen Fehler, da es gegen die strikten Paketgrenzen verstößt. Unterliegt das betroffene Paket dem eigenen Einflussbereich, besteht kein Problem, da man die Abhängigkeit einfach eintragen kann.

Anders ist der Fall, wenn es sich um ein externes Paket handelt. Dann müssen die Maintainer daran arbeiten. In einigen Fällen ist es jedoch nicht denkbar, mit der Verwendung eines Pakets zu warten. Dazu bietet Yarn die Möglichkeit, das Problem über eine paketspezifische Konfiguration zu lösen, sodass die Installation ohne Fehlermeldung erfolgen kann. Sie erfolgt in der ".yarnrc.yml"-Datei unter der packageExtensions-Einstellung. Anwender können pro installiertem Paket zusätzliche Erweiterungen angeben, beispielsweise fehlende Abhängigkeiten.

Ein Feature, das die Verwendung von Yarn weiter beschleunigt, ist das Build Dependency Tracking. Bisher hat Yarn Pakete, die über einen eigenen Build-Prozess verfügen, bei Änderungen am Projekt komplett neu gebaut. Die neue Funktion hält den Status der Pakete fest. Der Build erfolgt nur noch, wenn sich der Abhängigkeitsbaum des Pakets ändert.

Ein häufiges Problem ist die Inkompatibilität der Kommandozeilen auf verschiedenen Betriebssystemen, auf denen Entwickler die Skripte der "package.json"-Datei ausführen. Yarn löst es, indem ein über die Systeme normalisierter Shell-Interpreter Bestandteil des Paketmanagers ist. Er verfügt zwar nicht über den gesamten Funktionsumfang einer vollwertigen Shell, deckt jedoch einen Großteil der Anwendungsfälle ab.

Yarn dlx

Eine weitere Neuerung ist die Einführung des dlx-Kommandos. dlx steht für "Download and Execute". Der Befehl lädt das angegebene Paket herunter und führt es aus. Ein typischer Vertreter eines solchen Pakets ist json-server, ein einfacher Schnittstellenserver, der den Inhalt einer JSON-Datei als Restful-API anbietet. Entwickler können ihn mit folgendem Befehl starten:

yarn dlx json-server data.json

Beim Ausführen prüft Yarn, ob das Paket bereits im lokalen Cache vorhanden ist. Zunächst ist das nicht der Fall und Yarn lädt den json-server aus dem Paket-Repository herunter. Bei jeder weiteren Ausführung entfällt der Schritt, was den Start des Serverprozesses deutlich beschleunigt.

Der yarn dlx-Befehl funktioniert ähnlich wie npx, mit dem Unterschied, dass yarn dlx lediglich Pakete herunterlädt und ausführt, nicht aber automatisch auf dem lokalen System nach Paketen sucht. Der Grund für das unterschiedliche Verhalten liegt darin, dass die striktere Variante von yarn dlx potenzielle Sicherheitsprobleme reduzieren soll. Bei npx können Entwickler nicht sicherstellen, ob der Befehl in der lokalen oder der Netzwerkvariante abläuft. In Yarn ist das ganz klar getrennt. yarn run bezieht die Skripte aus lokalen Quellen in Form der scripts-Sektion der package.json, lokal installierten Paketen mit ausführbaren Dateien oder einer lokalen Skriptdatei.

Der Begriff Zero Installs bezeichnet eine Bestrebung des Paketmanagers, die Installation von Abhängigkeiten einer Applikation so schnell und stabil wie möglich zu gewährleisten. Vor der Einführung von Lock-Dateien war das nur auf der Ebene der obersten Abhängigkeiten in der "package.json"-Datei garantiert. Mit den Lock-Dateien wurden die Versionen aller installierten Pakete festgelegt.

Yarn geht mit Zero Installs noch einen Schritt weiter und speichert den Paket-Cache als Bestandteil des Repositorys. Das bedeutet, dass die bisherige Empfehlung, die Abhängigkeiten nicht einzuchecken, damit hinfällig ist. Alle Abhängigkeiten im Repository zu haben, beschleunigt nicht nur die Installation der Applikation, sondern macht sie auch deutlich stabiler. Es ist keine Verbindung zu einem externen Dienst wie einem zentralen Package-Repository mehr notwendig. Damit ist die Installation einer JavaScript-Applikation nicht mehr von externen Faktoren abhängig und kann auch in einem abgeschotteten Netz ohne Internetverbindung ablaufen.

Nachteile gibt es dennoch: Gerade bei umfangreichen Applikationen mit einer großen Anzahl von Dependencys steigt die Repository-Größe stark an. Deshalb speichert Yarn die Pakete in komprimierter Form.

In der Dokumentation ziehen die Entwickler von Yarn einen Vergleich zu NPM, bei dem 135 000 unkomprimierte Dateien mit einer Größe von 1,2 GByte in Yarn 2000 komprimierten Archiven mit einer Größe von 139 MByte entsprechen. Das lokale Cache ist als read-only implementiert. Die Pakete können ihn nur lesen, ein Ändern des Inhalts ist nicht ohne weiteres möglich.

Da das Zero-Installs-Konzept nicht nur Vorteile hat, lässt es sich über die Konfigurationsoption enableGlobalCache deaktivieren.

Ein erwähnenswertes Feature sind die Workspaces von Yarn. Sie gewährleisten eine bessere Unterstützung von Monorepos. Ein Monorepo ist im einfachsten Fall ein Repository, das mehrere Subprojekte enthält, die miteinander in Zusammenhang stehen.

Die Worktree-Definition, also die Definition des Konstrukts, das die einzelnen Workspaces umfasst, findet im übergeordneten Verzeichnis in einer zusätzlichen "package.json"-Datei statt. Innerhalb derer erfolgt die Definition einer workspaces-Eigenschaft, die auf die einzelnen Pakete verweist. Jeder Workspace verfügt wiederum über eine eigene "package.json"-Datei.

Die einzelnen Workspaces sind strikt voneinander getrennt. Das bedeutet, dass jeder nur auf seine eigenen Abhängigkeiten zugreifen kann. Die einzelnen Workspaces können sich untereinander als Dependency referenzieren. Das workspace-Protokoll referenziert Pakete innerhalb des Workspaces in der package.json-Datei und löst sie beim Publizieren auf.

Die neue Version von Yarn verfügt über das Kommando yarn workspaces foreach, das die Ausführung von Befehlen in den verschiedenen Workspaces erlaubt. Die einzelnen Workspaces kann Yarn entweder sequenziell oder parallel abarbeiten.

Auch der Release-Prozess eines Worktrees hat Yarn2 angepasst. Es adressiert einige Probleme, die durch den Einsatz von Monorepos entstehen. Damit Entwickler dieses Feature verwenden können, müssen sie zunächst das Version-Plug-in mit dem Befehl yarn plugin import version installieren. Damit aktualisiert das Kommando yarn version in einem Workspace auch die Versionsnummern aller Workspaces, die von einem Workspace abhängen. Im Gegensatz dazu arbeiten andere Werkzeuge im selben Bereich mit einem pauschalen Erhöhen der Versionsnummer aller Pakete im Worktree. Außerdem erlaubt die defer-Option des yarn version-Kommandos, einen Versionssprung noch nicht direkt durchzuführen, sondern ihn zunächst zu planen.

Der Befehl yarn version apply aktualisiert die Version im Projekt entsprechend. yarn version check hilft gerade bei umfangreichen Worktrees mit mehreren Entwicklern dabei, die Übersicht zu bewahren. Das Kommando prüft die einzelnen Workspaces auf Änderungen und wirft einen Fehler, falls eine Änderung vorliegt, aber die Versionsnummer nicht erhöht wurde.

Der Sprung auf die neue Version funktioniert in vielen Fällen ohne Probleme. Am häufigsten kommt es zu Schwierigkeiten bei den Paketgrenzen, die Yarn mittlerweile deutlich strikter handhabt. Die Entwickler von Yarn stellen mit dem Yarn Doctor ein Werkzeug zur Verfügung, das ein Projekt auf potenzielle Pobleme prüfen kann.

Im einfachsten Fall führen Entwickler den Yarn Doctor mit yarn dlx @yarnpkg/doctor aus und beheben die aufgelisteten Probleme. Anschließend können sie ein bestehendes node_modules löschen und die Abhängigkeiten mit yarn neu installieren. Die wichtigste Änderung ist das ".yarn"-Verzeichnis, das der Paketmanager erzeugt. Im Unterverzeichnis "cache" liegt der Paket-Cache, der das Zero-Installs-Konzept ermöglicht. Zusätzlich erzeugt Yarn die ".pnp.js"-Datei, mit der die über PnP aufgelösten Abhängigkeiten in der Applikation verwendet werden können.

In der Dokumentation findet sich eine ausführliche Migrations-Anleitung, die zahlreiche Problemstellungen adressiert und einige Best Practices dokumentiert.

Die neue Version von Yarn unterscheidet sich gravierend von der bisherigen. Die Entwickler des Paketmanagers haben den Schwerpunkt auf Stabilität und Performance gelegt. Gerade die Zero-Installs-Funktion sorgt dafür, dass eine Applikation deutlich schneller und stabiler einsatzbereit ist. Viele der neuen Features können Entwickler über die Konfigurationsoptionen ausschalten, sodass sich Yarn2 annähernd so verhält wie die bisherige Version.

Die Entwickler von Yarn haben einige Features konsequent weiterentwickelt. Beispiele sind PnP, das mittlerweile als Standard für neue Projekte zum Einsatz kommt und damit nicht nur eine optionale Erweiterung bleibt, und die Unterstützung von Workspaces für die Verwaltung umfangreicher Monorepos.

Neben allen Vorteilen, die die neue Version von Yarn mit sich bringt, bedeutet eine neue Major-Version allerdings immer Breaking Changes. Deshalb bleiben Nacharbeiten und die eine oder andere Schwierigkeit auch beim Umstieg von Yarn1 oder NPM auf Yarn2 nicht aus.

Sebastian Springer
arbeitet als JavaScript-Entwickler bei der Maiborn Wolff GmbH und ist Dozent für JavaScript, Sprecher auf zahlreichen Konferenzen und Autor diverser Fachartikel zum Thema JavaScript. (bbo)