Warum Peer-Abhängigkeiten in npm für Plugins verwenden?

217

Warum definiert beispielsweise ein Grunt-Plugin seine Abhängigkeit von Grunt als " Peer-Abhängigkeiten "?

Warum kann das Plugin nicht einfach Grunt als eigene Abhängigkeit in grunt-plug / node_modules haben ?

Peer-Abhängigkeiten werden hier beschrieben: https://nodejs.org/en/blog/npm/peer-dependencies/

Aber ich verstehe es nicht wirklich.

Beispiel

Ich arbeite zurzeit mit AppGyver-Steroiden, die Grunt-Tasks verwenden, um meine Quelldateien in einem Ordner / dist / zu erstellen, der auf einem lokalen Gerät bereitgestellt werden soll. Ich bin ziemlich neu bei npm und grunze, also möchte ich vollständig verstehen, was los ist.

Bisher verstehe ich das:

[rootfolder] /package.json teilt npm mit, dass es grunt-steroidsfür die Entwicklung vom npm-Paket abhängt :

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

In Ordnung. Wenn Sie npm install in [rootfolder] ausführen, wird die Abhängigkeit erkannt und grunt-steroids in [rootfolder] / node_modules / grunt-steroids installiert .

Npm liest dann [rootfolder] /node_modules/grunt-steroids/package.json, damit es grunt-steroidseigene Abhängigkeiten installieren kann :

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

Die " Abhängigkeiten " -Pakete werden in [rootfolder] / node_modules / grunt-steroids / node_modules installiert, was für mich logisch ist.

Die " devDependencies " sind nicht installiert, was sicher durch die Erkennung von npm gesteuert wird, die ich nur zu verwenden versuche grunt-steroidsund nicht darauf entwickle.

Aber dann haben wir die " peerDependencies ".

Diese sind in [rootfolder] / node_modules installiert , und ich verstehe nicht, warum und nicht in [rootfolder] / node_modules / grunt-steroids / node_modules, damit Konflikte mit anderen Grunt-Plugins (oder was auch immer) vermieden werden?

Thomas Stock
quelle

Antworten:

420

TL; DR: [1] peerDependencies gelten für Abhängigkeiten, die dem konsumierenden Code ausgesetzt sind (und von diesem voraussichtlich verwendet werden), im Gegensatz zu "privaten" Abhängigkeiten, die nicht verfügbar sind, und sind nur ein Implementierungsdetail.

Das Problem Peer-Abhängigkeiten lösen

Das Modulsystem von NPM ist hierarchisch. Ein großer Vorteil für einfachere Szenarien besteht darin, dass bei der Installation eines npm-Pakets dieses Paket seine eigenen Abhängigkeiten mit sich bringt, sodass es sofort funktioniert.

Aber Probleme entstehen, wenn:

  • Sowohl Ihr Projekt als auch ein Modul, das Sie verwenden, hängen von einem anderen Modul ab.
  • Die drei Module müssen miteinander sprechen.

Zum Beispiel

Angenommen, Sie bauen YourCoolProjectund verwenden beide JacksModule 1.0und JillsModule 2.0. Nehmen wir an, das JacksModulehängt auch von JillsModuleeiner anderen Version ab, sagen wir 1.0. Solange diese beiden Versionen nicht übereinstimmen, gibt es kein Problem. Die Tatsache , dass JacksModuleunter Verwendung von JillsModuleunter der Oberfläche ist nur eine Implementierung Detail. Wir bündeln JillsModulezweimal, aber das ist ein kleiner Preis, wenn wir stabile Software aus der Box bekommen.

Aber was ist nun, wenn JacksModuleseine Abhängigkeit JillsModulein irgendeiner Weise aufgedeckt wird? Es akzeptiert zum Beispiel eine Instanz von JillsClass... Was passiert, wenn wir eine new JillsClassusing-Version 2.0der Bibliothek erstellen und an diese weitergeben jacksFunction? Die Hölle bricht los! Einfache Dinge wie jillsObject instanceof JillsClasswerden plötzlich zurückkehren, falseweil jillsObjectes sich tatsächlich um eine Instanz einer anderen handelt JillsClass , der 2.0Version.

Wie Peer-Abhängigkeiten dies lösen

Sie erzählen npm

Ich brauche dieses Paket, aber ich brauche die Version, die Teil des Projekts ist, nicht irgendeine Version, die für mein Modul privat ist.

Wenn npm feststellt, dass Ihr Paket in einem Projekt installiert wird, das diese Abhängigkeit nicht aufweist oder dessen Version nicht kompatibel ist, wird der Benutzer während des Installationsvorgangs gewarnt.

Wann sollten Sie Peer-Abhängigkeiten verwenden?

  • Wenn Sie eine Bibliothek erstellen, die von anderen Projekten verwendet werden soll, und
  • Diese Bibliothek verwendet eine andere Bibliothek und
  • Sie erwarten / müssen, dass der Benutzer auch mit dieser anderen Bibliothek arbeitet

Häufige Szenarien sind Plugins für größere Frameworks. Denken Sie an Dinge wie Gulp, Grunt, Babel, Mocha usw. Wenn Sie ein Gulp-Plugin schreiben, möchten Sie, dass dieses Plugin mit demselben Gulp funktioniert, den das Projekt des Benutzers verwendet, und nicht mit Ihrer eigenen privaten Version von Gulp.


Anmerkungen

  1. Zu lang; habe nicht gelesen. Wird verwendet, um eine kurze Zusammenfassung für einen Text anzugeben, den man für zu lang hält.
Stijn de Witt
quelle
2
Eine wichtige Sache, die mir aufgefallen ist und die beim Erstellen eines Plugins nirgendwo gesagt wird, sollten wir ein Duplikat von Paketabhängigkeiten für die Peer-Abhängigkeiten haben? Im OP-Beispiel können wir sehen, dass dies "grunt": "0.4.4"sowohl in devDependencies als auch in peerDependencies der Fall ist, und es ist für mich sinnvoll, dort ein Duplikat zu haben, da dies sowohl bedeutet, dass ich dieses gruntPaket für meinen eigenen Gebrauch benötige , als auch, dass die Benutzer von my Die Bibliothek kann ihre eigene Version verwenden, sofern die Versionssperre für peerDependencies eingehalten wird. Ist das korrekt? Oder ist das OP-Beispiel sehr schlecht?
Vadorequest
4
Ich kann mir vorstellen, dass Leute, die ein Grunt-Plugin erstellen, Fans von Grunt sind :) Als solches scheint es für sie selbstverständlich, Grunt selbst für den Erstellungsprozess ihres Plugins zu verwenden ... Aber warum sollten sie den Grunt-Versionsbereich sperren wollen, damit ihr Plugin funktioniert? mit dem Build-Prozess, den sie verwenden, um es zu erstellen? Durch Hinzufügen als Entwicklungsabhängigkeit können sie dies entkoppeln. Grundsätzlich gibt es zwei Phasen: Erstellungszeit und Laufzeit. Entwicklungsabhängigkeiten werden während der Erstellungszeit benötigt. Zur Laufzeit sind regelmäßige und Peer-Abhängigkeiten erforderlich. Natürlich wird mit Abhängigkeiten von Abhängigkeiten alles schnell verwirrend :)
Stijn de Witt
1
Vielen Dank für diese Antwort! Nur um zu klären, in Ihrem Beispiel, wenn JacksModuledavon abhängig , JillsModule ^1.0.0mit JillsModuleeiner Peer - Abhängigkeit des Sein JacksModuleund YourCoolProjectwurden mit JacksModuleund JillsModule ^2.0.0wir werden die Peer - Abhängigkeit Warnung von NPM erhalten, die uns beraten installieren JillsModule ^1.0.0als auch. Aber was passiert dann? YourCoolProjectwird jetzt zwei Versionen von JillsModuleimportierbar haben import jillsModule from "..."? Und wie erinnere ich mich daran, dass ich bei der Verwendung JacksModuleeine Instanz von übergeben muss JillsModule v1.0.0?
Tonix
1
@tonix Nun, es wird in der Tat ein Problem sein, dass Sie eine Versionsinkompatibilität haben. peerDependencies löst das nicht. Es hilft jedoch, das Problem deutlich zu machen. Weil die Versionsinkongruenz deutlich angezeigt wird, anstatt zwei Versionen stillschweigend zu verwenden. Der App-Entwickler, der die Bibliotheken auswählt, muss eine Lösung finden.
Stijn de Witt
2
@tonix Oder die dritte Option: Klonen Sie das JacksModuleRepo, aktualisieren Sie es, um davon abhängig zu sein, JillsModule ^2.0.0und bieten Sie dem Projektbetreuer eine PR an. Es kann hilfreich sein, zuerst einen Fehler zu melden, der besagt, dass diese Abhängigkeit veraltet ist, und Sie möchten helfen, sie zu aktualisieren. Wenn Sie eine gute PR machen, werden die meisten Bibliotheksbetreuer sie zusammenführen und sich dafür bedanken. Wenn die Betreuer nicht reagieren, können Sie Ihre Abzweigung in NPM-Namespaces unter Ihrem Namen veröffentlichen und stattdessen Ihre Abzweigung verwenden. In jedem Fall gibt es Lösungen, die peerDependenciesjedoch nicht von alleine gelöst werden.
Stijn de Witt
26

Ich würde Ihnen empfehlen, den Artikel zuerst noch einmal zu lesen. Es ist ein bisschen verwirrend, aber das Beispiel mit Winston-Mail zeigt Ihnen die Antwort, warum:

Zum Beispiel, lassen Sie sich so tun, als [email protected]angegeben "winston": "0.5.x"in seinem "dependencies"Objekt , weil das die neueste Version ist wurde es gegen getestet. Als App-Entwickler möchten Sie die neuesten und besten Inhalte, also suchen Sie nach den neuesten Versionen von winstonund von winston-mailund fügen Sie sie in Ihre package.json als ein

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

Wenn Sie nun npm install ausführen, wird das unerwartete Abhängigkeitsdiagramm von angezeigt

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

In diesem Fall ist es möglich, mehrere Versionen eines Pakets zu haben, was einige Probleme verursachen würde. Mit Peer-Abhängigkeiten können npm-Entwickler sicherstellen, dass der Benutzer über das spezifische Modul verfügt (im Stammordner). Sie haben jedoch Recht mit dem Punkt, dass die Beschreibung einer bestimmten Version eines Pakets zu Problemen mit anderen Paketen führen würde, die andere Versionen verwenden. Dieses Problem hat mit npm-Entwicklern zu tun, wie in den Artikeln angegeben

Ein Ratschlag : Die Anforderungen an Peer-Abhängigkeiten sollten im Gegensatz zu denen für reguläre Abhängigkeiten mild sein . Sie sollten Ihre Peer-Abhängigkeiten nicht auf bestimmte Patch-Versionen beschränken.

Daher sollten Entwickler Semver folgen, um PeerDependencies zu definieren. Sie sollten ein Problem für das Grunt-Steroids-Paket auf GitHub öffnen ...

Fer To
quelle
1
Sie sagen das, multiple versions of a package which would cause some issuesaber ist das nicht der springende Punkt eines Paketmanagers? Sie diskutieren dies sogar weiter oben in demselben Artikel, in dem das Projekt zwei Versionen desselben Pakets enthält: eine vom Entwickler und eine von einer Bibliothek eines Drittanbieters.
Adam Beck
1
Ich glaube, ich verstehe den Punkt der Peer-Abhängigkeit, aber im winstonBeispiel kann ich die winston-mailBibliothek jetzt einfach nicht verwenden, weil meine Version nicht mit der Peer-Abhängigkeit übereinstimmt. Ich hätte dieses vorübergehende Downgrade von der neuesten und besten für die 1-Bibliothek viel lieber, als es überhaupt nicht verwenden zu können.
Adam Beck
1
Soweit ich es verstehe und verwende, hat Ihr erster Kommentar mit Testen zu tun. Wenn Sie beispielsweise ein Paket haben, das von Ihnen für ein bestimmtes Paket eines Drittanbieters getestet wurde, können Sie nicht sicher sein, ob eines vorhanden ist Wenn sich Ihre Abhängigkeiten ändern (Fehlerbehebung, Aktualisierung der wichtigsten Funktionen), funktioniert Ihr Paket. Daher können Sie eine bestimmte Plugin-Version angeben und werden mit Ihren Tests gespeichert.
Fer bis
1
Zu Ihrem zweiten Kommentar: Deshalb sagen sie in den Dokumenten, dass Entwickler mit ihren Paketabhängigkeiten nachsichtig sein und Semver verwenden sollten, z. B. anstelle von "0.2.1", "~ 0.2.1" -> erlaubt aber "0.2.x" nicht "0.3.x" oder "> = 0.2.1" -> alles von "0.2.x" bis "1.x" oder "x.2". .. (aber nicht wirklich vorzuziehen für ein npm-Paket würde mit ~
Fer To gehen
15

peerDependencies erklärt mit dem einfachsten möglichen Beispiel:

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


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

Wenn Sie npm install in myPackage ausführen, wird ein Fehler ausgegeben, da versucht wird, die React-Version ^15.0.0UND zu installieren, foodie nur mit React kompatibel ist ^16.0.0.

peerDependencies werden NICHT installiert.

Christopher Tokar
quelle
warum nicht einfach reagieren 16 als dep in foo setzen? Auf diese Weise sind sowohl 15 als auch 16 verfügbar und foo kann 16 und mypackage 15 verwenden.
Nitinsh99
React ist ein Framework, das zur Laufzeit gebootet wird. Damit sowohl React 15 als auch React 16 auf derselben Seite vorhanden sind, müssen beide gleichzeitig verstärkt werden, was für den Endbenutzer extrem schwer und problematisch wäre. Wenn fooArbeiten sowohl mit 15 Reagieren und Reagieren 16 dann könnte seine peerDependency Liste als >=15 < 17.
Jens Bodal
nitinsh99 meine antwort war, den zweck von peerDependencies mit dem einfachsten beispiel zu erklären, nicht wie man den durch peerDependencies ausgelösten fehler beseitigt
Christopher Tokar
@ nitinsh99 Das Hinzufügen einer Reaktion innerhalb der Paketabhängigkeit führt zu Problemen wie Hooks - Mehrere Reaktionen in einem Paket
Masood