Kloner i node_modules fikk du ned? Dedupere filsystemet ditt

(14. mars 2019)

Hvor mange eksemplarer av [email protected] ville du liker? (Foto av 浮萍 闪电 Unsplash )

OPPDATERING: implementeringen beskrevet nedenfor har problemer med filer som har avhengigheter (require / import utsagn ). På grunn av strukturen til node\_modules/, kan en identisk fil som bor på to steder trekke inn forskjellige versjoner av sine egne avhengigheter, så for å være ekstra trygg, bør du begrense deg til å deduplisere filer med null avhengigheter.

En av problemene vi traff på Tradle i flere store Node.js / React Native / web-app-prosjekter, er at node\_modules ender opp med duplikater av visse avhengigheter. Når appen starter, analyserer den, utfører den og kaster bort minne på den samme modulen flere ganger.

I vanlig gammel Node.js er det vanligvis ikke en stor nok ytelsesstraff å sutre over, men det gjør fortsatt vondt å vite at det skjer. I mobil / nett er sutring obligatorisk. På et tidspunkt tror jeg vi hadde 10 eksemplarer av en versjon av readable-stream i React Native-appen vår, og jeg er ikke god til emoji, så jeg lar Wat si det for meg:

Wat lærer Chaucer engelsk

Vil du føle smerten? Her er en eksempelvis avhengighetsstruktur som forårsaker problemet:

# 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

Prosjektet ditt avhenger av [email protected], men to av dine avhengigheter avhenger av [email protected]. Pakkeforvalteren din kan ikke deduplisere [email protected] ved å heise den opp til roten fordi [email protected] sitter der. Så du ender med to kopier av [email protected], en under depA og en under depB . De er identiske. Og det er ingenting du kan gjøre med det, med mindre du har tid til å sende PR til forfatterne av depA og depB for å oppgradere dem til [email protected] (PR som det kan ta måneder å bli slått sammen), og vent til de blir slått sammen. Det eller vedlikeholde gafler av begge disse modulene. Så du løser et annet problem.

Jeg vet ikke hvordan han visste det hele helt tilbake da

Gratulerer! Du har nå utsatt dedupliseringsproblemet til bundleren din. Webpack, Browserify og Metro må nå hver løse det uavhengig. Her er åpent nummer på Webpack. Den har vært åpen siden 2017, og hvis jeg var Webpack, ville jeg irritert meg over folk som prøvde å gjøre det til mitt problem.

Dette problemet er ikke nytt og har tålmodig snikret fra sidelinjen som de mange inkarnasjoner av pakkeforvaltere og bundlere for Node.js og nettleseren paraded av. Det vil trolig være en stund til, som en solid digital kakerlakk.

Heldigvis, med de nylige skattelettelsene, har vi nå råd til å skru på lyset på enden av tunnelen. (Men bare for de rike).

Lyset på enden av tunnelen

For å beskjære identiske kopier av avhengigheter i bunken til Tradles React Native-app, bestemte vi oss for ikke å prøve å legge til støtte for Metro Packager eller appellere til Metro Packager-teamet. Vi sender også appen vår med React Native for Web, som følger med Webpack, og vi ønsket et triks som ville fungere for begge deler.

Vi bestemte oss for å målrette det som alle pakkeadministratorer og bundlere har til felles: Node.jss resolve() algoritme. Dette er algoritmen som kjøres når du kjører require("some-module"), for å finne den absolutte banen til en modul i filsystemet. Både pakkeforvaltere og bundlere er avhengige av denne algoritmen, den første som bygger node\_modules katalogstruktur, den andre som krysser den for å bygge pakken.

Løsningen er enkelt: kryss node\_modules, identifiser alle duplikater, velg en av dem som kanonisk (f.eks. den første alfabetisk), og skriv om *.js -filene alle andre for å peke på den kanoniske. *.js -filene i duplikatmappene ser ut som dette:

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

Dette føles litt skittent, men det fungerer og gjør pakken vår mindre og dermed raskere å laste. Modulen er åpen kildekode, prøv den gjerne: https://github.com/tradle/dedupe-deps

Det må være en bedre måte

Noen av dere ruller sannsynligvis øynene nå og skriker “bare bruk pnpm!”Forsiktig der, øye-rulling kan være vanedannende. Og du vil ikke vite hva de vil foreskrive deg for å ta kanten av øye-rullende tilbaketrekning.

pnpm er en relativt ny pakker i by. Den bruker en kombinasjon av harde lenker og symlinker for å garantere at en modul bare er til stede en gang i et gitt prosjekt node\_modules. Faktisk garanterer det at det bare vil være til stede en gang i hele filsystemet! Dette er veldig kult. En dag bruker vi alle pnpm, og alt vil fungere magisk, men jeg har en følelse av at det i dag fremdeles vil være kompatibilitetsproblemer med noen bundlere.

... Ja, jeg har nettopp sjekket, og det er fortsatt et problem i Metro, selv om det ser ut til at de lukker inn en løsning: https://github.com/facebook/metro/ pull / 257

Tar det et skritt videre

Så tilbake til vår filsystembaserte løsning. Vi kan ta ting et skritt videre og drepe flere fugler i samme stein. Dette er uten tvil enda større fugler enn den opprinnelige. La oss forestille oss at vi har dette avhengighetstreet:

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

Her har vi to forskjellige versjoner av [email protected]. Avhengig av semver -områdene som er spesifisert for lodash i depA og depB, kan de veldig godt være deduperbare. For øyeblikket tar dedupe-deps -modulen ikke opp dette scenariet, men vi godtar gjerne PR.