Skip to content

Что такое peerDependencies

Nikita Elfimov edited this page Aug 28, 2021 · 9 revisions

Дисклеймер

Чтобы понять что тут написано настоятельно рекомендуется ознакомиться (если не знакомы) с:

Что такое peerDependencies Какую проблему они решают?

Абстрактное определение peerDependencies которое можно встретить на просторах интернета никак не поможет понять что это такое и зачем оно надо, так что начать с ними знакомиться следует с проблемы, которую они решают

Реальный пример использования

У нас есть 3 воркспейса: @dashboard/app, @dashboard/index, @dashboard/history

У нас есть пакет react-intl. Из этого пакета нас интересует функционал основанный на работе реакт контекстов:

  • Есть IntlProvider (Context.Provider)
  • Есть хук useIntl (useContext)

Наш проект работает следующим образом:

@dashboard/app "собирает" остальные 2 фрагмента воедино. Так как оба из них используют react-intl И им необходим одинаковый объект intl, то IntlProvider инициализируется именно там. Т.е. абстрактно это выглядит как-то так:

@dashboard/app:

<IntlProvider>
   {route === '/' && <IndexPage />}
   {route === '/history' && <HistoryPage />}
</IntlProvider>

@dashboard/index:

const IndexPage = () => {
   const intl = useIntl()

   return (
      <Text>
         {intl.formatMessage(messages.indexPage)}
      </Text>
   )
}

@dashboard/history:

const HistoryPage = () => {
   const intl = useIntl()

   return (
      <Text>
         {intl.formatMessage(messages.historyPage)}
      </Text>
   )
}

Что может пойти не так?

Сначала обратим внимание на то, как выглядят package.json у всех воркспейсов:

@dashboard/app:

{
  "name": "@dashboard/app",
  "version": "0.0.0",
  "license": "BSD-3-Clause",
  "main": "src/index.ts",
  "dependencies": {
    "react-intl": "5.20.10"
  }
}

@dashboard/index:

{
  "name": "@dashboard/index",
  "version": "0.0.0",
  "license": "BSD-3-Clause",
  "main": "src/index.ts",
  "dependencies": {
    "react-intl": "5.20.10"
  }
}

@dashboard/history:

{
  "name": "@dashboard/history",
  "version": "0.0.0",
  "license": "BSD-3-Clause",
  "main": "src/index.ts",
  "dependencies": {
    "react-intl": "5.20.10"
  }
}

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

@dashboard/app
|_ react-intl

@dashboard/index
|_ react-intl

@dashboard/history
|_ react-intl

Пояснение: у каждого воркспейса свой инстанс(он же экземпляр) пакета react-intl. Чем это чревато:

  • У каждого воркспейса будет свой собственный "мир", в котором существует свой IntlProvider и не существует IntlProvider который мы уже инициализировали в @dashboard/app
  • useIntl будет знать о существовании только своего IntlProvider, который, как известно, нигде не используется

А если поконкретнее и с логами то такой косяк выглядит примерно так:

Invariant Violation: [React Intl] Could not find required intl object. <IntlProvider> needs to exist in the component ancestry

Если упрощать до одного предложения: useIntl стучится в IntlProvider которого не существует. Но почему? Потому что он не знает о существовании IntlProvider в @dashboard/app

Наконец, решение - peerDependencies

Получается наша проблема свелась к тому, что нам нужно сообщить воркспейсам @dashboard/index, @dashboard/history о том, что существует react-intl в @dashboard/app, и uesIntl нужно брать именно оттуда, чтобы получать тот самый объект intl, а не создавать свой

Для этого воспользуемся peerDependencies, и пекеджи у воркспейсов выглядят теперь так:

@dashboard/app:

{
  "name": "@dashboard/app",
  "version": "0.0.0",
  "license": "BSD-3-Clause",
  "main": "src/index.ts",
  "dependencies": {
    "react-intl": "5.20.10"
  }
}

@dashboard/index:

{
  "name": "@dashboard/index",
  "version": "0.0.0",
  "license": "BSD-3-Clause",
  "main": "src/index.ts",
  "peerDependencies": {
    "react-intl": "*"
  }
}

@dashboard/history:

{
  "name": "@dashboard/history",
  "version": "0.0.0",
  "license": "BSD-3-Clause",
  "main": "src/index.ts",
  "peerDependencies": {
    "react-intl": "*"
  }
}

Теперь дерево по факту выглядит так:

@dashboard/app
|_ react-intl

@dashboard/index

@dashboard/history

Да, react-intl просто отсутствует как инстанс в index и history, а peerDependencies говорит, что нужно использовать родительский инстанс react-intl (в нашем случае это тот, что в @dashboard/app)

И...все, в целом это вся суть peerDependencies.

Дополнительно

Еще их можно использовать как регламент для вашей либы. То есть если ваша либа совместима с реактом версии 16, в peerDependencies это можно указать так:

"peerDependencies" :
   "react": "16"

И теперь при попытке установить вашу либу в проект где используется 14 версия реакта будет ошибка