Node.js ohne node_modules

Letzte Woche haben die Entwickler von Yarn (dem Paketmanager für Javascript) eine neue Funktion angekündigt - die Plug'n'Play-Installation. Mit dieser Funktion können Sie Node.js-Projekte ausführen, ohne den Ordner node_modules zu verwenden, in dem Projektabhängigkeiten normalerweise vor dem Start installiert werden. Die Beschreibung der Funktion erklärt, dass node_modules nicht mehr benötigt werden - die Module werden aus dem allgemeinen Cache des Paketmanagers geladen.


Gleichzeitig kündigten NPM-Entwickler eine ähnliche Lösung für das Problem an.


Schauen wir uns diese Lösungen genauer an und versuchen Sie, sie in realen Projekten zu testen.


Problemgeschichte


Ursprünglich basierte das modulare NodeJS-System vollständig auf dem Dateisystem. Jeder Aufruf von require() dem Dateisystem zugeordnet. Um Module von Drittanbietern zu organisieren, wurde der Ordner node_modules erfunden, in den wiederverwendbare Module und Bibliotheken heruntergeladen und installiert werden sollten. Somit erhielt jedes Projekt seine eigenen Abhängigkeiten, wodurch Speicherplatz ineffizient verschwendet wurde.


Die Installation von Abhängigkeiten nimmt in CI-Systemen den größten Teil der Erstellungszeit in Anspruch. Eine Beschleunigung dieses Schritts wirkt sich daher günstig auf die Erstellungszeit insgesamt aus.


Vereinfacht besteht die Installation von Modulen aus folgenden Schritten:


  1. Die spezifische Version des Moduls wird aus dem gültigen Intervall berechnet.
  2. Alle Module der erforderlichen Versionen werden aus dem Repository heruntergeladen und im lokalen Cache gespeichert
  3. Module aus dem lokalen Cache werden in den Projektordner node_modules kopiert

Wenn die ersten beiden Schritte bereits ausreichend optimiert sind und schnell ausgeführt werden, wenn Sie bereits zwischengespeicherte Module haben, bleibt der dritte Schritt im Vergleich zu den ersten Versionen von node und npm nahezu unverändert.


Der neue Ansatz schlägt vor, den dritten Schritt zu beseitigen und das eigentliche Kopieren von Dateien durch das Erstellen einer Tabelle zu ersetzen, in der die angeforderten Module ihren Kopien im lokalen Cache zugeordnet werden.


Verwenden von Symlinks


Anstatt Module tatsächlich zu kopieren, können Sie einen Symlink zu ihrer Position im Cache hinzufügen. Dieser Ansatz ist in PNPM implementiert, einem weiteren alternativen Paketmanager. Der Ansatz mag gut funktionieren, aber bei Symlinks gibt es viele Probleme, die mit dem doppelten Speicherort der Datei, der Suche nach benachbarten Modulen usw. verbunden sind. Darüber hinaus ist das Erstellen von Symlinks eine Dateioperation, die ich auf ideale Weise vermeiden möchte.


Versuchen Garn PNP


Weitere Informationen zu dieser Funktion finden Sie in der offiziellen Beschreibung . Dieser Absatz enthält seine kurze Nacherzählung.


Die PNP-fähige Version von Yarn befindet sich jetzt in Feature-Branch Yarn- PNP.


Wir klonen das Repository lokal mit dem gewünschten Zweig


 git clone git@github.com:yarnpkg/yarn.git --branch yarn-pnp 

Die Garnmontageanleitung ist hier , die Schritte sind sehr trivial.


Fügen Sie nach Abschluss des Builds der benutzerdefinierten Version des Garns einen Alias ​​hinzu und beginnen Sie damit zu arbeiten:


 alias yarn-local="node $PWD/lib/cli/index.js" 

Plug'n'play wird auf zwei Arten aktiviert: entweder über das Flag: yarn --pnp oder durch zusätzliche Konfiguration in package.json : "installConfig": {"pnp": true} .


Als Beispiel haben Garnentwickler bereits ein Demo-Projekt vorbereitet. Es verfügt über Webpack, Babel und andere für ein modernes Frontend typische Tools. Versuchen wir, die Abhängigkeiten auf unterschiedliche Weise zu ermitteln und die folgenden Ergebnisse zu erzielen:


  • Typische yarn : 19s
  • Installation über yarn --pnp : 3s

Vor der Messung wurde eine Kaltinstallation durchgeführt, sodass sich alle erforderlichen Module bereits im Cache befanden.


Mal sehen, wie es funktioniert. Nach einer pnp-Installation wird im Projektstamm eine zusätzliche .pnp.js Datei erstellt, die eine Überschreibung der nativen Logik in der in Node.js integrierten Module-Klasse enthält. Durch das Laden dieser Datei in unseren Code geben wir der Funktion require() die Möglichkeit, Module aus dem globalen Cache node_modules und nicht node_modules . Alle integrierten Garnbefehle, wie z. B. yarn start oder yarn test , laden diese Datei standardmäßig vor, sodass Sie keine Änderungen an Ihrem Code vornehmen müssen, wenn Sie bereits zuvor Garn verwendet haben.


Zusätzlich zu den Zuordnungsmodulen führt pnp.js eine zusätzliche Abhängigkeitsüberprüfung durch. Wenn Sie versuchen, require('test') ohne deklarierte Abhängigkeit in package.json , wird der folgende Fehler package.json : Error: You cannot require a package ("test") that is not declared in your dependencies . Diese Verbesserung sollte die Zuverlässigkeit und Vorhersagbarkeit des Codes erhöhen.


Unter den Mängeln des neuen Ansatzes ist anzumerken, dass eine zusätzliche Integration für Tools erforderlich ist, die ohne die integrierten Knotenmechanismen direkt mit dem Verzeichnis node_modules arbeiteten. Beispielsweise benötigen Webpack und andere Front-End-Builder zusätzliche Plugins, damit sie die für die Bündelung erforderlichen Dateien finden können.


Im Demo-Projekt gibt es Skizzen von Resolvern für Eslint, Jest, Rollup und Webpack.


In meinem Experiment gab es immer noch Probleme mit Typescript, was sehr mit dem Vorhandensein von node_modules zusammenhängt, und es gibt keine einfache Möglichkeit, die Modulsuchstrategie zu überschreiben.


Es wird auch Probleme mit Postintall-Skripten geben. Da das Modul im Cache verbleibt, können Skripte nach der Installation, die ihren Status ändern (z. B. zusätzliche Dateien hochladen), den Cache beschädigen und andere davon abhängige Projekte beschädigen. --ignore-scripts empfehlen, die Skriptausführung mit dem --ignore-scripts deaktivieren. Sie hatten bereits experimentiert, diese Flagge standardmäßig für alle Projekte in Facebook zu aktivieren, und fanden keine ernsthaften Probleme. Auf lange Sicht scheint das Aufgeben von Postinstall-Skripten angesichts bekannter Sicherheitsprobleme ein guter Schritt zu sein.


Ich versuche NPM zu basteln


Das NPM-Team kündigte auch seine alternative Lösung an. Ihr neues Tool, tink, wird mit einem separaten, NPM-unabhängigen Modul geliefert . Tink empfängt die Datei package-lock.json als package-lock.json , die automatisch generiert wird, wenn die npm install . Basierend auf der Sperrdatei generiert tink eine Datei node_modules/.package-map.json , in der die Projektion lokaler Module an ihrem tatsächlichen Speicherort im Cache node_modules/.package-map.json wird.


Im Gegensatz zu Yarn gibt es keine Hook-Datei, die zum Patchen in Ihr Projekt vorinstalliert werden kann. Stattdessen wird empfohlen, anstelle des node den Befehl tink zu verwenden, um die richtige Umgebung zu erhalten. Dieser Ansatz ist weniger ergonomisch, da Änderungen an Ihrem Code erforderlich sind, damit er funktioniert. Als Proof-of-Concept reicht dies jedoch aus.


Ich habe versucht, die Installationsgeschwindigkeit der Module mit den tink npm ci und tink zu vergleichen, aber tink war noch langsamer, sodass ich keine Ergebnisse gebe. Offensichtlich ist dieses Projekt im Vergleich zu Garn viel rauer und überhaupt nicht optimiert. Nun, wir werden auf neue Releases warten.


Fazit


Das Ablehnen des Verzeichnisses node_modules ist angesichts der Erfahrung mit anderen Sprachen, in denen dieser Ansatz ursprünglich nicht vorhanden war, ein logischer Schritt. Dies wirkt sich günstig auf die Build-Geschwindigkeit bei CI-Systemen aus, bei denen es möglich ist, den Paket-Cache zwischen Builds zu speichern. Wenn Sie außerdem den .pnp.js und die Datei .pnp.js von einem Computer auf einen anderen übertragen, können Sie die Umgebung reproduzieren, ohne Yarn zu starten. Dies kann in Container-Build-Systemen hilfreich sein: .pnp.js Sie das Verzeichnis mit dem Cache ein, legen Sie die Datei .pnp.js , und Sie können die Tests sofort ausführen.


Der neue Ansatz sieht ungewöhnlich aus und bricht einige etablierte Praktiken auf der Grundlage der Tatsache, dass alle Module immer in node_modules verfügbar sind. Die Datei .pnp.js bietet jedoch eine API, mit der Sie von der realen Position der Dateien abstrahieren und mit dem virtuellen Baum arbeiten können. Darüber hinaus gibt es als letzten Ausweg den Befehl yarn unplug --persist , mit dem ein Modul aus dem Cache extrahiert und lokal in node_modules .


Auf jeden Fall wurde noch nichts finalisiert, auch wenn die Pull-Anfrage in Yarn noch nicht eingegangen ist, sollten wir mit Änderungen rechnen. Für mich war es jedoch interessant, die Alpha-Version der Funktion in der Praxis auszuprobieren und sie an einigen meiner persönlichen Projekte zu testen und sicherzustellen, dass dieser Ansatz wirklich funktioniert und die Installation beschleunigt.


Referenzen


Source: https://habr.com/ru/post/de423487/


All Articles