-
Notifications
You must be signed in to change notification settings - Fork 0
Что такое peerDependencies
Чтобы понять что тут написано настоятельно рекомендуется ознакомиться (если не знакомы) с:
- Реакт контекстами
- Как работают dev и обычные зависимости
- Yarn workspaces
Абстрактное определение 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
Получается наша проблема свелась к тому, что нам нужно сообщить воркспейсам @dashboard/index
, @dashboard/history
о том, что существует react-intl
в @dashboard/app
, и useIntl
нужно брать именно оттуда, чтобы получать тот самый объект 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 версия реакта будет ошибка
Давайте представим что у нас есть пакет @fragments/navigation
со следующим списком зависимостей:
Как можно заметить в пакете фигурируют только обычные dependencies
. В связи с этим пакеты установятся следующим образом:
То есть внутри нашего фрагмента появится по одному новому экземпляру каждой либы. Теперь ближе к делу: наш фрагмент нужно отрендерить, для этого мы будем использовать условный renderer-entrypoint
, и теперь зависимости в нашем проекте будут выглядеть следующим образом:
То есть теперь в каждом из присутствующих воркспейсов будут свои экземпляры либ. А теперь переходим к самому интересному: попробуем воспользоваться библиотекой react-intl
. Для этого мы загрузим все локали (переводы текста на другие языки, если проще) в IntlProvider
внутри @site/renderer-entrypoint
.
Почему мы делаем это в рендерер энтрипоинте? - Потому что мы хотим шейрить логику работы с локалями по всему проекту. Если по всему проекту будет один IntlProvider - мы можем быть уверены, что во всех фрагментах логика смены локали будет одна, и не будет инцидентов по типу "в одном фрагменте язык поменялся, а в другом - нет".
Теперь, в нашем фрагменте нам нужно взять локали из IntlProvider
который был инициализирован в рендерер энтрипонте, на схеме это выглядит примерно так:
Но, думаю уже почувствовали подвох: так работать не будет. Все произойдет так:
То есть наш фрагмент будет обращаться к экземпляру react-intl
в котором нету провайдера и который не знает о наших локалях, которые мы описывали в рендерер энтрипоинте
Попробуем peerDependencies:
Давайте теперь добавим пиры и посмотрим что произойдет:
Таким образом мы сказали фрагменту о том, чтобы он взял react-intl
от своего родителя, а не устанавливал свой.
Примечание: так как либа не будет физически присутствовать в нашем воркспейсе все референсы на нее будут не валидны:
import { useIntl } from 'react-intl'
Этот код даст нам TypeError, который скажет что либы react-intl
не существует. Чтобы этого избежать мы добавим react-intl
как дев зависимость (devDependencies
), таким образом в бандле наш фрагмент будет по-прежнему ссылаться на экземпляр либы родителя, но при этом сможет ссылаться на ее копию установленную у себя же, но только для типизации: