Clonele din node_modules v-au dat jos? Reduceți sistemul de fișiere

(14 martie 2019)

Câte copii ale [email protected] iti place? (Foto de 浮萍 闪电 pe Unsplash )

UPDATE: implementarea descrisă mai jos are probleme cu fișierele care au dependențe (require / import ). Datorită structurii node\_modules/, un fișier identic care trăiește în două locații ar putea extrage versiuni diferite ale propriilor dependențe, astfel încât pentru a fi mai sigur, ar trebui să vă limitați la deduplicarea fișierelor cu zero dependențe.

Una dintre problemele pe care le-am lovit la Tradle în mai multe proiecte mari Node.js / React Native / web app este că node\_modules se termină cu copii duplicate ale anumite dependențe. Când aplicația pornește, analizează, execută și apoi irosește memoria de pe același modul de mai multe ori.

În Node.js vechi obișnuit, de obicei nu există o penalizare de performanță suficient de mare pentru a vă plânge, dar încă mă doare să știi că se întâmplă. În mobil / web, văicăritul este obligatoriu. La un moment dat, cred că am avut 10 copii ale unei versiuni de readable-stream în aplicația noastră React Native și nu mă pricep la emoji, așa că îl voi lăsa pe Wat să-mi spună:

Wat predă engleza Chaucer

Vrei să simți durerea? Iată un exemplu de structură de dependență care cauzează problema:

# 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

Proiectul dvs. depinde de [email protected], dar două dintre dependențele depind de [email protected]. Managerul de pachete nu poate deduplica [email protected] ridicându-l până la rădăcină, deoarece [email protected] stă acolo. Așadar, ajungeți la două copii ale [email protected], una sub depA și una sub depB . Sunt identice. Și nu puteți face nimic în acest sens, cu excepția cazului în care aveți timp să trimiteți PR către autorii depA și depB pentru a le actualiza la [email protected] (PR-uri care ar putea dura câteva luni pentru a se îmbina) și așteptați ca acestea să fie îmbinate. Asta sau menține furcile ambelor module. Așadar, rezolvați o altă problemă.

Nu știu cum a știut-o înapoi atunci

Felicitări! Ați amânat problema de deduplicare către grupul dvs. Webpack, Browserify și Metro trebuie acum să le rezolve independent. Iată numărul deschis pe Webpack. A fost deschisă din 2017 și, dacă aș fi Webpack, aș fi supărat de oamenii care încearcă să facă din asta problema mea.

Această problemă nu este nouă și a răbdat cu răbdare de pe margine ca multe întrupările managerilor și pachetelor de pachete pentru Node.js și browserul defilat de. Probabil că va mai exista o vreme, ca un gândac digital consistent.

Din fericire, odată cu reducerile fiscale recente, ne putem permite acum să aprindem lumina la capătul tunelului. (Dar doar pentru cei bogați).

Lumina de la capătul tunelului

Pentru a tăia copiile identice ale dependențelor din pachetul de aplicații Tradct React Native, am decis să nu încercăm să adăugați asistență Metro Packager sau faceți apel la echipa Metro Packager. De asemenea, livrăm aplicația noastră cu React Native pentru Web, care include pachete cu Webpack și am vrut un truc care să funcționeze pentru ambele.

Am decis să vizăm lucrul pe care toți managerii de pachete și grupurile îl au în comun: Algoritmul resolve() al lui Node.js. Acesta este algoritmul executat când rulați require("some-module"), pentru a găsi calea absolută a unui modul din sistemul de fișiere. Atât managerii de pachete, cât și pachetele se bazează pe acest algoritm, primul care construiește structura directorului node\_modules, al doilea care îl traversează pentru a construi pachetul.

Soluția este simplu: traversați node\_modules, identificați toate duplicatele, alegeți unul dintre ele ca canonice (de exemplu, primul alfabetic) și rescrieți fișierele *.js în toți ceilalți să-l arate pe cel canonic. Fișierele *.js din folderele duplicate ajung să arate astfel:

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

Acest lucru se simte cam murdar, dar funcționează și face pachetul nostru mai mic și, astfel, mai rapid de încărcat. Modulul este open source, nu ezitați să încercați: https://github.com/tradle/dedupe-deps

Trebuie să existe o modalitate mai bună

Unii dintre voi probabil că vă dați ochii peste cap acum și țipați „folosiți doar pnpm!”Atenție acolo, mișcarea ochilor poate crea dependență. Și nu doriți să știți ce vă vor prescrie pentru a elimina avantajul retragerii cu ochii.

pnpm este un ambalator relativ nou în oraș. Acesta folosește o combinație de legături dure și link-uri simbolice pentru a garanta că un modul este prezent o singură dată în node\_modules unui anumit proiect. De fapt, garantează că va fi prezent o singură dată în întregul sistem de fișiere! Este foarte tare. Într-o zi vom folosi cu toții pnpm și totul va funcționa în mod magic, dar am sentimentul că astăzi vor exista în continuare probleme de compatibilitate cu niște pachete.

... Da, tocmai am verificat și este încă o problemă în Metro, deși se pare că se apropie de o soluție: https://github.com/facebook/metro/ pull / 257

Făcând un pas mai departe

Deci, înapoi la soluția noastră bazată pe sistemul de fișiere. Putem face lucrurile cu un pas mai departe și putem ucide mai multe păsări cu aceeași piatră. Probabil că acestea sunt păsări chiar mai mari decât cea originală. Să ne imaginăm că avem acest arbore de dependență:

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

Aici avem două versiuni diferite ale [email protected]. Cu toate acestea, în funcție de intervalele semver specificate pentru lodash în depA și depB, pot fi foarte bine deductibile. În prezent, modulul dedupe-deps nu abordează acest scenariu, dar suntem încântați să acceptăm PR-urile.