¿Por qué usar dependencias entre pares en npm para complementos?


217

¿Por qué, por ejemplo, un complemento de Grunt define su dependencia de Grunt como " dependencias entre pares "?

¿Por qué el complemento no puede tener Grunt como su propia dependencia en grunt-plug / node_modules ?

Las dependencias entre pares se describen aquí: https://nodejs.org/en/blog/npm/peer-dependencies/

Pero realmente no lo entiendo.

Ejemplo

Estoy trabajando con AppGyver Steroids en este momento, que utiliza tareas de Grunt para construir mis archivos de origen en una carpeta / dist / para que se sirvan en un dispositivo local. Soy bastante nuevo en npm y gruñido, así que quiero comprender completamente lo que está sucediendo.

Hasta ahora entiendo esto:

[rootfolder] /package.json le dice a npm que depende del grunt-steroidspaquete npm para el desarrollo:

  "devDependencies": {
    "grunt-steroids": "0.x"
  },

Bueno. La ejecución de npm install en [rootfolder] detecta la dependencia e instala grunt-steroids en [rootfolder] / node_modules / grunt-steroids .

Npm luego lee [rootfolder] /node_modules/grunt-steroids/package.json para que pueda instalar sus grunt-steroidspropias dependencias .:

"devDependencies": {
    "grunt-contrib-nodeunit": "0.3.0",
    "grunt": "0.4.4"
  },
"dependencies": {
    "wrench": "1.5.4",
    "chalk": "0.3.0",
    "xml2js": "0.4.1",
    "lodash": "2.4.1"
  },
"peerDependencies": {
    "grunt": "0.4.4",
    "grunt-contrib-copy": "0.5.0",
    "grunt-contrib-clean": "0.5.0",
    "grunt-contrib-concat": "0.4.0",
    "grunt-contrib-coffee": "0.10.1",
    "grunt-contrib-sass": "0.7.3",
    "grunt-extend-config": "0.9.2"
  },

Los paquetes de " dependencias " se instalan en [rootfolder] / node_modules / grunt-steroids / node_modules, lo cual es lógico para mí.

Las " devDependencies " no están instaladas, lo que estoy seguro es controlado por npm detectando que solo estoy tratando de usar grunt-steroids, y no desarrollarlo.

Pero luego tenemos las " dependencias de pares ".

Estos se instalan en [rootfolder] / node_modules , y no entiendo por qué allí y no en [rootfolder] / node_modules / grunt-steroids / node_modules para evitar conflictos con otros plugins grunt (o lo que sea).

Respuestas:


420

TL; DR: [1] peerDependencies son para dependencias que están expuestas (y se espera que sean utilizadas por) el código consumidor, a diferencia de las dependencias "privadas" que no están expuestas, y son solo un detalle de implementación.

El problema que resuelven las dependencias entre pares

El sistema de módulos de NPM es jerárquico. Una gran ventaja para escenarios más simples es que cuando instala un paquete npm, ese paquete trae consigo sus propias dependencias para que funcione de manera inmediata.

Pero surgen problemas cuando:

  • Tanto su proyecto como algún módulo que esté utilizando dependen de otro módulo.
  • Los tres módulos tienen que hablar entre ellos.

Por ejemplo

Digamos que estás construyendo YourCoolProjecty estás usando ambos JacksModule 1.0y JillsModule 2.0. Y supongamos que eso JacksModuletambién depende JillsModule, pero en una versión diferente, digamos 1.0. Mientras esas 2 versiones no cumplan, no hay problema. El hecho de que JacksModulese use JillsModuledebajo de la superficie es solo un detalle de implementación. Estamos agrupando JillsModuledos veces, pero ese es un pequeño precio a pagar cuando sacamos el software estable de la caja.

Pero ahora, ¿qué JacksModulepasa si expone su dependencia de JillsModulealguna manera? Acepta una instancia de, JillsClasspor ejemplo ... ¿Qué sucede cuando creamos una new JillsClassversión 2.0de uso de la biblioteca y se la pasamos jacksFunction? ¡Todo el infierno se desatará! Cosas simples como jillsObject instanceof JillsClassregresarán repentinamente falseporque en jillsObjectrealidad es una instancia de otra JillsClass , la 2.0versión.

Cómo las dependencias de pares resuelven esto

Dicen npm

Necesito este paquete, pero necesito la versión que forma parte del proyecto, no alguna versión privada de mi módulo.

Cuando npm ve que su paquete se está instalando en un proyecto que no tiene esa dependencia, o que tiene una versión incompatible , advertirá al usuario durante el proceso de instalación.

¿Cuándo deberías usar las dependencias entre pares?

  • Cuando está construyendo una biblioteca para ser utilizada por otros proyectos, y
  • Esta biblioteca está utilizando alguna otra biblioteca y
  • Usted espera / necesita que el usuario trabaje con esa otra biblioteca también

Los escenarios comunes son complementos para marcos más grandes. Piense en cosas como Gulp, Grunt, Babel, Mocha, etc. Si escribe un complemento Gulp, desea que ese complemento funcione con el mismo Gulp que está utilizando el proyecto del usuario, no con su propia versión privada de Gulp.


Anotaciones

  1. Demasiado largo; No leí. Se usa para indicar un breve resumen de un texto que se ha considerado demasiado largo.

2
Una cosa importante que noté y que no se dice en ninguna parte, cuando estamos construyendo un complemento, ¿deberíamos tener un duplicado de dependencias de paquetes, para las dependencias de pares? En el ejemplo de OP, podemos ver que "grunt": "0.4.4"es tanto en devDependencies como en peerDependencies, y tiene sentido para mí tener un duplicado allí, porque significa que necesito ese gruntpaquete para mi propio uso, pero también que los usuarios de mi la biblioteca puede usar su propia versión, siempre que respete el bloqueo de versión de peerDependencies ¿Es eso correcto? ¿O el ejemplo de OP es muy malo?
Vadorequest

44
Me imagino que las personas que crean un complemento de Grunt son fanáticos de Grunt :) Como tal, parece natural que utilicen Grunt para el proceso de compilación de su complemento ... Pero, ¿por qué querrían bloquear el rango de versiones de Grunt? con el proceso de construcción que utilizan para crearlo? Agregarlo como una dependencia de desarrollo les permite desacoplar esto. Básicamente hay 2 fases: tiempo de construcción y tiempo de ejecución. Se necesitan dependencias de desarrollo durante el tiempo de compilación. Se necesitan dependencias regulares y similares en tiempo de ejecución. Por supuesto, con dependencias de dependencias todo se vuelve confuso rápidamente :)
Stijn de Witt

1
Gracias por esta respuesta! Solo para aclarar, en su ejemplo, si JacksModuledepende de JillsModule ^1.0.0con JillsModuleser una dependencia de pares JacksModuley YourCoolProjectestaban usando JacksModuley JillsModule ^2.0.0, obtendremos la advertencia de dependencia entre pares por la NGP, que nos asesorarán para instalar JillsModule ^1.0.0también. ¿Pero qué pasa entonces? YourCoolProjectahora tendrá dos versiones de JillsModuleimportable a través import jillsModule from "..."? ¿Y cómo recuerdo que cuando uso JacksModulenecesito pasarle una instancia JillsModule v1.0.0?
tonix

1
@tonix Bueno, de hecho será un problema que tengas una incompatibilidad de versión. peerDependencies no resuelve eso. Pero ayuda a hacer explícito el problema. Porque mostrará claramente que la versión no coincide en lugar de usar dos versiones en silencio. El desarrollador de la aplicación que está seleccionando las bibliotecas tendrá que encontrar una solución.
Stijn de Witt

2
@tonix O la tercera opción: clonar el JacksModulerepositorio, actualizarlo para que dependa JillsModule ^2.0.0y ofrecer un RP al responsable del proyecto. Puede ser útil enviar un error primero diciendo que esta dependencia está desactualizada y le gustaría ayudar a actualizarla. Si haces un buen PR, la mayoría de los mantenedores de la biblioteca lo fusionarán y te lo agradecerán. Si los mantenedores no responden, puede publicar su bifurcación en los espacios de nombres NPM debajo de su nombre y usar su bifurcación en su lugar. De cualquier manera, hay soluciones pero peerDependenciesno lo resuelve por sí solo.
Stijn de Witt

26

Le recomendaría que lea el artículo nuevamente primero. Es un poco confuso, pero el ejemplo con winston-mail le muestra la respuesta por qué:

Por ejemplo, supongamos que se winston-mail@0.2.3especifica "winston": "0.5.x"en su "dependencies"objeto porque esa es la última versión con la que se probó. Como desarrollador de aplicaciones, quieres lo último y lo mejor, así que buscas las últimas versiones de winstony de winston-maily las colocas en tu package.json como

{
  "dependencies": {  
    "winston": "0.6.2",  
    "winston-mail": "0.2.3"  
  }  
}

Pero ahora, ejecutar npm install da como resultado el gráfico de dependencia inesperado de

├── winston@0.6.2  
└─┬ winston-mail@0.2.3                
  └── winston@0.5.11

En este caso, es posible tener múltiples versiones de un paquete que causarían algunos problemas. Las dependencias entre pares permiten a los desarrolladores de npm asegurarse de que el usuario tenga el módulo específico (en la carpeta raíz). Pero tiene razón con el punto de que describir una versión específica de un paquete generaría problemas con otros paquetes que usan otras versiones. Este problema tiene que ver con los desarrolladores de npm, como dicen los artículos

Un consejo : los requisitos de dependencia entre pares, a diferencia de los de las dependencias regulares, deben ser indulgentes . No debe bloquear sus dependencias pares a versiones de parche específicas.

Por lo tanto, los desarrolladores deben seguir semver para definir las peerDependencies. Debería abrir un problema para el paquete de esteroides grunt en GitHub ...


1
Dices eso, multiple versions of a package which would cause some issuespero ¿no es ese el objetivo de un administrador de paquetes? Incluso discuten esto más adelante en el mismo artículo donde hay 2 versiones del mismo paquete en el proyecto: una proporcionada por el desarrollador y otra por una biblioteca de terceros.
Adam Beck

1
Creo que entiendo el punto de la dependencia de pares, pero en el winstonejemplo, ¿ahora no puedo usar la winston-mailbiblioteca porque mi versión no coincide con la dependencia de pares? Preferiría tener esa degradación temporal de la última y mejor para la biblioteca 1 que no poder usarla en absoluto.
Adam Beck

1
para su primer comentario, hasta donde yo entiendo y lo uso, tiene que ver con las pruebas, por ejemplo, si tiene un paquete que ha sido probado por un paquete específico de terceros, no puede estar seguro de que si uno de sus dependencias cambian (corrección de errores, actualización de características principales) de que su paquete funcionará. Por lo tanto, puede especificar una versión específica del complemento y guardar con sus pruebas.
Fer To

1
En su segundo comentario: por eso dicen en los documentos que los desarrolladores deben ser indulgentes con sus dependencias de paquetes y deben usar semver, por ejemplo, en lugar de "0.2.1", "~ 0.2.1" -> permite "0.2.x" pero no "0.3.x" o "> = 0.2.1" -> todo, desde "0.2.x" a "1.x" o "x.2.". .. (pero no es realmente preferible para un paquete npm iría con ~
Fer To

15

peerDependencies explicado con el ejemplo más simple posible:

{
  "name": "myPackage",
  "dependencies": {
    "foo": "^4.0.0",
    "react": "^15.0.0"
  }
}


{
  "name": "foo"
  "peerDependencies": {
    "react": "^16.0.0"
  }
}

ejecutar npm install en myPackage arrojará un error porque está intentando instalar la versión React ^15.0.0Y fooque solo es compatible con React ^16.0.0.

Las dependencias peer NO están instaladas.


¿por qué no simplemente poner reaccionar 16 como dep dentro de foo? de esa manera tanto 15 como 16 estarán disponibles y foo puede usar 16 y mypackage puede usar 15?
nitinsh99

React es un marco que se inicia en tiempo de ejecución, para que React 15 y React 16 existan en la misma página, necesitaría que ambos se vuelvan a acoplar simultáneamente, lo que sería extremadamente pesado y problemático para el usuario final. Si foofunciona con React 15 y React 16, entonces podría enumerar su Dependencia entre pares como >=15 < 17.
Jens Bodal

nitinsh99 mi respuesta fue explicar el propósito de las dependencias de pares con el ejemplo más simple posible, no cómo deshacerme del error arrojado por las dependencias de pares
Christopher Tokar

@ nitinsh99 agregando reaccionar dentro de la dependencia del paquete proporcionará un problema como Hooks - Múltiples reacciones en un paquete
Masood
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.