¿Los clones en node_modules te deprimieron? Elimina tu sistema de archivos

(14 de marzo de 2019)

¿Cuántas copias de [email protected] ¿te gusta? (Foto de 浮萍 闪电 en Unsplash )

ACTUALIZACIÓN: la implementación que se describe a continuación tiene problemas con archivos que tienen dependencias (require / import declaraciones ). Debido a la estructura de node\_modules/, un archivo idéntico que vive en dos ubicaciones puede incluir diferentes versiones de sus propias dependencias, por lo que, para estar más seguro, debe limitarse a deduplicar archivos con cero dependencias.

Uno de los problemas que encontramos en Tradle en varios proyectos grandes de aplicaciones web Node.js / React Native / es que node\_modules termina con copias duplicadas de ciertas dependencias. Cuando la aplicación se inicia, analiza, ejecuta y luego desperdicia memoria en el mismo módulo varias veces.

En los viejos Node.js normales, generalmente no hay una penalización de rendimiento lo suficientemente grande para quejarse, pero todavía duele saber que está pasando. En móvil / web, lloriquear es obligatorio. En un momento, creo que teníamos 10 copias de una versión de readable-stream en nuestra aplicación React Native, y no soy bueno con los emoji, así que dejaré que Wat lo diga por mí:

Wat enseña inglés a Chaucer

¿Quieres sentir el dolor? Aquí hay una estructura de dependencia de muestra que causa el 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

Su proyecto depende de [email protected], pero dos de sus las dependencias dependen de [email protected]. Su administrador de paquetes no puede deduplicar [email protected] levantándolo hasta la raíz porque [email protected] está allí. Así que terminas con dos copias de [email protected], una debajo de depA y otra debajo de depB . Son idénticos. Y no hay nada que pueda hacer al respecto, a menos que tenga tiempo para enviar PR a los autores de depA y depB para actualizarlos a [email protected] (RP que podrían tardar meses en fusionarse) y espere a que se fusionen. Eso o mantener las bifurcaciones de ambos módulos. Ve a resolver otro problema.

No sé cómo lo supo todo el en ese entonces

¡Felicitaciones! Ahora ha transferido el problema de la deduplicación a su agrupador. Webpack, Browserify y Metro ahora necesitan resolverlo de forma independiente. Aquí está el problema abierto en Webpack. Ha estado abierto desde 2017, y si yo fuera Webpack, me molestaría que la gente intente convertirlo en mi problema.

Este problema no es nuevo y se ha estado riendo pacientemente desde el margen como muchos encarnaciones de administradores de paquetes y agrupadores para Node.js y el navegador desfilado por. Probablemente permanecerá por un tiempo más, como una cucaracha digital abundante.

Afortunadamente, con los recientes recortes de impuestos, ahora podemos permitirnos encender la luz al final del túnel. (Pero solo para los ricos).

La luz al final del túnel

Para eliminar las copias idénticas de dependencias en el paquete de la aplicación React Native de Tradle, decidimos no intentar agregue apoyo a Metro Packager o apele al equipo de Metro Packager. También enviamos nuestra aplicación con React Native for Web, que se incluye con Webpack, y queríamos un truco que funcionara para ambos.

Decidimos apuntar a lo que todos los administradores de paquetes y agrupadores tienen en común: El algoritmo resolve() de Node.js. Este es el algoritmo que se ejecuta cuando ejecuta require("some-module"), para encontrar la ruta absoluta de un módulo en su sistema de archivos. Tanto los administradores de paquetes como los empaquetadores se basan en este algoritmo, el primero en construir la estructura del directorio node\_modules, el segundo en recorrerlo para construir el paquete.

La solución es simple: recorra node\_modules, identifique todos los duplicados, elija uno de ellos como canónico (por ejemplo, el primero en orden alfabético) y reescriba los archivos *.js en todos los demás para apuntar al canónico. Los archivos *.js en las carpetas duplicadas terminan luciendo así:

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

Esto se siente un poco sucio, pero funciona y hace que nuestro paquete sea más pequeño y, por lo tanto, más rápido de cargar. El módulo es de código abierto, no dudes en probarlo: https://github.com/tradle/dedupe-deps

Debe haber una mejor manera

Algunos de ustedes probablemente estén poniendo los ojos en blanco ahora y gritando “¡solo use pnpm!“Con cuidado, poner los ojos en blanco puede ser adictivo. Y no quiere saber qué le recetarán para aliviar la abstinencia de los ojos en blanco.

pnpm es un empaquetador relativamente nuevo en pueblo. Utiliza una combinación de enlaces físicos y enlaces simbólicos para garantizar que un módulo solo esté presente una vez en el node\_modules de un proyecto determinado. De hecho, ¡garantiza que solo estará presente una vez en todo su sistema de archivos! Esto es muy genial. Algún día todos usaremos pnpm y todo funcionará mágicamente, pero tengo la sensación de que hoy todavía habrá problemas de compatibilidad con algunos paquetes.

… Sí, acabo de comprobar y sigue siendo un problema en Metro, aunque parece que se están acercando a una solución: https://github.com/facebook/metro/ pull / 257

Dando un paso más

Así que volvamos a nuestra solución basada en sistemas de archivos. Podemos llevar las cosas un paso más allá y matar más pájaros de un mismo tiro. Podría decirse que son aves aún más grandes que la original. Imaginemos que tenemos este árbol de dependencias:

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

Aquí tenemos dos versiones diferentes de [email protected]. Sin embargo, según los rangos semver especificados para lodash en depA y depB, es muy posible que se puedan deducir. Actualmente, el módulo dedupe-deps no aborda este escenario, pero estamos felices de aceptar RP.