Warum haben Sie den Support für package-lock.json in npm 7 verlassen?

Von dem Moment an, als wir angekündigt haben, dass Dateien in npm 7 unterstützt werden yarn.lock, haben sie mir dieselbe Frage mehrmals gestellt. Es klang so: „Warum dann die Unterstützung verlassen package-lock.json? Warum nicht einfach benutzen yarn.lock? " Die kurze Antwort auf diese Frage lautet: „Weil sie die Anforderungen von npm nicht vollständig erfüllt. Wenn Sie sich ausschließlich darauf verlassen, wird dies die Fähigkeit von npm beeinträchtigen, optimale Paketinstallationsschemata zu erstellen und dem Projekt neue Funktionen hinzuzufügen. " Eine detailliertere Antwort finden Sie in diesem Material.







yarn.lock



Die Grundstruktur der Datei yarn.lock



Die Datei yarn.lockist eine Beschreibung der Entsprechung von Paketabhängigkeitsspezifizierern und Metadaten, die die Auflösung dieser Abhängigkeiten beschreiben. Beispielsweise:



mkdirp@1.x:
  version "1.0.2"
  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.2.tgz#5ccd93437619ca7050b538573fc918327eba98fb"
  integrity sha512-N2REVrJ/X/jGPfit2d7zea2J1pf7EAR5chIUcfHffAZ7gmlam5U65sAm76+o4ntQbSRdTjYf7qZz3chuHlwXEA==


In dieser Passage wird Folgendes berichtet: "Jede Abhängigkeit von mkdirp@1.xsollte genau nach dem aufgelöst werden, was hier angegeben ist." Wenn mehrere Pakete davon abhängen mkdirp@1.x, werden alle diese Abhängigkeiten auf dieselbe Weise aufgelöst.



Wenn in npm 7 eine Datei im Projekt vorhanden ist yarn.lock, verwendet npm die darin enthaltenen Metadaten. Die Feldwerte resolvedteilen npm mit, von wo Pakete heruntergeladen werden müssen, und die Feldwerte integritywerden verwendet, um zu überprüfen, was empfangen wird, um sicherzustellen, dass es mit dem übereinstimmt, was erwartet wurde. Wenn dem Projekt Pakete hinzugefügt oder daraus entfernt werden, wird der Inhalt entsprechend aktualisiert yarn.lock.



Gleichzeitig erstellt npm wie zuvor eine Dateipackage-lock.json. Wenn diese Datei im Projekt vorhanden ist, wird sie als maßgebliche Informationsquelle für die Struktur (Form) des Abhängigkeitsbaums verwendet.



Die Frage hier ist: "Wenn es yarn.lockfür Yarns Paketmanager gut genug ist, warum kann npm diese Datei nicht einfach verwenden?"



Ergebnisse der Installation deterministischer Abhängigkeiten



Die Ergebnisse der Installation von Paketen mit Yarn sind garantiert gleich, wenn dieselbe Datei yarn.lockund dieselbe Version von Yarn verwendet werden. Die Verwendung verschiedener Versionen von Yarn kann dazu führen, dass sich Paketdateien auf der Festplatte unterschiedlich befinden.



Die Datei yarn.lockgarantiert eine deterministische Auflösung von Abhängigkeiten. Wenn dies beispielsweise foo@1.xzulässig ist foo@1.2.3, yarn.lockgeschieht dies bei Verwendung derselben Datei immer in allen Versionen von Yarn. Dies ist jedoch (zumindest an sich) nicht gleichbedeutend mit der Garantie, dass die Struktur des Abhängigkeitsbaums deterministisch ist!



Betrachten Sie das folgende Abhängigkeitsdiagramm:



root -> (foo@1, bar@1)
foo -> (baz@1)
bar -> (baz@2)


Hier sind einige Abhängigkeitsbaumschemata aufgeführt, von denen jedes als korrekt angesehen werden kann.



Baum Nummer 1:



root
+-- foo
+-- bar
|   +-- baz@2
+-- baz@1


Baum Nummer 2:



+-- foo
|   +-- baz@1
+-- bar
+-- baz@2


Die Datei yarn.lockkann uns nicht sagen, welcher bestimmte Abhängigkeitsbaum verwendet werden soll. Wenn ein rootBefehl im Paket ausgeführt wird require(«baz»)(was falsch ist, da diese Abhängigkeit nicht im Abhängigkeitsbaum widergespiegelt wird), yarn.lockgarantiert die Datei nicht die korrekte Ausführung dieser Operation. Dies ist eine Form des Determinismus, die eine Datei geben kann package-lock.json, aber nicht yarn.lock.



In der Praxis natürlich seit Garn in der Akteyarn.lockWenn alle Informationen zur Auswahl der entsprechenden Version einer Abhängigkeit erforderlich sind, ist die Auswahl deterministisch, solange alle dieselbe Version von Yarn verwenden. Dies bedeutet, dass die Versionsauswahl immer auf die gleiche Weise erfolgt. Der Code ändert sich erst, wenn jemand ihn ändert. Es ist zu beachten, dass Yarn intelligent genug ist, um beim Erstellen des Abhängigkeitsbaums nicht von Unstimmigkeiten hinsichtlich der Ladezeit des Paketmanifests betroffen zu sein. Andernfalls könnte der Determinismus der Ergebnisse nicht garantiert werden.



Da dies durch die Merkmale der Garnalgorithmen und nicht durch die auf der Platte verfügbaren Datenstrukturen bestimmt wird (ohne den zu verwendenden Algorithmus zu identifizieren), ist diese Garantie des Determinismus grundsätzlich schwächer als die Garantie, die sie gibtpackage-lock.jsonenthält eine vollständige Beschreibung der Struktur des auf der Festplatte gespeicherten Abhängigkeitsbaums.



Mit anderen Worten, genau wie Yarn den Abhängigkeitsbaum erstellt, wird von der Datei yarn.lockund der Implementierung von Yarn selbst beeinflusst. Und in npm beeinflusst nur die Datei, wie der Abhängigkeitsbaum aussieht package-lock.json. Aufgrund der in beschriebenen Projektstruktur package-lock.jsonwird es schwieriger, versehentlich verschiedene Versionen von npm zu verwenden. Und wenn Änderungen an der Datei vorgenommen werden (möglicherweise versehentlich oder absichtlich), werden diese Änderungen in der Datei deutlich sichtbar, wenn die geänderte Version zum Projekt-Repository hinzugefügt wird, das das Versionskontrollsystem verwendet.



Verschachtelte Abhängigkeiten und Deduplizierung von Abhängigkeiten



Darüber hinaus gibt es eine ganze Reihe von Situationen, in denen mit verschachtelten Abhängigkeiten gearbeitet und Abhängigkeiten dedupliziert werden, wenn die Datei yarn.lockdas Ergebnis der Abhängigkeitsauflösung, die in der Praxis von npm verwendet wird, nicht genau wiedergeben kann. Darüber hinaus gilt dies auch für Fälle, in denen npm yarn.lockMetadaten als Quelle verwendet. Während npm es yarn.lockals zuverlässige Informationsquelle verwendet, betrachtet npm diese Datei nicht als maßgebliche Informationsquelle über Einschränkungen der Abhängigkeitsversion.



In einigen Fällen generiert Yarn einen Abhängigkeitsbaum mit einem sehr hohen Grad an Paketduplizierung, den wir nicht benötigen. Infolgedessen stellt sich heraus, dass die genaue Befolgung des Yarn-Algorithmus in solchen Fällen alles andere als ideal ist.



Betrachten Sie das folgende Abhängigkeitsdiagramm:



root -> (x@1.x, y@1.x, z@1.x)
x@1.1.0 -> ()
x@1.2.0 -> ()
y@1.0.0 -> (x@1.1, z@2.x)
z@1.0.0 -> ()
z@2.0.0 -> (x@1.x)


Das Projekt rootrichtet sich nach den 1.xPaket - Versionen x, yund z. Das Paket yhängt von x@1.1und ab z@2.x. Das Paket der zVersion 1 hat keine Abhängigkeiten, das Paket der Version 2 jedoch x@1.x.



Basierend auf diesen Informationen generiert npm den folgenden Abhängigkeitsbaum:



root (x@1.x, y@1.x, z@1.x) <--   x@1.x
+-- x 1.2.0                <-- x@1.x   1.2.0
+-- y (x@1.1, z@2.x)
|   +-- x 1.1.0            <-- x@1.x   1.1.0
|   +-- z 2.0.0 (x@1.x)    <--   x@1.x
+-- z 1.0.0


Das Paket z@2.0.0hängt davon ab x@1.x, das gleiche kann über gesagt werden root. Die Datei yarn.lockist x@1.xc zugeordnet 1.2.0. Eine Paketabhängigkeit, zin der auch angegeben wurde, x@1.xwird stattdessen aufgelöst x@1.1.0.



Obwohl die Abhängigkeit x@1.xdort beschrieben wird, yarn.lockwo angegeben wird, dass sie in die Paketversion aufgelöst werden soll 1.2.0, gibt es daher ein zweites Auflösungsergebnis x@1.xfür die Paketversion 1.1.0.



Wenn Sie npm mit einem Flag ausführen --prefer-dedupe, geht das System einen Schritt weiter und installiert nur eine Instanz der Abhängigkeit x. Dies führt zur Bildung des folgenden Abhängigkeitsbaums:



root (x@1.x, y@1.x, z@1.x)
+-- x 1.1.0       <-- x@1.x       1.1.0
+-- y (x@1.1, z@2.x)
|   +-- z 2.0.0 (x@1.x)
+-- z 1.0.0


Dies minimiert das Duplizieren von Abhängigkeiten. Der resultierende Abhängigkeitsbaum wird in die Datei übernommen package-lock.json.



Da die Datei yarn.locknur die Reihenfolge erfasst, in der Abhängigkeiten aufgelöst werden, und nicht den resultierenden Paketbaum, bildet Yarn einen solchen Abhängigkeitsbaum:



root (x@1.x, y@1.x, z@1.x) <--   x@1.x
+-- x 1.2.0                <-- x@1.x   1.2.0
+-- y (x@1.1, z@2.x)
|   +-- x 1.1.0            <-- x@1.x   1.1.0
|   +-- z 2.0.0 (x@1.x)    <-- x@1.1.0   , ...
|       +-- x 1.2.0        <-- Yarn     ,    yarn.lock
+-- z 1.0.0


Das Paket wird xbei Verwendung von Garn dreimal im Abhängigkeitsbaum angezeigt. Bei Verwendung von npm ohne zusätzliche Einstellungen - zweimal. Und wenn Sie das Flag verwenden --prefer-dedupe- nur einmal (obwohl dann der Abhängigkeitsbaum nicht die neueste und nicht die beste Version des Pakets ist).



Alle drei resultierenden Abhängigkeitsbäume können in dem Sinne als korrekt angesehen werden, dass jedes Paket die Versionen der Abhängigkeiten erhält, die die angegebenen Anforderungen erfüllen. Wir möchten jedoch keine Paketbäume mit zu vielen Duplikaten erstellen. Überlegen Sie, was passieren wird, wenn x- dies ist ein großes Paket, das viele eigene Abhängigkeiten hat!



Daher gibt es nur einen Weg, wie npm den Paketbaum optimieren kann, während die Erstellung deterministischer und reproduzierbarer Abhängigkeitsbäume beibehalten wird. Diese Methode besteht in der Verwendung einer Sperrdatei, deren Prinzip der Bildung und Verwendung sich grundlegend unterscheidet yarn.lock.



Korrektur der Ergebnisse der Implementierung von Benutzerabsichten



Wie bereits erwähnt, kann der Benutzer in npm 7 das Flag --prefer-dedupeverwenden, um den Algorithmus zur Generierung des Abhängigkeitsbaums anzuwenden , bei dem die Deduplizierung von Abhängigkeiten Vorrang hat und nicht der Wunsch, immer die neuesten Paketversionen zu installieren. Das Flag ist --prefer-dedupenormalerweise ideal in Situationen, in denen doppelte Pakete minimiert werden müssen.



Wenn dieses Flag verwendet wird, sieht der resultierende Baum für das obige Beispiel folgendermaßen aus:



root (x@1.x, y@1.x, z@1.x) <--   x@1.x 
+-- x 1.1.0                <-- x@1.x   1.1.0   
+-- y (x@1.1, z@2.x)
|   +-- z 2.0.0 (x@1.x)    <--   x@1.x
+-- z 1.0.0


In diesem Fall sieht npm, dass obwohl es x@1.2.0die neueste Version des Pakets ist, die die Anforderung erfüllt x@1.x, es durchaus möglich ist, stattdessen zu wählen x@1.1.0. Wenn Sie diese Version auswählen, werden weniger Pakete im Abhängigkeitsbaum dupliziert.



Wenn Sie die Struktur des Abhängigkeitsbaums nicht in einer Sperrdatei festgelegt hätten, müsste jeder Programmierer, der an einem Projekt in einem Team arbeitet, seine Arbeitsumgebung auf die gleiche Weise einrichten, wie andere Teammitglieder sie konfigurieren. Nur so kann er das gleiche Ergebnis wie die anderen erzielen. Wenn die "Implementierung" des Mechanismus zum Erstellen von Abhängigkeitsbäumen auf diese Weise optimiert werden kann, bietet dies npm-Benutzern eine ernsthafte Möglichkeit, Abhängigkeiten für ihre eigenen spezifischen Anforderungen zu optimieren. Wenn die Ergebnisse der Erstellung eines Baums jedoch von der Implementierung des Systems abhängen, ist es unmöglich, deterministische Abhängigkeitsbäume zu erstellen. Dazu führt die Verwendung der Datei yarn.lock.



Hier einige weitere Beispiele, wie zusätzliche npm-Optimierungen zur Erstellung verschiedener Abhängigkeitsbäume führen können:



  • --legacy-peer-deps, ein Flag, das bewirkt, dass npm vollständig ignoriert wird peerDependencies.
  • --legacy-bundling, eine Flagge, die npm sagt, dass er nicht einmal versuchen sollte, den Abhängigkeitsbaum "flacher" zu machen.
  • --global-style, das Flag, mit dem alle transitiven Abhängigkeiten als verschachtelte Abhängigkeiten in den übergeordneten Abhängigkeitsordnern installiert werden.


Das Erfassen und Korrigieren der Ergebnisse des Auflösens von Abhängigkeiten und das Berechnen, dass beim Erstellen des Abhängigkeitsbaums derselbe Algorithmus verwendet wird, funktioniert nicht unter Bedingungen, unter denen Benutzer die Möglichkeit haben, den Mechanismus zum Erstellen des Abhängigkeitsbaums zu konfigurieren.



Durch die Festlegung der Struktur des fertigen Abhängigkeitsbaums können wir Benutzern ähnliche Funktionen zur Verfügung stellen und gleichzeitig den Prozess der Erstellung deterministischer und reproduzierbarer Abhängigkeitsbäume nicht stören.



Leistung und Datenvollständigkeit



Die Datei ist package-lock.jsonnicht nur nützlich, wenn Sie den Determinismus und die Reproduzierbarkeit von Abhängigkeitsbäumen sicherstellen müssen. Darüber hinaus verlassen wir uns auf diese Datei, um Paketmetadaten zu verfolgen und zu speichern. Dies spart erheblich Zeit, die sonst nur bei Verwendung package.jsonmit der npm-Registrierung erforderlich gewesen wäre. Da die Funktionen der Datei yarn.locksehr begrenzt sind, enthält sie keine Metadaten, die ständig heruntergeladen werden müssen.



In npm 7 package-lock.jsonenthält die Datei alles, was npm benötigt, um den Abhängigkeitsbaum eines Projekts vollständig zu erstellen. In npm 6 werden diese Daten nicht so bequem gespeichert. Wenn wir also auf eine alte Sperrdatei stoßen, müssen wir das System mit zusätzlicher Arbeit laden, aber dies wird für ein Projekt nur einmal durchgeführt.



Infolgedessen, auch wenn inyarn.lock Wenn Informationen über die Struktur des Abhängigkeitsbaums geschrieben wurden, müssen wir eine andere Datei verwenden, um zusätzliche Metadaten zu speichern.



Zukünftige Möglichkeiten



Worüber wir hier gesprochen haben, kann sich dramatisch ändern, wenn wir verschiedene neue Ansätze zum Platzieren von Abhängigkeiten von Datenträgern berücksichtigen. Dies sind pnpm, Garn 2 / Beere und PnP-Garn.



Während wir an npm 8 arbeiten, werden wir einen virtuellen Dateisystemansatz für Abhängigkeitsbäume untersuchen. Diese Idee orientierte sich an Tink und das Konzept wurde 2019 bestätigt. Wir diskutieren auch die Idee, zu etwas wie der von pnpm verwendeten Struktur überzugehen, obwohl dies in gewissem Sinne eine noch dramatischere Änderung ist als die Verwendung eines virtuellen Dateisystems.



Wenn sich alle Abhängigkeiten in einem zentralen Repository befinden und verschachtelte Abhängigkeiten nur durch symbolische Links oder ein virtuelles Dateisystem dargestellt werden, wäre die Modellierung der Struktur des Abhängigkeitsbaums für uns kein so wichtiges Problem. Wir benötigen jedoch noch mehr Metadaten, als die Datei bereitstellen kann yarn.lock. Daher ist es sinnvoller, das vorhandene Dateiformat zu aktualisieren und zu rationalisieren, package-lock.jsonals einen vollständigen Übergang zu yarn.lock.



Dies ist kein Artikel, der als "Über die Gefahren von yarn.lock" bezeichnet werden könnte.



Ich möchte darauf hinweisen, dass Yarn nach meinem Wissen zuverlässig korrekte Projektabhängigkeitsbäume generiert. Und für eine bestimmte Version von Yarn (zum Zeitpunkt dieses Schreibens gilt dies für alle neuen Versionen von Yarn) sind diese Bäume wie bei npm vollständig deterministisch.



Die Datei yarn.lockreicht aus, um deterministische Abhängigkeitsbäume mit derselben Version von Yarn zu erstellen. Wir können uns jedoch nicht auf Mechanismen verlassen, die von der Implementierung des Paketmanagers abhängen, da solche Mechanismen in vielen Tools verwendet werden. Dies gilt umso mehr, wenn Sie die Implementierung des Dateiformats berücksichtigenyarn.lockist nirgends formal dokumentiert. (Dies ist kein Problem, das nur bei Yarn auftritt. Npm ist dieselbe Situation. Das Dokumentieren von Dateiformaten ist eine ziemlich große Sache.) Der



beste Weg, um die Zuverlässigkeit beim Erstellen hochdeterministischer Abhängigkeitsbäume sicherzustellen, besteht auf lange Sicht darin, die Ergebnisse der Abhängigkeitsauflösung aufzuzeichnen. Verlassen Sie sich nicht auf die Überzeugung, dass zukünftige Implementierungen des Paketmanagers beim Auflösen von Abhängigkeiten denselben Pfad wie frühere Implementierungen verfolgen. Dieser Ansatz schränkt unsere Fähigkeit ein, optimierte Abhängigkeitsbäume zu entwerfen.



Abweichungen von der anfänglich festgelegten Struktur des Abhängigkeitsbaums sollten das Ergebnis des expliziten Wunsches des Benutzers sein. Solche Abweichungen sollten sich selbst dokumentieren und Änderungen an zuvor aufgezeichneten Daten in der Struktur des Abhängigkeitsbaums vornehmen.



Nur package-lock.jsonoder ein Mechanismus, der dieser Datei ähnlich ist, kann npm solche Möglichkeiten bieten.



Welchen Paketmanager verwenden Sie in Ihren JavaScript-Projekten?






All Articles