Klonen in node_modules hebben je in de steek gelaten? Ontdoe je bestandssysteem

(14 maart 2019)

Hoeveel exemplaren van [email protected] zouden je houdt van? (Foto door 浮萍 闪电 op Unsplash )

UPDATE: de hieronder beschreven implementatie heeft problemen met bestanden die afhankelijkheden hebben (require / import instructies ). Vanwege de structuur van node\_modules/, kan een identiek bestand op twee locaties verschillende versies van zijn eigen afhankelijkheden binnenhalen, dus om extra veilig te zijn, moet je jezelf beperken tot het ontdubbelen van bestanden met nul afhankelijkheden.

Een van de problemen die we bij Tradle tegenkwamen in verschillende grote Node.js / React Native / webapp-projecten is dat node\_modules eindigt met dubbele kopieën van bepaalde afhankelijkheden. Wanneer de app opstart, parseert het, voert het uit en verspilt het vervolgens meerdere keren geheugen aan dezelfde module.

In gewone oude Node.js is er meestal niet genoeg prestatieverbinding om over te zeuren, maar het doet nog steeds pijn om te weten dat het gebeurt. Op mobiel / internet is zeuren verplicht. Op een gegeven moment denk ik dat we 10 exemplaren hadden van een versie van readable-stream in onze React Native-app, en ik ben niet goed in emoji, dus ik laat Wat het voor me zeggen:

Wat leert Chaucer Engels

Wil je de pijn voelen? Hier is een voorbeeld van een afhankelijkheidsstructuur die het probleem veroorzaakt:

# 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

Uw project is afhankelijk van [email protected], maar twee van uw afhankelijkheden zijn afhankelijk van [email protected]. Je pakketbeheerder kan [email protected] niet ontdubbelen door het naar de root te hijsen omdat [email protected] daar zit. U krijgt dus twee exemplaren van [email protected], één onder depA en één onder depB . Ze zijn identiek. En u kunt er niets aan doen, tenzij u de tijd heeft om PRs te sturen naar de auteurs van depA en depB om ze te upgraden naar [email protected] (PRs die maanden kunnen duren voordat ze zijn samengevoegd), en wacht tot ze zijn samengevoegd. Dat of onderhoud vorken van beide modules. Dus ga je een ander probleem oplossen.

Ik weet niet hoe hij het allemaal wist ver terug

Gefeliciteerd! U heeft het ontdubbelingsprobleem nu uitgesteld naar uw bundler. Webpack, Browserify en Metro moeten het nu elk onafhankelijk oplossen. Hier is het openstaande probleem op Webpack. Het is open sinds 2017, en als ik Webpack was, zou ik geïrriteerd zijn als mensen het tot mijn probleem probeerden te maken.

Dit probleem is niet nieuw en grinnikt geduldig aan de zijlijn zoals de velen incarnaties van pakketbeheerders en bundlers voor Node.js en de browser die voorbijgaat. Het zal waarschijnlijk nog een tijdje bestaan, als een stevige digitale kakkerlak.

Gelukkig kunnen we het ons, met de recente belastingverlagingen, nu veroorloven om het licht aan het einde van de tunnel aan te doen. (Maar alleen voor de rijken).

Het licht aan het einde van de tunnel

Om de identieke kopieën van afhankelijkheden in Tradles React Native apps bundel te snoeien, hebben we besloten om niet te proberen voeg ondersteuning toe aan Metro Packager of doe een beroep op het Metro Packager-team. We leveren onze app ook met React Native for Web, dat wordt gebundeld met Webpack, en we wilden een truc die voor beide zou werken.

We besloten ons te richten op wat alle pakketbeheerders en bundelaars gemeen hebben: Het resolve() algoritme van Node.js. Dit is het algoritme dat wordt uitgevoerd wanneer u require("some-module") uitvoert om het absolute pad van een module op uw bestandssysteem te vinden. Zowel pakketbeheerders als bundelaars vertrouwen op dit algoritme, de eerste om de node\_modules directorystructuur te bouwen, de tweede om deze te doorlopen om de bundel te bouwen.

De oplossing is simple: doorloop node\_modules, identificeer alle duplicaten, kies een ervan als canoniek (bijv. de eerste alfabetisch) en herschrijf de *.js -bestanden in alle anderen verwijzen naar de canonieke. De *.js bestanden in de dubbele mappen zien er uiteindelijk zo uit:

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

Dit voelt een beetje vies aan, maar het werkt en maakt onze bundel kleiner en dus sneller te laden. De module is open source, probeer het gerust eens: https://github.com/tradle/dedupe-deps

Er moet een betere manier zijn

Sommigen van jullie rollen nu waarschijnlijk met je ogen en schreeuwen "gebruik gewoon pnpm!”Pas op, oogrollen kan verslavend zijn. En je wilt niet weten wat ze je zullen voorschrijven om de rand van oogverblindende terugtrekking te voorkomen.

pnpm is een relatief nieuwe inpakker in stad. Het gebruikt een combinatie van harde links en symlinks om te garanderen dat een module slechts één keer aanwezig is in de node\_modules van een bepaald project. In feite garandeert het dat het maar één keer in uw gehele bestandssysteem aanwezig zal zijn! Dit is erg cool. Op een dag zullen we allemaal pnpm gebruiken en alles zal op magische wijze werken, maar ik heb het gevoel dat er vandaag nog steeds compatibiliteitsproblemen zullen zijn met sommige bundlers.

… Ja, ik heb het net gecontroleerd en het is nog steeds een probleem in Metro, hoewel het lijkt alsof ze een oplossing naderen: https://github.com/facebook/metro/ pull / 257

Nog een stap verder

Dus terug naar onze op bestandssysteem gebaseerde oplossing. We kunnen nog een stap verder gaan en meer vliegen met dezelfde steen. Dit zijn waarschijnlijk nog grotere vogels dan de originele. Laten we ons voorstellen dat we deze afhankelijkheidsboom hebben:

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

Hier hebben we twee verschillende versies van [email protected]. Afhankelijk van de semver bereiken die zijn opgegeven voor lodash in depA en depB, kunnen ze heel goed ontdubbeld worden. Momenteel biedt de dedupe-deps -module geen oplossing voor dit scenario, maar we accepteren PRs graag.