Kloner i node_modules fik dig ned? Dedupér dit filsystem

(14. mar. 2019)

Hvor mange kopier af [email protected] ville Du kan lide? (Foto af 浮萍 闪电 Uplash )

UPDATE: implementeringen beskrevet nedenfor har problemer med filer, der har afhængigheder (require / import udsagn ). På grund af strukturen af ​​node\_modules/ kan en identisk fil, der bor to steder muligvis trække i forskellige versioner af sine egne afhængigheder, så for at være ekstra sikker skal du begrænse dig til at deduplicere filer med nul afhængigheder.

Et af de problemer, vi rammer på Tradle i flere store Node.js / React Native / web-app-projekter, er at node\_modules ender med duplikater af visse afhængigheder. Når appen starter, parses den, udføres og spildes derefter hukommelsen på det samme modul flere gange.

I almindelig gammel Node.js er der normalt ikke en stor nok præstationsstraff til at klynke over, men det gør stadig ondt at vide, at det sker. På mobil / internet er klynke obligatorisk. På et tidspunkt tror jeg, vi havde 10 eksemplarer af en version af readable-stream i vores React Native-app, og jeg er ikke god til emoji, så jeg lader Wat sige det for mig:

Wat lærer Chaucer engelsk

Vil du føle smerten? Her er en eksempelafhængighedsstruktur, der forårsager 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

Dit projekt afhænger af [email protected], men to af dine afhængigheder afhænger af [email protected]. Din pakkehåndtering kan ikke deduplicere [email protected] ved at hejse det op til roden, fordi [email protected] sidder der. Så du ender med to kopier af [email protected], en under depA og en under depB . De er identiske. Og der er ikke noget, du kan gøre ved det, medmindre du har tid til at sende PRer til forfatterne af depA og depB for at opgradere dem til [email protected] (PRer, der kan tage måneder at blive flettet), og vent på, at de bliver flettet. Det eller vedligeholder gafler af begge disse moduler. Så du løser et andet problem.

Jeg ved ikke, hvordan han vidste det hele langt tilbage

Tillykke! Du har nu udsat deduplikationsproblemet til din bundler. Webpack, Browserify og Metro skal nu hver løse det uafhængigt. Her er åbent nummer på Webpack. Det har været åbent siden 2017, og hvis jeg var Webpack, ville jeg irritere mig over folk, der forsøgte at gøre det til mit problem.

Dette problem er ikke nyt og har tålmodigt snikret fra sidelinjen som de mange inkarnationer af pakkeadministratorer og bundlere til Node.js og browseren paraderet af. Det vil sandsynligvis eksistere et stykke tid længere, som en stor digital kakerlak.

Heldigvis med de nylige skattelettelser har vi nu råd til at tænde lyset i slutningen af ​​tunnelen. (Men bare for de rige).

Lyset ved enden af ​​tunnelen

For at beskære de samme kopier af afhængigheder i bunden af ​​Tradles React Native-app besluttede vi ikke at forsøge at tilføje support til Metro Packager eller appellere til Metro Packager-teamet. Vi sender også vores app med React Native for Web, som er bundtet med Webpack, og vi ønskede et trick, der kunne fungere for begge.

Vi besluttede at målrette mod det, som alle pakkehåndtere og bundlere har til fælles: Node.jss resolve() algoritme. Dette er den algoritme, der udføres, når du kører require("some-module") for at finde den absolutte sti til et modul på dit filsystem. Både pakkeadministratorer og bundlere er afhængige af denne algoritme, den første til at opbygge node\_modules katalogstruktur, den anden til at krydse den for at oprette pakken.

Løsningen er simpelt: kryds node\_modules, identificer alle dubletter, vælg en af ​​dem som kanoniske (f.eks. den første alfabetisk), og omskriv *.js -filer i alle andre for at pege på den kanoniske. *.js filerne i de dobbelte mapper ender med at se sådan ud:

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

Dette føles lidt snavset, men det fungerer og gør vores pakke mindre og dermed hurtigere at indlæse. Modulet er open source, prøv det gerne: https://github.com/tradle/dedupe-deps

Der må være en bedre måde

Nogle af jer ruller sandsynligvis øjnene nu og skriger ”brug bare pnpm!”Forsigtig der kan øjenrulning være vanedannende. Og du vil ikke vide, hvad de ordinerer dig til at tage kanten af ​​øjenrullende tilbagetrækning.

pnpm er en relativt ny pakke i by. Det bruger en kombination af hårde links og symlinks til at garantere, at et modul kun er til stede en gang i et givet projekts node\_modules. Faktisk garanterer det, at det kun vil være til stede en gang i hele dit filsystem! Dette er meget sejt. En dag bruger vi alle pnpm, og alt fungerer magisk, men jeg har en fornemmelse af, at der i dag stadig vil være kompatibilitetsproblemer med nogle bundlere.

... Ja, jeg har lige tjekket, og det er stadig et problem i Metro, selvom det ser ud til, at de lukker ind på en løsning: https://github.com/facebook/metro/ pull / 257

At tage det et skridt videre

Så tilbage til vores filsystembaserede løsning. Vi kan tage tingene et skridt videre og dræbe flere fugle i samme sten. Formentlig er disse endnu større fugle end den oprindelige. Lad os forestille os, at vi har dette afhængighedstræ:

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 forskellige versioner af [email protected]. Afhængigt af semver -områderne, der er angivet for lodash i depA og depB, kan de meget vel være deduperbare. I øjeblikket behandler dedupe-deps -modulet ikke dette scenario, men vi accepterer gerne PRer.