I cloni in node_modules ti hanno messo giù? Deduplica il tuo filesystem

(14 marzo 2019)

Quante copie di [email protected] sarebbero ti piace? (Foto di 浮萍 闪电 su Unsplash )

AGGIORNAMENTO: limplementazione descritta di seguito presenta problemi con i file che hanno dipendenze (istruzioni require / import ). A causa della struttura di node\_modules/, un file identico che risiede in due posizioni potrebbe estrarre versioni diverse delle proprie dipendenze, quindi per essere ancora più sicuro, dovresti limitarti a deduplicare file con zero dipendenze.

Uno dei problemi che abbiamo riscontrato a Tradle in diversi grandi progetti di app Node.js / React Native / web è che node\_modules finisce con le copie duplicate di alcune dipendenze. Quando lapp si avvia, analizza, esegue e quindi spreca memoria sullo stesso modulo più volte.

Nel normale vecchio Node.js, di solito non cè una penalità di prestazioni abbastanza grande per lamentarsi, ma fa ancora male sapere che sta succedendo. In mobile / web, piagnucolare è obbligatorio. Ad un certo punto penso che avessimo 10 copie di una versione di readable-stream nella nostra app React Native e non sono bravo con le emoji, quindi lascerò che Wat lo dica per me:

Wat insegna inglese Chaucer

Vuoi sentire il dolore? Di seguito è riportato un esempio di struttura delle dipendenze che causa il 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

Il tuo progetto dipende da [email protected], ma due dei tuoi le dipendenze dipendono da [email protected]. Il tuo gestore di pacchetti non può deduplicare [email protected] sollevandolo alla radice perché [email protected] è lì. Quindi ti ritroverai con due copie di [email protected], una sotto depA e una sotto depB . Sono identici. E non cè niente che puoi fare al riguardo, a meno che tu non abbia il tempo di inviare PR agli autori di depA e depB per aggiornarli a [email protected] (PR che potrebbero richiedere mesi per essere unite) e attendi che vengano unite. Quello o mantenere le fork di entrambi i moduli. Quindi risolvi qualche altro problema.

Non so come facesse a saperlo tutto molto tempo fa

Congratulazioni! Ora hai rimandato il problema di deduplicazione al tuo bundler. Webpack, Browserify e Metro ora devono risolverlo in modo indipendente. Ecco il problema aperto su Webpack. È aperto dal 2017 e, se fossi Webpack, sarei infastidito dal fatto che le persone tentassero di renderlo il mio problema.

Questo problema non è nuovo ed è stato pazientemente ridacchiando da bordo campo come molti incarnazioni di gestori di pacchetti e bundler per Node.js e il browser presentato da. Probabilmente durerà ancora un po , come un sostanzioso scarafaggio digitale.

Fortunatamente, con i recenti tagli alle tasse, ora possiamo permetterci di accendere la luce alla fine del tunnel. (Ma solo per i ricchi).

La luce alla fine del tunnel

Per eliminare le copie identiche delle dipendenze nel bundle dellapp React Native di Tradle, abbiamo deciso di non provare a aggiungi il supporto a Metro Packager o fai appello al team di Metro Packager. Spediamo anche la nostra app con React Native for Web, che si integra con Webpack, e volevamo un trucco che funzionasse per entrambi.

Abbiamo deciso di indirizzare la cosa che tutti i gestori di pacchetti e i bundler hanno in comune: Algoritmo resolve() di Node.js. Questo è lalgoritmo che viene eseguito quando esegui require("some-module"), per trovare il percorso assoluto di un modulo sul tuo filesystem. Sia i gestori di pacchetti che i bundler si affidano a questo algoritmo, il primo per costruire la struttura della directory node\_modules, il secondo per attraversarla per costruire il bundle.

La soluzione è semplice: traverse node\_modules, identifica tutti i duplicati, scegline uno come canonico (ad esempio il primo in ordine alfabetico) e riscrivi i file *.js in tutti gli altri puntano a quello canonico. I file *.js nelle cartelle duplicate finiscono per avere questo aspetto:

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

Sembra un po sporco, ma funziona e rende il nostro pacchetto più piccolo e quindi più veloce da caricare. Il modulo è open source, sentiti libero di provarlo: https://github.com/tradle/dedupe-deps

Deve esserci un modo migliore

Alcuni di voi probabilmente stanno roteando gli occhi adesso e gridano "usa solo pnpm!"Attento lì, alzare gli occhi al cielo può creare dipendenza. E non vuoi sapere cosa ti prescriveranno per evitare un astinenza strabiliante.

pnpm è un pacchettizzatore relativamente nuovo in cittadina. Utilizza una combinazione di collegamenti fisici e collegamenti simbolici per garantire che un modulo sia presente solo una volta nel node\_modules di un dato progetto. In effetti, garantisce che sarà presente solo una volta nellintero filesystem! Questo è molto bello. Un giorno useremo tutti pnpm e tutto magicamente funzionerà, ma ho la sensazione che oggi ci saranno ancora problemi di compatibilità con alcuni bundler.

... Sì, ho appena controllato ed è ancora un problema in Metro, anche se sembra che si stiano avvicinando a una soluzione: https://github.com/facebook/metro/ pull / 257

Fare un ulteriore passo avanti

Torniamo alla nostra soluzione basata su filesystem. Possiamo fare un ulteriore passo avanti e uccidere più piccioni con la stessa pietra. Probabilmente, questi sono uccelli ancora più grandi delloriginale. Immaginiamo di avere questo albero delle dipendenze:

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

Qui abbiamo due diverse versioni di [email protected]. Tuttavia, a seconda degli intervalli di semver specificati per lodash in depA e depB, potrebbero benissimo essere deduplicabili. Attualmente il modulo dedupe-deps non affronta questo scenario, ma siamo lieti di accettare PR.