Klone in node_modules haben dich runtergebracht? Deduplizieren Sie Ihr Dateisystem

(14. März 2019)

Wie viele Kopien von [email protected] würden du magst? (Foto von 浮萍 闪电 auf Unsplash )

UPDATE: Die unten beschriebene Implementierung weist Probleme mit Dateien auf, die Abhängigkeiten aufweisen (require / import -Anweisungen ). Aufgrund der Struktur von node\_modules/ kann eine identische Datei, die sich an zwei Speicherorten befindet, unterschiedliche Versionen ihrer eigenen Abhängigkeiten enthalten. Um besonders sicher zu sein, sollten Sie sich darauf beschränken, Dateien mit Null zu deduplizieren Abhängigkeiten.

Eines der Probleme, auf die wir bei Tradle in mehreren großen Node.js / React Native / Web App-Projekten gestoßen sind, ist, dass node\_modules doppelte Kopien von hat bestimmte Abhängigkeiten. Wenn die App gestartet wird, wird dasselbe Modul mehrmals analysiert, ausgeführt und anschließend Speicher verschwendet.

In normalen alten Node.js gibt es normalerweise keine ausreichend große Leistungseinbußen, um darüber zu jammern. aber es tut immer noch weh zu wissen, dass es passiert. In Mobile / Web ist Jammern obligatorisch. Ich glaube, wir hatten einmal 10 Kopien einer Version von readable-stream in unserer React Native-App, und ich bin nicht gut in Emoji, also lasse ich Wat es für mich sagen:

Wat unterrichtet Chaucer-Englisch

Willst du den Schmerz fühlen? Hier ist eine Beispielabhängigkeitsstruktur, die das Problem verursacht:

# assume for simplicity that deps specify exact versions, i.e.:
# myProject"s package.json specifies "lodash": "4.0.0"
# depA and depB"s package.json specifies "lodash": "3.0.0"myProject/
node\_modules/
lodash # 4.0.0
depA
node\_modules/lodash # 3.0.0
depB
node\_modules/lodash # 3.0.0 <- not cool

Ihr Projekt hängt von [email protected] ab, aber von zwei Ihrer Abhängigkeiten hängen von [email protected] ab. Ihr Paketmanager kann [email protected] nicht deduplizieren, indem er es an den Stamm hochzieht, da [email protected] dort sitzt. Sie erhalten also zwei Kopien von [email protected], eine unter depA und eine unter depB . Sie sind identisch. Und Sie können nichts dagegen tun, es sei denn, Sie haben die Zeit, PRs an die Autoren von depA und depB zu senden, um sie zu aktualisieren [email protected] (PRs, deren Zusammenführung Monate dauern kann) und warten, bis sie zusammengeführt werden. Das oder Wartungsgabeln dieser beiden Module. Also lösen Sie ein anderes Problem.

Ich weiß nicht, woher er das alles wusste damals

Herzlichen Glückwunsch! Sie haben das Deduplizierungsproblem jetzt auf Ihren Bundler verschoben. Webpack, Browserify und Metro müssen es nun jeweils unabhängig voneinander lösen. Hier ist das offene Problem in Webpack. Es ist seit 2017 geöffnet, und wenn ich Webpack wäre, würde ich mich über Leute ärgern, die versuchen, es zu meinem Problem zu machen.

Dieses Problem ist nicht neu und hat wie die vielen geduldig von der Seitenlinie gekichert Inkarnationen von Paketmanagern und Bundlern für Node.js und den von vorgeführten Browser. Es wird wahrscheinlich noch eine Weile dauern, wie eine herzhafte digitale Kakerlake.

Glücklicherweise können wir es uns angesichts der jüngsten Steuersenkungen jetzt leisten, das Licht am Ende des Tunnels einzuschalten. (Aber nur für die Reichen).

Das Licht am Ende des Tunnels

Um die identischen Kopien der Abhängigkeiten im Bundle der React Native-App von Tradle zu entfernen, haben wir beschlossen, dies nicht zu versuchen Unterstützung für Metro Packager hinzufügen oder das Metro Packager-Team ansprechen. Wir liefern unsere App auch mit React Native for Web aus, das mit Webpack gebündelt ist, und wir wollten einen Trick, der für beide funktioniert.

Wir haben uns entschlossen, das Ziel zu erreichen, das alle Paketmanager und Bundler gemeinsam haben: Der resolve() -Algorithmus von Node.js. Dies ist der Algorithmus, der ausgeführt wird, wenn Sie require("some-module") ausführen, um den absoluten Pfad eines Moduls in Ihrem Dateisystem zu ermitteln. Sowohl Paketmanager als auch Bundler verlassen sich auf diesen Algorithmus. Der erste erstellt die Verzeichnisstruktur node\_modules und der zweite durchläuft sie, um das Bundle zu erstellen.

Die Lösung lautet Einfach: Durchqueren Sie node\_modules, identifizieren Sie alle Duplikate, wählen Sie eines als kanonisch aus (z. B. das erste alphabetisch) und schreiben Sie die *.js -Dateien in neu alle anderen sollen auf den kanonischen hinweisen. Die *.js -Dateien in den doppelten Ordnern sehen am Ende folgendermaßen aus:

// deduped by dedupe-deps
module.exports = require("../../../changes-feed/node\_modules/readable-stream/duplex.js")

Das fühlt sich etwas schmutzig an, aber es funktioniert und macht unser Bundle kleiner und damit schneller zu laden. Das Modul ist Open Source. Probieren Sie es aus: https://github.com/tradle/dedupe-deps

Es muss einen besseren Weg geben

Einige von Ihnen verdrehen jetzt wahrscheinlich die Augen und schreien: "Benutze einfach pnpm!„Vorsicht, Augenrollen kann süchtig machen. Und Sie möchten nicht wissen, was sie Ihnen verschreiben, um den augenfälligen Rückzug zu vereinfachen.

pnpm ist ein relativ neuer Verpacker Stadt, Dorf. Es verwendet eine Kombination aus Hardlinks und Symlinks, um sicherzustellen, dass ein Modul in node\_modules eines bestimmten Projekts nur einmal vorhanden ist. Tatsächlich garantiert es, dass es in Ihrem gesamten Dateisystem nur einmal vorhanden ist! Das ist sehr cool. Eines Tages werden wir alle pnpm verwenden und alles wird auf magische Weise funktionieren, aber ich habe das Gefühl, dass es heute noch Kompatibilitätsprobleme mit einigen Bundlern geben wird.

… Ja, ich habe es gerade überprüft und es ist immer noch ein Problem in Metro, obwohl es so aussieht, als würden sie sich einer Lösung nähern: https://github.com/facebook/metro/ pull / 257

Noch einen Schritt weiter

Also zurück zu unserer dateisystembasierten Lösung. Wir können noch einen Schritt weiter gehen und mehr Vögel mit demselben Stein töten. Das sind wohl noch größere Vögel als die ursprünglichen. Stellen wir uns vor, wir haben diesen Abhängigkeitsbaum:

myProject/
node\_modules/
lodash # 4.0.0
depA
node\_modules/lodash # 3.0.5
depB
node\_modules/lodash # 3.0.0 <- not cool

Hier haben wir zwei verschiedene Versionen von [email protected]. Abhängig von den semver Bereichen, die für lodash in depA angegeben sind und depB können sie durchaus deduplizierbar sein. Derzeit behandelt das Modul dedupe-deps dieses Szenario nicht, wir akzeptieren jedoch gerne PRs.