Зачем использовать зависимости пиров в npm для плагинов?

218

Почему, например, плагин Grunt определяет свою зависимость от grunt как « равноправные зависимости »?

Почему плагин не может просто иметь Grunt как свою собственную зависимость в grunt-plug / node_modules ?

Зависимости пира описаны здесь: https://nodejs.org/en/blog/npm/peer-dependencies/

Но я не совсем понимаю.

пример

Сейчас я работаю со стероидами AppGyver, которые используют задачи Grunt для создания моих исходных файлов в папке / dist / для обслуживания на локальном устройстве. Я совершенно новый в npm и хрюкаю, поэтому я хочу полностью понять, что происходит.

Пока я получаю это:

[rootfolder] /package.json сообщает npm, что это зависит от grunt-steroidsпакета npm для разработки:

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

Ладно. Запуск npm install в [rootfolder] обнаруживает зависимость и устанавливает grunt-стероиды в [rootfolder] / node_modules / grunt-steroids .

Затем Npm читает [rootfolder] /node_modules/grunt-steroids/package.json, чтобы установить grunt-steroidsсобственные зависимости.

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

Пакеты " зависимостей " устанавливаются в [rootfolder] / node_modules / grunt-steroids / node_modules, что логично для меня.

« DevDependencies » не установлены, что, я уверен, контролируется npm, определяющим, что я просто пытаюсь использовать grunt-steroids, а не развиваться на нем.

Но тогда у нас есть " peerDependencies ".

Они установлены в [rootfolder] / node_modules , и я не понимаю, почему там, а не в [rootfolder] / node_modules / grunt-steroids / node_modules, чтобы избежать конфликтов с другими подключаемыми плагинами (или чем-то еще)?

Томас Сток
источник

Ответы:

421

TL; DR: peerDependencies для зависимостей, которые предоставляются (и предполагается, что будут использоваться) потребляющим кодом, в отличие от «частных» зависимостей, которые не предоставляются и являются только деталями реализации.

Проблема взаимозависимости решает

Система модулей NPM является иерархической. Одним из больших преимуществ для более простых сценариев является то, что при установке пакета npm этот пакет приносит с собой свои собственные зависимости, поэтому он будет работать «из коробки».

Но проблемы возникают, когда:

  • Ваш проект и используемый вами модуль зависят от другого модуля.
  • Три модуля должны общаться друг с другом.

В примере

Допустим, вы строите, YourCoolProjectи вы используете оба JacksModule 1.0и JillsModule 2.0. И давайте предположим, что это JacksModuleтакже зависит JillsModule, но, скажем, от другой версии 1.0. Пока эти 2 версии не встречаются, проблем нет. Тот факт, что JacksModuleиспользуется JillsModuleпод поверхностью, это просто деталь реализации. Мы объединяем JillsModuleдва пакета , но это небольшая цена, когда мы получаем стабильное программное обеспечение из коробки.

Но что теперь, если он каким-то образом JacksModuleподвергает свою зависимость JillsModule. Например, он принимает экземпляр JillsClass... Что произойдет, когда мы создадим new JillsClassверсию 2.0библиотеки с использованием и передадим ее jacksFunction? Весь ад вырвется на свободу! Простые вещи вроде jillsObject instanceof JillsClassвнезапно вернутся, falseпотому что jillsObjectна самом деле это экземпляр другой JillsClass , 2.0версии.

Как это делают коллегиальные зависимости?

Они говорят нпм

Мне нужен этот пакет, но мне нужна версия, которая является частью проекта, а не какая-то версия, приватная для моего модуля.

Когда npm обнаружит, что ваш пакет устанавливается в проект, который не имеет этой зависимости или имеет несовместимую версию , он предупредит пользователя во время процесса установки.

Когда вы должны использовать зависимости от сверстников?

  • Когда вы создаете библиотеку для использования другими проектами, и
  • Эта библиотека использует какую-то другую библиотеку, и
  • Вы ожидаете / нуждаетесь в работе пользователя с этой другой библиотекой

Распространенные сценарии - это плагины для больших фреймворков. Подумайте о таких вещах, как Gulp, Grunt, Babel, Mocha и т. Д. Если вы пишете плагин Gulp, вы хотите, чтобы этот плагин работал с тем же Gulp, который используется проектом пользователя, а не с вашей собственной частной версией Gulp.

Стейн де Витт
источник
2
Одна важная вещь, которую я заметил и нигде не сказано, когда мы создаем плагин, должен ли у нас быть дубликат зависимостей пакетов для одноранговых зависимостей? В примере с OP мы видим, что "grunt": "0.4.4"это как в devDependencies, так и в peerDependencies, и для меня имеет смысл иметь там дубликат, потому что это означает, что мне нужен и этот gruntпакет для моего собственного использования, а также то, что пользователи моего библиотека может использовать свою собственную версию, если она учитывает блокировку версии peerDependencies. Это правильно? Или пример ОП очень плохой?
Vadorequest
4
Я могу себе представить, что люди, создающие плагин Grunt, являются поклонниками Grunt :) Поэтому для них вполне естественно использовать Grunt для процесса сборки своего плагина .... Но почему они хотят заблокировать диапазон версий Grunt для своих плагинов? с процессом сборки, который они используют для его создания? Добавление его как зависимости dev позволяет им отделить это. В основном есть 2 этапа: время сборки и время выполнения. Dev-зависимости необходимы во время сборки. Регулярные и одноранговые зависимости необходимы во время выполнения. Конечно, с зависимостями зависимостей все быстро сбивает с толку :)
Stijn de Witt
1
Спасибо за этот ответ! Просто чтобы прояснить, в вашем примере, если JacksModuleзависит от того, JillsModule ^1.0.0с JillsModuleбудучи равноправным зависимость JacksModuleи YourCoolProjectиспользовали JacksModuleи JillsModule ^2.0.0, мы получим предупреждение о зависимостях сверстников по НПМ, который посоветует нам установить , JillsModule ^1.0.0как хорошо. Но что происходит потом? YourCoolProjectтеперь будет иметь две версии JillsModuleимпортируемых через import jillsModule from "..."? И как я помню, что при использовании JacksModuleмне нужно передать его экземпляр JillsModule v1.0.0?
tonix
1
@tonix Ну, это действительно будет проблемой из-за несовместимости версий. peerDependencies не решает это. Но это помогает сделать проблему явной. Потому что он будет четко показывать несоответствие версий вместо того, чтобы молча использовать две версии. Разработчик приложения, который выбирает библиотеки, должен будет найти решение.
Стейн де Витт
2
@tonix Или третий вариант: клонируйте репозиторий JacksModule, обновите его, чтобы зависеть от него, JillsModule ^2.0.0и предложите PR для сопровождающего проекта. Это может помочь при появлении сообщения об ошибке, говорящего, что эта зависимость устарела, и вы хотели бы помочь обновить ее. Если вы сделаете хороший пиар, большинство сопровождающих библиотек объединят его и поблагодарить за это. Если сопровождающие не отвечают, вы можете опубликовать свой форк в пространстве имен NPM под своим именем и использовать вместо этого свой форк. В любом случае, есть решения, но peerDependenciesне решить их самостоятельно.
Стейн де Витт
26

Я бы порекомендовал вам сначала прочитать статью снова. Это немного сбивает с толку, но пример с winston-mail показывает вам ответ:

Например, давайте притворимся, что winston-mail@0.2.3указано "winston": "0.5.x"в его "dependencies"объекте, потому что это последняя версия, с которой он был протестирован. Как разработчик приложений, вы хотите самые последние и самые лучшие вещи, поэтому вы ищите последние версии winstonи winston-mailи помещаете их в свой package.json как

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

Но теперь запуск npm install приводит к неожиданному графу зависимостей

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

В этом случае возможно иметь несколько версий пакета, что может вызвать некоторые проблемы. Зависимости между равноправными узлами позволяют разработчикам npm убедиться, что у пользователя есть определенный модуль (в корневой папке). Но вы правы в том, что описание одной конкретной версии пакета может привести к проблемам с другими пакетами, использующими другие версии. Эта проблема связана с разработчиками npm, как говорится в статьях

Один совет : требования к зависимости от сверстников, в отличие от требований к регулярным зависимостям, должны быть снисходительными . Вы не должны блокировать ваши одноранговые зависимости до определенных версий исправлений.

Поэтому разработчики должны следовать semver для определения peerDependencies. Вы должны открыть вопрос для пакета grunt-стероидов на GitHub ...

Fer To
источник
1
Вы говорите это, multiple versions of a package which would cause some issuesно не в этом ли суть менеджера пакетов? Они даже обсуждают это далее в той же статье, где в проекте есть 2 версии одного и того же пакета: одна предоставлена ​​разработчиком, а другая - сторонней библиотекой.
Адам Бек
1
Я думаю, что понимаю суть одноранговой зависимости, но в winstonпримере я теперь просто не могу использовать winston-mailбиблиотеку, потому что моя версия не соответствует одноранговой зависимости? Я бы предпочел иметь это временное понижение с последней и самой лучшей версии для библиотеки 1, чем вообще не использовать ее.
Адам Бек
1
для вашего первого комментария, насколько я понимаю и использую его, это имеет отношение к тестированию, например, если у вас есть пакет, который был протестирован вами для конкретного стороннего пакета, вы не можете быть уверены, что если один изменения ваших зависимостей (исправление ошибок, обновление основных функций), что ваш пакет будет работать. Таким образом, вы можете указать конкретную версию плагина и сохранить с вашими тестами.
до
1
На ваш второй комментарий: именно поэтому они говорят в документах, что разработчики должны быть осторожны со своими зависимостями пакетов и должны использовать semver, например, вместо «0.2.1», «~ 0.2.1» -> разрешает «0.2.x», но не «0.3.x» или «> = 0.2.1» -> все от «0.2.x» до «1.x» или «x.2.». .. (но не очень предпочтительные для пакета НОГО будут идти с ~
Фер To
15

peerDependencies объяснено на простейшем возможном примере:

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


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

запуск npm install в myPackage выдаст ошибку, потому что он пытается установить версию React ^15.0.0AND, fooкоторая совместима только с React ^16.0.0.

peerDependencies НЕ установлены.

Кристофер Токарь
источник
почему бы просто не отреагировать 16 как dep внутри foo? таким образом будут доступны и 15 и 16, и foo может использовать 16, а mypackage может использовать 15?
nitinsh99
React - это фреймворк, который загружается во время выполнения, чтобы и React 15, и React 16 существовали на одной и той же странице, вам нужно было бы одновременно выполнить загрузку, что было бы чрезвычайно тяжело и проблематично для конечного пользователя. Если fooработает как с React 15, так и с React 16, он может указать свой peerDependency как >=15 < 17.
Йенс Бодал
nitinsh99 мой ответ состоял в том, чтобы объяснить цель peerDependencies самым простым возможным примером, а не как избавиться от ошибки, вызванной peerDependencies
Кристофер Токар
@ nitinsh99 Добавление реакции в зависимости от пакета вызовет проблему, такую ​​как крючки - множественные реакции в пакете
Масуд