Pourquoi utiliser les dépendances entre pairs dans npm pour les plugins?

217

Pourquoi, par exemple, un plugin Grunt définit-il sa dépendance vis-à-vis de grunt comme des " dépendances entre pairs "?

Pourquoi le plugin ne peut-il pas simplement avoir Grunt comme sa propre dépendance dans grunt-plug / node_modules ?

Les dépendances entre pairs sont décrites ici: https://nodejs.org/en/blog/npm/peer-dependencies/

Mais je ne comprends pas vraiment.

Exemple

Je travaille actuellement avec AppGyver Steroids qui utilise des tâches Grunt pour créer mes fichiers sources dans un dossier / dist / à servir sur un appareil local. Je suis tout à fait nouveau à npm et grognement, donc je veux bien comprendre ce qui se passe.

Jusqu'à présent, je comprends ceci:

[rootfolder] /package.json indique à npm que cela dépend du grunt-steroidspackage npm pour le développement:

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

D'accord. L'exécution de npm install dans [rootfolder] détecte la dépendance et installe grunt-steroids dans [rootfolder] / node_modules / grunt-steroids .

Npm lit ensuite [rootfolder] /node_modules/grunt-steroids/package.json pour pouvoir installer ses grunt-steroidspropres dépendances .:

"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"
  },

Les packages " dépendances " sont installés dans [rootfolder] / node_modules / grunt-steroids / node_modules, ce qui est logique pour moi.

Les " devDependencies " ne sont pas installés, ce qui, j'en suis sûr, est contrôlé par la détection de npm que j'essaie simplement d'utiliser grunt-steroids, et que je ne développe pas dessus.

Mais alors nous avons les " pairsDépendances ".

Ceux-ci sont installés dans [rootfolder] / node_modules , et je ne comprends pas pourquoi là-bas et pas dans [rootfolder] / node_modules / grunt-steroids / node_modules afin d' éviter les conflits avec d'autres plugins grunt (ou autre)?

Thomas Stock
la source

Réponses:

420

TL; DR: [1] peerDependencies sont pour les dépendances qui sont exposées (et devraient être utilisées par) le code consommateur, par opposition aux dépendances "privées" qui ne sont pas exposées, et ne sont qu'un détail d'implémentation.

Le problème des dépendances entre pairs résout

Le système de modules de NPM est hiérarchique. Un gros avantage pour les scénarios plus simples est que lorsque vous installez un package npm, ce package apporte ses propres dépendances pour qu'il fonctionne dès la sortie de la boîte.

Mais des problèmes surviennent lorsque:

  • Votre projet et certains modules que vous utilisez dépendent d'un autre module.
  • Les trois modules doivent se parler.

Par exemple

Disons que vous construisez YourCoolProjectet que vous utilisez les deux JacksModule 1.0et JillsModule 2.0. Et supposons que cela JacksModuledépend aussi JillsModule, mais d'une version différente, disons 1.0. Tant que ces 2 versions ne se rencontrent pas, il n'y a pas de problème. Le fait d' JacksModuleutiliser JillsModulesous la surface n'est qu'un détail de mise en œuvre. Nous regroupons JillsModuledeux fois, mais c'est un petit prix à payer lorsque nous obtenons un logiciel stable prêt à l'emploi.

Mais maintenant, si JacksModuleexpose sa dépendance d' JillsModuleune manière ou d'une autre. Il accepte une instance de JillsClasspar exemple ... Que se passe-t-il lorsque nous créons une new JillsClassversion utilisatrice 2.0de la bibliothèque et la transmettons à jacksFunction? Tout l'enfer se déchaînera! Des choses simples comme jillsObject instanceof JillsClassreviendront soudainement falsecar jillsObjectc'est en fait une instance d' une autre JillsClass , la 2.0version.

Comment les dépendances entre pairs résolvent cela

Ils disent à npm

J'ai besoin de ce paquet, mais j'ai besoin de la version qui fait partie du projet, pas d'une version privée de mon module.

Lorsque npm voit que votre package est en cours d'installation dans un projet qui n'a pas cette dépendance ou qui en a une version incompatible , il avertit l'utilisateur pendant le processus d'installation.

Quand devez-vous utiliser les dépendances entre pairs?

  • Lorsque vous construisez une bibliothèque à utiliser par d'autres projets, et
  • Cette bibliothèque utilise une autre bibliothèque, et
  • Vous attendez / avez besoin que l'utilisateur travaille également avec cette autre bibliothèque

Les scénarios courants sont des plugins pour des cadres plus grands. Pensez à des choses comme Gulp, Grunt, Babel, Mocha, etc. Si vous écrivez un plugin Gulp, vous voulez que ce plugin fonctionne avec le même Gulp que le projet de l'utilisateur utilise, pas avec votre propre version privée de Gulp.


Annotations

  1. Trop long; n'a pas lu. Utilisé pour indiquer un court résumé d'un texte que l'on a jugé trop long.
Stijn de Witt
la source
2
Une chose importante que j'ai remarquée et qui n'est mentionnée nulle part, lorsque nous construisons un plugin, devrions-nous avoir un doublon de dépendances de package, pour les dépendances entre pairs? Dans l'exemple OP, nous pouvons voir que "grunt": "0.4.4"c'est à la fois dans devDependencies et peerDependencies, et cela a du sens pour moi d'avoir un doublon là, car cela signifie à la fois que j'ai besoin de ce gruntpackage pour mon propre usage, mais aussi que les utilisateurs de mon La bibliothèque peut utiliser sa propre version, tant qu'elle respecte le verrou de version peerDependencies. Est-ce exact? Ou l'exemple OP est-il très mauvais?
Vadorequest
4
Je peux imaginer que les gens qui créent un plugin Grunt soient des fans de Grunt :) En tant que tel, il semble naturel pour eux d'utiliser Grunt eux-mêmes pour le processus de construction de leur plugin .... Mais pourquoi voudraient-ils verrouiller la gamme de versions Grunt que leur plugin fonctionne avec le processus de construction qu'ils utilisent pour le créer? L'ajouter en tant que dépendance de développement leur permet de le dissocier. Fondamentalement, il y a 2 phases: le temps de construction et le temps d'exécution. Les dépendances de développement sont nécessaires pendant la construction. Des dépendances régulières et homologues sont nécessaires lors de l'exécution. Bien sûr, avec les dépendances des dépendances, tout devient vite déroutant :)
Stijn de Witt
1
Merci pour cette réponse! Juste pour clarifier, dans votre exemple, si cela JacksModuledépend JillsModule ^1.0.0d' JillsModuleêtre une dépendance de pair JacksModuleet que vous YourCoolProjectutilisiez JacksModuleet JillsModule ^2.0.0, nous obtiendrons l'avertissement de dépendance de pair par NPM, qui nous conseillera d'installer JillsModule ^1.0.0également. Mais que se passe-t-il alors? YourCoolProjectaura désormais deux versions de JillsModuleimportable via import jillsModule from "..."? Et comment puis-je me souvenir que lorsque j'utilise, JacksModuleje dois lui transmettre une instance de JillsModule v1.0.0?
tonix
1
@tonix Eh bien, ce sera en effet un problème que vous ayez une incompatibilité de version. peerDependencies ne résout pas cela. Mais cela aide à rendre le problème explicite. Parce qu'il montrera clairement la différence de version au lieu d'utiliser deux versions en silence. Le développeur de l'application qui sélectionne les bibliothèques devra trouver une solution.
Stijn de Witt
2
@tonix Ou la troisième option: cloner le JacksModuleréférentiel, le mettre à jour en fonction JillsModule ^2.0.0et proposer un PR au responsable du projet. Il peut être utile de soumettre d'abord un bogue indiquant que cette dépendance est obsolète et que vous souhaitez aider à la mettre à jour. Si vous faites un bon PR, la plupart des mainteneurs de bibliothèque le fusionneront et vous en remercient. Si les responsables ne répondent pas, vous pouvez publier votre fork dans NPM namespaced sous votre nom et utiliser votre fork à la place. De toute façon, il existe des solutions mais peerDependenciesne les résout pas de lui-même.
Stijn de Witt
26

Je vous recommanderais de relire l'article en premier. C'est un peu déroutant mais l'exemple avec winston-mail vous montre la réponse pourquoi:

Par exemple, supposons que cela soit [email protected]spécifié "winston": "0.5.x"dans son "dependencies"objet car c'est la dernière version avec laquelle il a été testé. En tant que développeur d'applications, vous voulez les dernières et meilleures choses, vous devez donc rechercher les dernières versions de winstonet de winston-mailet les mettre dans votre package.json en tant que

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

Mais maintenant, l'exécution de l'installation de npm entraîne le graphique de dépendance inattendu de

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

Dans ce cas, il est possible d'avoir plusieurs versions d'un package, ce qui entraînerait certains problèmes. Les dépendances entre pairs permettent aux développeurs npm de s'assurer que l'utilisateur dispose du module spécifique (dans le dossier racine). Mais vous avez raison de dire que la description d'une version spécifique d'un package entraînerait des problèmes avec d'autres packages utilisant d'autres versions. Ce problème est lié aux développeurs npm, comme l'indiquent les articles

Un conseil : les exigences de dépendance des pairs, contrairement à celles des dépendances régulières, doivent être indulgentes . Vous ne devez pas verrouiller vos dépendances entre pairs vers des versions de correctifs spécifiques.

Par conséquent, les développeurs doivent suivre semver pour définir les dépendances entre pairs. Vous devriez ouvrir un problème pour le paquet grunt-steroids sur GitHub ...

Fer à
la source
1
Vous dites cela, multiple versions of a package which would cause some issuesmais n'est-ce pas tout l'intérêt d'un gestionnaire de paquets? Ils en discutent même plus loin dans le même article où il y a 2 versions du même package dans le projet: une fournie par le développeur et une fournie par une bibliothèque tierce.
Adam Beck
1
Je pense que je comprends le point de la dépendance des pairs, mais dans l' winstonexemple, suis-je maintenant incapable d'utiliser la winston-mailbibliothèque parce que ma version ne correspond pas à la dépendance des pairs? Je préférerais de beaucoup avoir cette mise à niveau temporaire de la plus récente et de la meilleure pour la bibliothèque 1 plutôt que de ne pas pouvoir l'utiliser du tout.
Adam Beck
1
pour votre premier commentaire, pour autant que je le comprenne et l'utilise, cela a à voir avec le test, par exemple si vous avez un package qui a été testé par vous pour un package tiers spécifique, vous ne pouvez pas être sûr que si un de vos dépendances changent (correction de bogue, mise à jour majeure des fonctionnalités) que votre package fonctionnera. Par conséquent, vous pouvez spécifier une version de plugin spécifique et les enregistrer avec vos tests.
Fer To
1
Sur votre deuxième commentaire: c'est pourquoi ils disent dans les documents que les développeurs doivent être indulgents avec leurs dépendances de package et doivent utiliser semver, par exemple au lieu de "0.2.1", "~ 0.2.1" -> autorise "0.2.x" mais pas "0.3.x", ou "> = 0.2.1" -> tout de "0.2.x" à "1.x" ou "x.2". .. (mais pas vraiment préférable pour un package npm irait avec ~
Fer To
15

peerDependencies expliqué avec l'exemple le plus simple possible:

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


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

l'exécution de npm install dans myPackage générera une erreur car il essaie d'installer la version React ^15.0.0ET fooqui n'est compatible qu'avec React ^16.0.0.

Les dépendances entre pairs ne sont PAS installées.

Christopher Tokar
la source
pourquoi ne pas simplement mettre react 16 en dep à l'intérieur de foo? de cette façon, les 15 et 16 seront disponibles et foo peut utiliser 16 et mypackage peut utiliser 15?
nitinsh99
React est un framework qui est amorcé au moment de l'exécution, pour que React 15 et React 16 existent sur la même page, vous auriez besoin que les deux soient boostés simultanément, ce qui serait extrêmement lourd et problématique pour l'utilisateur final. Si foofonctionne à la fois avec React 15 et React 16, il pourrait répertorier sa peerDependency comme >=15 < 17.
Jens Bodal
nitinsh99 ma réponse était d'expliquer le but de peerDependencies avec l'exemple le plus simple possible, pas comment se débarrasser de l'erreur lancée par peerDependencies
Christopher Tokar
@ nitinsh99 l'ajout de la dépendance à l'intérieur du package fournira un problème comme Hooks - Plusieurs réactions dans un package
Masood