Klony w node_modules cię przygnębiły? Usuń swój system plików

(14 marca 2019 r.)

Ile kopii [email protected] lubisz? (Zdjęcie: 浮萍 闪电 na Unsplash )

AKTUALIZACJA: implementacja opisana poniżej ma problemy z plikami, które mają zależności (require / import instrukcje ). Ze względu na strukturę node\_modules/ identyczny plik znajdujący się w dwóch lokalizacjach może pobierać różne wersje własnych zależności, więc aby być bardziej bezpiecznym, należy ograniczyć się do deduplikacji plików z wartością zero zależności.

Jednym z problemów, które napotkaliśmy w Tradle w kilku dużych projektach aplikacji Node.js / React Native / webowych, jest to, że node\_modules kończy się zduplikowanymi kopiami pewne zależności. Kiedy aplikacja się uruchamia, analizuje, wykonuje, a następnie marnuje pamięć na ten sam moduł wiele razy.

W zwykłym starym Node.js zwykle nie ma wystarczająco dużego spadku wydajności, aby narzekać, ale nadal boli wiedzieć, że to się dzieje. W przypadku urządzeń mobilnych / sieci jęczenie jest obowiązkowe. Wydaje mi się, że w pewnym momencie mieliśmy 10 kopii wersji readable-stream w naszej aplikacji React Native i nie jestem dobry w emoji, więc pozwolę Watowi powiedzieć to za mnie:

Wat uczy Chaucera angielskiego

Chcesz poczuć ból? Oto przykładowa struktura zależności, która powoduje problem:

# 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

Twój projekt zależy od [email protected], ale dwa z Twoich zależności zależą od [email protected]. Twój menedżer pakietów nie może deduplikować [email protected], podnosząc go do katalogu głównego, ponieważ znajduje się tam [email protected]. Otrzymujesz więc dwie kopie [email protected], jedną pod depA i jedną pod depB . Są identyczne. I nic nie możesz na to poradzić, chyba że masz czas, aby wysłać PR do autorów depA i depB, aby zaktualizować je do [email protected] (pliki PR, których scalenie może zająć miesiące) i poczekaj, aż zostaną scalone. To lub utrzymanie rozwidleń obu tych modułów. Więc idź rozwiązać jakiś inny problem.

Nie wiem, skąd on to wszystko wiedział dawno temu

Gratulacje! Odłożyłeś teraz problem z deduplikacją do swojego pakietu. Webpack, Browserify i Metro muszą teraz rozwiązać ten problem niezależnie. Oto otwarty problem dotyczący Webpack. Jest otwarty od 2017 roku i gdybym był Webpackiem, byłbym zirytowany, gdyby ludzie próbowali zrobić z tego mój problem.

Ten problem nie jest nowy i cierpliwie chichocze z boku, jak wielu inkarnacje menedżerów pakietów i bundlerów dla Node.js i przeglądarki. Prawdopodobnie będzie istniał jeszcze przez jakiś czas, jak porządny cyfrowy karaluch.

Na szczęście dzięki niedawnym obniżkom podatków możemy teraz pozwolić sobie na włączenie światła na końcu tunelu. (Ale tylko dla bogatych).

Światło na końcu tunelu

Aby usunąć identyczne kopie zależności w pakiecie aplikacji Tradle React Native, zdecydowaliśmy się nie próbować dodaj wsparcie do Metro Packager lub odwołaj się do zespołu Metro Packager. Dostarczamy również naszą aplikację z React Native for Web, który jest dołączany do Webpack, i chcieliśmy sztuczki, która zadziała w obu przypadkach.

Postanowiliśmy skupić się na tym, co łączy wszystkich menedżerów pakietów i programy pakujące: Algorytm Node.js resolve(). To jest algorytm, który jest wykonywany po uruchomieniu require("some-module") w celu znalezienia bezwzględnej ścieżki modułu w systemie plików. Zarówno menedżerowie pakietów, jak i programy pakujące opierają się na tym algorytmie. Pierwszy z nich tworzy strukturę katalogów node\_modules, a drugi przetwarza go, aby zbudować pakiet.

Rozwiązanie jest takie proste: przejdź przez node\_modules, zidentyfikuj wszystkie duplikaty, wybierz jeden z nich jako kanoniczny (np. pierwszy alfabetycznie) i przepisz pliki *.js w wszyscy inni, aby wskazać na kanoniczny. Pliki *.js w zduplikowanych folderach wyglądają tak:

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

To trochę brudne, ale działa i sprawia, że ​​nasz pakiet jest mniejszy, a tym samym szybciej się ładuje. Moduł jest typu open source, możesz go wypróbować: https://github.com/tradle/dedupe-deps

Musi być lepszy sposób

Niektórzy z was prawdopodobnie przewracają teraz oczami i krzyczą „po prostu użyj pnpm!„Ostrożnie, przewracanie oczami może być uzależniające. I nie chcesz wiedzieć, co ci zalecą, aby uniknąć przewracającego oczu wycofania.

pnpm to stosunkowo nowy paker w miasto. Używa kombinacji twardych linków i linków symbolicznych, aby zagwarantować, że moduł występuje tylko raz w node\_modules danego projektu. W rzeczywistości gwarantuje, że wystąpi tylko raz w całym systemie plików! To jest bardzo fajne. Któregoś dnia wszyscy użyjemy pnpm i wszystko będzie magicznie działać, ale mam wrażenie, że dzisiaj nadal będą występować problemy ze zgodnością z niektórymi pakietami.

… Tak, właśnie sprawdziłem i nadal jest to problem w Metro, chociaż wygląda na to, że zbliżają się do rozwiązania: https://github.com/facebook/metro/ pull / 257

O krok dalej

Wróćmy więc do naszego rozwiązania opartego na systemie plików. Możemy pójść o krok dalej i zabić więcej ptaków tym samym kamieniem. Zapewne są to jeszcze większe ptaki niż oryginał. Wyobraźmy sobie, że mamy to drzewo zależności:

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

Tutaj mamy dwie różne wersje [email protected]. Jednak w zależności od semver zakresów określonych dla lodash w depA i depB, bardzo dobrze można je deduplikować. Obecnie moduł dedupe-deps nie obejmuje tego scenariusza, ale z przyjemnością przyjmujemy PR.