Понимание, когда и зачем использовать npm peerDependencies

AngularInDepth уходит от Medium. Эта статья, ее обновления и более свежие статьи размещены на новой платформе inDepth.dev

В этой статье я надеюсь прояснить, что такое одноранговые зависимости npm и особенно, когда их следует использовать. Одноранговые зависимости перечислены в файле package.json в объекте peerDependencies.

Чтобы получить максимальную отдачу от этой статьи, вы должны иметь хотя бы вводное представление о npm.

СОДЕРЖАНИЕ

В этой статье:

  1. Мы сравним, как именно работают зависимости и одноранговые зависимости.
  2. Мы рассмотрим несколько примеров как зависимостей, так и одноранговых зависимостей.
  3. Затем мы рассмотрим, как npm обрабатывает конфликты версий.
  4. Наконец, прочно усвоив основы, мы предложим подход к принятию решения о целесообразности одноранговых зависимостей.

Сценарий

Чтобы все было по-настоящему, предположим, что вы создаете библиотеку Angular или даже простой файл JavaScript, который экспортирует некоторые функции.

Ваш проект полагается на пакеты из npm Registry. Эти пакеты являются зависимостями вашего проекта.

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

Другие команды добавят ваш пакет как зависимость в свои собственные проекты. Мы используем зависимости и одноранговые зависимости в package.json, чтобы сообщить этим другим проектам, какие пакеты также необходимо добавить для работы нашего пакета.

Итак, на самом базовом уровне вот как работают зависимости и одноранговые зависимости:

Зависимости

Зависимости перечислены в файле package.json в объекте dependencies.

Когда вы добавляете пакет в dependencies, вы говорите:

  • Моему коду нужен этот пакет для запуска.
  • Если этого пакета еще нет в моем каталоге node_modules, добавьте его автоматически.
  • Кроме того, добавьте пакеты, перечисленные в зависимостях пакета. Эти пакеты называются транзитивными зависимостями.

Одноранговые зависимости

Peer Dependencies перечислены в файле package.json в объекте peerDependencies.

Добавляя пакет в peerDependencies, вы говорите:

  • Мой код совместим с этой версией пакета.
  • Если этот пакет уже существует в node_modules, ничего не делайте.
  • Если этого пакета еще нет в каталоге node_modules или это неправильная версия, не добавляйте его. Но покажите пользователю предупреждение о том, что он не найден.

Добавление зависимостей

Итак, мы добавляем зависимости в файл package.json нашей папки пакетов npm. Давайте посмотрим, как именно мы добавляем пакеты в качестве зависимостей, и некоторые примеры зависимостей пакетов.

Добавление зависимости

Зависимость - это пакет npm, от которого зависит возможность запуска нашего пакета. Некоторые популярные пакеты, которые обычно добавляются как зависимости, - это lodash, request и moment.

Мы добавляем такую ​​обычную зависимость:

npm install lodash

npm добавляет имя и версию пакета к объекту dependencies в файле package.json нашего проекта.

"dependencies": {
    "lodash": "^4.17.11"
  }

Некоторые из вас, возможно, помнят старые времена, когда нам приходилось использовать флаг --save, чтобы заставить npm обновлять dependencies в package.json. К счастью, нам больше не нужно этого делать.

Добавление одноранговой зависимости

Peer Dependencies используются, чтобы указать, что наш пакет совместим с определенной версией пакета npm. Хорошие примеры - Angular и React.

Чтобы добавить одноранговую зависимость, вам действительно нужно вручную изменить файл package.json. Например, для проектов библиотеки компонентов Angular я рекомендую добавить angular/core в качестве одноранговой зависимости. Итак, если вы хотите указать, что ваш пакет создан для Angular 7, вы можете включить что-то вроде этого:

"peerDependencies": {
  "@angular/core": "^7.0.0"
}

О конфликтах

Я получаю много вопросов о том, должен ли определенный пакет npm входить в dependencies или в peerDependencies. Ключом к принятию этого решения является понимание того, как npm справляется с конфликтами версий.

Если вы читали мои предыдущие статьи, вы знаете, что мне нравится, что вы можете делать это вместе со мной! Так что не стесняйтесь работать вместе со мной в этом небольшом эксперименте с npm.

конфликт-тестовый проект

Для начала создадим простой тестовый проект. Назову свое:
conflict-test

Я создал это так:

md conflict-test
cd conflict-test
npm init -y

Затем я вручную отредактировал файл package.json и добавил две зависимости:

"dependencies": {
    "todd-a": "^1.0.0",
    "todd-b": "^1.0.0"
  }

Эти todd-a и todd-b пакеты также имеют свои собственные зависимости:

тодд-а

"dependencies": {
    "lodash": "^4.17.11",
    "todd-child": "^1.0.0"
  }

todd-b

"dependencies": {
    "lodash": "^4.17.11",
    "todd-child": "^2.0.0"
  }

Я хочу, чтобы вы заметили, что todd-a и todd-b используют одну и ту же версию lodash. Но у них есть конфликт версий для todd-child:
todd-a использует todd-child версию 1.0.0
todd-b использует todd-child версию 2.0.0

Теперь я знаю, что вам, как и мне, очень интересно посмотреть, как npm справляется с этим конфликтом версий. В моем основном проекте conflict-test я запускаю npm install. Как и следовало ожидать, npm волшебным образом устанавливает пакеты todd-a и todd-b в нашу папку node_modules. Он также добавляет пакеты, от которых они зависят (транзитивные зависимости). Итак, после запуска npm install мы смотрим на папку node_modules. Это выглядит так:

node_modules
├── lodash 4.17.11
├── todd-a 1.0.0
├── todd-b 1.0.0
│   └── node_modules
│       └── todd-child 2.0.0
└── todd-child 1.0.0

Интересно то, что в нашем проекте есть одна копия lodash. Но у него есть две копии todd-child. Обратите внимание, что todd-b получает свою собственную частную копию todd-child 2.0.0.

Итак, вот правило:

npm разрешает конфликты версий путем добавления дубликатов частных версий конфликтующего пакета.

Подход к взаимозависимости

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

Иногда достаточно иметь две версии одного и того же пакета. Однако некоторые пакеты будут вызывать конфликты, если в одной и той же кодовой базе есть две разные версии.

Например, предположим, что наша библиотека компонентов была создана с использованием Angular 5. Мы бы не хотели, чтобы в наш пакет добавлялась еще одна совершенно другая версия angular/core, когда кто-то добавляет ее как зависимость к своему приложению Angular 6.

Ключ:
Мы не хотим, чтобы наша библиотека добавляла другую версию пакета в node-modules, если этот пакет может конфликтовать с существующей версией и вызывать проблемы.

peerDependencies или зависимости?

Итак, это подводит нас к основному вопросу о наших зависимостях:

Когда мой пакет зависит от другого пакета, следует ли мне помещать его в зависимости или peerDependencies?

Ну, как и с большинством технических вопросов: это зависит от обстоятельств.

Peer Dependencies выражают совместимость. Например, вы захотите уточнить, с какой версией Angular совместима ваша библиотека.

Руководящие принципы

Используйте Peer Dependencies, если верно одно из следующих условий:

  • Наличие нескольких копий пакета вызовет конфликты
  • Зависимость видна в вашем интерфейсе
  • Вы хотите, чтобы разработчик решил, какую версию установить

Возьмем, к примеру, angular/core. Очевидно, что если вы создаете библиотеку Angular, angular/core будет очень заметной частью интерфейса вашей библиотеки. Следовательно, он принадлежит вашему peerDependencies.

Однако, возможно, ваша библиотека использует Moment.js внутри для обработки некоторых входных данных, связанных со временем. Moment.js, скорее всего, не будет отображаться в интерфейсе ваших Angular Services или компонентов. Следовательно, он принадлежит вашему dependencies.

Угловой как зависимость

Учитывая, что вы собираетесь указать в своей документации, что ваша библиотека представляет собой набор компонентов и служб Angular, вы можете задать вопрос:

«Нужно ли мне вообще указывать angular / core как зависимость? Если кто-то использует мою библиотеку, у него уже будет существующий проект Angular ».

Хороший вопрос!

Да, обычно мы можем предположить, что для нашей конкретной библиотеки Angular в Workspace уже будут доступны пакеты Angular. Следовательно, технически нам не нужно было бы беспокоиться о добавлении их в наш список зависимостей.

Однако мы действительно хотим сообщить разработчику, с какими версиями Angular совместима наша библиотека. Поэтому я рекомендую следующий подход:

Добавьте как минимум angular / core для совместимой версии Angular в свой peerDependencies.

Таким образом, разработчики увидят предупреждение, если попытаются использовать вашу библиотеку Angular 7 в своем проекте Angular 6. Не беспокойтесь о добавлении других пакетов Angular. Вы можете предположить, что если у них есть angular / core, у них есть другие библиотеки Angular.

В заключение

В случае сомнений вам, вероятно, следует склоняться к использованию peerDependencies. Это позволяет пользователям вашего пакета самостоятельно выбирать, какие пакеты добавить.