Road to Hell Dependencias de JavaScript

Cada proyecto de JavaScript comienza con buenas intenciones, es decir, que sus creadores se prometen a sí mismos no usar demasiados paquetes NPM durante su desarrollo. Pero incluso si los desarrolladores hacen esfuerzos considerables para cumplir esta promesa, los paquetes de NPM penetran gradualmente en sus proyectos. El tamaño del archivo package.jsoncrece con el tiempo. Y con la package-lock.jsoninstalación de dependencias, se produce un verdadero horror, expresado en adiciones y eliminaciones de paquetes, especialmente notable con el próximo PR ... "Todo está bien", dice el líder del equipo. El resto del equipo asiente de acuerdo. ¿Qué otra cosa hacer? Todos disfrutamos el hecho de que el ecosistema de JavaScript está vivo y bien. No necesitamos reinventar la rueda cada vez e intentar resolver los problemas que ya han sido resueltos por la comunidad de código abierto.





Suponga que va a hacer un blog y quiere usar Gatsby.js. Intente agregar este generador de sitio basado en su proyecto. Ahora felicidades. Su proyecto solo tenía 19,000 dependencias adicionales. ¿Esto es normal? ¿Qué tan complejo puede volverse un árbol de dependencia de JavaScript? ¿Cómo se convierte un árbol de dependencias en un infierno? Vamos a resolverlo.

¿Qué es un paquete de JavaScript?


NPM (Node Package Manager, Node Package Manager) almacena el registro de paquetes más grande del mundo. Estos son paquetes de JavaScript. NPM es más que RubyGems, PyPi y Maven combinados. Esta conclusión se puede hacer sobre la base del análisis de los datos del proyecto Module Counts , que monitorea el número de paquetes en los registros populares.


Datos sobre la cantidad de paquetes en los registros populares

Puede pensar que en este gráfico se representan cantidades muy grandes de código. De la manera que es. Para convertir un proyecto en un paquete NPM, este paquete debe tener un archivopackage.json. Dicho paquete puede enviarse al registro NPM.

¿Qué es package.json?


Estas son las tareas que resuelve package.json:

  • Enumera los paquetes de los que depende su proyecto (esta es una lista de dependencias del proyecto).
  • En él, usando las reglas de versiones semánticas, se establecen versiones de paquetes de dependencia que su proyecto puede usar.
  • Le permite reproducir el entorno necesario para que el paquete funcione y, como resultado, simplifica la transferencia del proyecto a otros desarrolladores.

Un archivo package.jsonpuede considerarse como un archivo READMEbombeado con esteroides. Aquí puede describir las dependencias de su paquete, aquí puede escribir scripts que se ejecutan durante el ensamblaje y las pruebas del proyecto. El mismo archivo contiene información sobre la versión del proyecto especificada por su desarrollador y una descripción del proyecto. Estamos particularmente interesados ​​en la posibilidad package.jsonde especificar dependencias del proyecto.

Quizás el hecho de que las dependencias del proyecto estén indicadas en este archivo parece algo alarmante. Imagine que hay un paquete que depende de otro paquete, y este otro paquete depende de otro paquete. Tal cadena de dependencias puede ser arbitrariamente larga. Por esta razón, instalar el único paquete, Gatsby.js, significa equipar el proyecto con 19,000 dependencias adicionales.

Tipos de dependencia en package.json


Para comprender mejor cómo crecen las listas de dependencias del proyecto con el tiempo, hablemos sobre los diferentes tipos de dependencias que puede tener un proyecto. A saber, package.jsonse pueden encontrar las siguientes secciones que describen varias dependencias:

  • dependencies - estas son dependencias ordinarias, cuya funcionalidad se utiliza en el proyecto y a las que se accede desde su código.
  • devDependencies- Estas son dependencias de desarrollo. Por ejemplo, la biblioteca más bonita utilizada para formatear código.
  • peerDependencies - si las dependencias se escriben en esta sección, el desarrollador del paquete informa al que lo instalará que necesitará una versión específica del paquete especificado en esta sección.
  • optionalDependencies - enumeran dependencias opcionales, como la imposibilidad de instalación que no violará el proceso de instalación del paquete.
  • bundledDependencies — , . , NPM, , .

package-lock.json


Todos sabemos que el archivo package-lock.json, en el curso del trabajo en el proyecto, sufre cambios constantemente. Se le quita algo, se le agrega algo. Esto es especialmente notable cuando se ve PR que contiene una versión actualizada de este archivo. A menudo lo damos por sentado. Un archivo se package-lock.jsongenera automáticamente cada vez que cambia un archivo package.jsono carpeta node_modules. Esto le permite mantener el contenido del árbol de dependencias exactamente como estaba cuando instaló las dependencias del proyecto. Esto permite, al instalar el proyecto, reproducir el árbol de dependencias. Esto resuelve el problema de tener diferentes versiones del mismo paquete de diferentes desarrolladores.

Considere un proyecto cuyas dependencias incluyen React. La entrada correspondiente está disponible en package.json. Si miras el archivopackage-lock.json de este proyecto, entonces puede ver algo como lo siguiente:

    "react": {
      "version": "16.13.0",
      "resolved": "https://registry.npmjs.org/react/-/react-16.13.0.tgz",
      "integrity": "sha512-TSavZz2iSLkq5/oiE7gnFzmURKZMltmi193rm5HEoUDAXpzT9Kzw6oNZnGoai/4+fUnm7FqS5dwgUL34TujcWQ==",
      "requires": {
        "loose-envify": "^1.1.0",
        "object-assign": "^4.1.1",
        "prop-types": "^15.6.2"
      }
    }

Un archivo package-lock.jsones una lista grande de dependencias del proyecto. Aquí están las versiones de dependencia, las rutas (URI) a los módulos, los hashes utilizados para verificar la integridad del módulo y los paquetes necesarios para este módulo. Si lee este archivo, puede encontrar registros de todos los paquetes que React necesita. Aquí es donde reside el verdadero infierno de las dependencias. Todo lo que necesita el proyecto se describe aquí.

Comprender las dependencias de Gatsby.js


¿Cómo es que, habiendo establecido solo una dependencia, agregamos hasta 19,000 dependencias al proyecto? Se trata de dependencias de dependencia. Por eso tenemos lo que tenemos:

$ npm install --save gatsby

...

+ gatsby@2.19.28
added 1 package from 1 contributor, removed 9 packages, updated 10 packages and audited 19001 packages in 40.382s

Si observa package.json, allí solo puede encontrar una dependencia. Pero si lo miras package-lock.json, resulta que frente a nosotros hay un monstruo de casi 14 kilobytes. Una respuesta más detallada sobre el significado de todas esas líneas de código significa lo que package-lock.jsonse puede encontrar en el archivo package.jsonen el repositorio de Gatsby.js . Hay muchas dependencias directas, a saber, de acuerdo con los cálculos de npm , 132. Si cada una de estas dependencias tiene al menos una dependencia más, entonces el número total de dependencias del proyecto se duplicará, y tendrá 264 dependencias. Por supuesto, en el mundo real esto no es así. Cada dependencia directa del proyecto tiene más de 1 dependencia inherente. Como resultado, la lista de dependencias del proyecto es muy larga.

Por ejemplo, nos interesaremos cuántas veces se usa la biblioteca lodash como dependencia para otros paquetes :

$ npm ls lodash
example-js-package@1.0.0
└─┬ gatsby@2.19.28
  ├─┬ @babel/core@7.8.6
  │ ├─┬ @babel/generator@7.8.6
  │ │ └── lodash@4.17.15  deduped
  │ ├─┬ @babel/types@7.8.6
  │ │ └── lodash@4.17.15  deduped
  │ └── lodash@4.17.15  deduped
  ├─┬ @babel/traverse@7.8.6
  │ └── lodash@4.17.15  deduped
  ├─┬ @typescript-eslint/parser@2.22.0
  │ └─┬ @typescript-eslint/typescript-estree@2.22.0
  │   └── lodash@4.17.15  deduped
  ├─┬ babel-preset-gatsby@0.2.29
  │ └─┬ @babel/preset-env@7.8.6
  │   ├─┬ @babel/plugin-transform-block-scoping@7.8.3
  │   │ └── lodash@4.17.15  deduped
  │   ├─┬ @babel/plugin-transform-classes@7.8.6
  │   │ └─┬ @babel/helper-define-map@7.8.3
  │   │   └── lodash@4.17.15  deduped
  │   ├─┬ @babel/plugin-transform-modules-amd@7.8.3
  │   │ └─┬ @babel/helper-module-transforms@7.8.6
  │   │   └── lodash@4.17.15  deduped
  │   └─┬ @babel/plugin-transform-sticky-regex@7.8.3
  │     └─┬ @babel/helper-regex@7.8.3
  │       └── lodash@4.17.15  deduped
  ...

Afortunadamente, la mayoría de estas dependencias están representadas por la misma versión de lodash. Y con este enfoque, node_modulessolo habrá una carpeta de biblioteca lodash. Es cierto que este no suele ser el caso. A veces, diferentes paquetes necesitan diferentes versiones del mismo paquete. Es por eso que aparecieron muchos chistes sobre el gran tamaño de la carpeta node_modules. En nuestro caso, sin embargo, no todo es tan malo:

$ du -sh node_modules
200M    node_modules

200 megabytes no es tan malo. Vi cómo el tamaño de esta carpeta alcanza fácilmente los 700 MB. Si está interesado en aprender qué módulos ocupan más espacio, puede ejecutar el siguiente comando:

$ du -sh ./node_modules/* | sort -nr | grep '\dM.*'
 17M    ./node_modules/rxjs
8.4M    ./node_modules/@types
7.4M    ./node_modules/core-js
6.8M    ./node_modules/@babel
5.4M    ./node_modules/gatsby
5.2M    ./node_modules/eslint
4.8M    ./node_modules/lodash
3.6M    ./node_modules/graphql-compose
3.6M    ./node_modules/@typescript-eslint
3.5M    ./node_modules/webpack
3.4M    ./node_modules/moment
3.3M    ./node_modules/webpack-dev-server
3.2M    ./node_modules/caniuse-lite
3.1M    ./node_modules/graphql
...

Sí, rxjs es un paquete insidioso.

Aquí hay un comando simple que ayuda a reducir el tamaño de la carpeta node_modulesy simplificar su estructura:

$ npm dedup
moved 1 package and audited 18701 packages in 4.622s

51 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Durante la deduplicación, npm intenta simplificar la estructura del árbol de dependencias al encontrar las dependencias utilizadas por otras dependencias y moverlas para que puedan compartirse. Esto se aplica a nuestro ejemplo lodash. lodash @4.17.15Como resultado, se utilizan muchos paquetes para garantizar su operatividad, es suficiente instalar esta versión de la biblioteca solo una vez. Por supuesto, esta es la situación en la que nos encontramos desde el principio, solo estableciendo dependencias. Si en el proceso de trabajar en un proyecto package.jsonagrega nuevas dependencias, a veces se recomienda recordar al equipo npm dedup. Si usa el administrador de paquetes de hilo, hay un comando similar que parece deducir hilo. Pero, de hecho, no es necesario, ya que la optimización de dependencia se realiza automáticamente cuando se ejecuta el comando yarn install.

Visualización de dependencia


¿Interesado en una representación gráfica de las dependencias de su proyecto? Si es así, puede crear dicha presentación utilizando herramientas especiales. Consideremos algunos de ellos.

El siguiente es un resultado de visualización de dependencia obtenido usando npm.anvaka.com/ .


Visualización de dependencias usando npm.anvaka.com

Aquí puede ver las dependencias de las dependencias del paquete de proyecto Gatsby.js. El resultado es similar a una gran web. El proyecto Gatsby.js tiene tantas dependencias que esta "web" casi "colgó" mi navegador. Ahora , si está interesado, un enlace a este diagrama. Se puede presentar en forma 3D.

Aquí hay una visualización hecha usando npm.broofa.com .


Un fragmento de una visualización de dependencia realizada con npm.broofa.com

Esto es similar a un diagrama de flujo. Ella, para Gatsby.js, resulta ser muy complicada. Puedes echarle un vistazo aquí . Elementos del circuito pueden ser de color basado en estimaciones de npms.io . Puedes subir tu propio archivo al sitiopackage.json.

La herramienta Package Phobia le permite saber cuánto espacio necesita antes de instalar un paquete.


Información del paquete recibida utilizando la fobia del paquete

Aquí puede averiguar sobre el tamaño del paquete publicado y cuánto espacio en disco ocupará después de la instalación.

En pocas palabras: con un gran poder viene una gran responsabilidad


Al final, quiero decir que JavaScript y NPM son excelentes herramientas. Lo bueno es que los desarrolladores modernos tienen la oportunidad de usar un gran conjunto de dependencias. Ejecutar el comando npm installpara evitar escribir un par de líneas de código es tan fácil que a veces nos olvidamos de las consecuencias.

Ahora que ha leído hasta este punto, debe tener una comprensión más completa de las características de la estructura de árbol de dependencia npm-project. Si agrega una biblioteca al proyecto que es muy grande, o si simplemente explora las dependencias de su proyecto, siempre puede aprovechar lo que hemos discutido aquí y analizar las dependencias.

¡Queridos lectores! ¿Desea utilizar la menor cantidad de dependencias posible en sus proyectos npm?


All Articles