Pnpm: особенности peer dependencies при работе над проектом

Pnpm: особенности peer dependencies при работе над проектом

Anastasia Kotova

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

Peer dependencies (или peer deps) — это набор зависимостей, которые должны быть установлены в окружении, где будет работать пакет. Основное их предназначение – обеспечить совместимость этого пакета с определенной версией другого пакета, который должен быть установлен совместно. Например, react-dom требует соответствующую версию react:

{
  "name": "react-dom",
  "version": "18.3.1",
  "peerDependencies": {
    "react": "^18.3.1"
  },
  ...
}

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

Как работает в npm

До 6 версии npm peerDependencies не устанавливались автоматически, а лишь выдавали предупреждение, если в дереве обнаруживалась недействительная версия peer-зависимости. Начиная с npm@7, peerDependencies уже устанавливаются по умолчанию. Это поведение можно отключить с помощью параметра peerDependenciesMeta.

Вот так это выглядит в библиотеке react-redux:

{
  "name": "react-redux",
  "version": "9.2.0",
  "peerDependencies": {
    "@types/react": "^18.2.25 || ^19",
    "react": "^18.0 || ^19",
    "redux": "^5.0.0"
  },
  "peerDependenciesMeta": {
    "@types/react": {
      "optional": true
    },
    "redux": {
      "optional": true
    }
  },
  ...
}

С помощью optional: true указывается, какие пакеты не нужно устанавливать при выполнении npm install.

Особенности работы в pnpm

На самом деле, работа с peer deps в pnpm не сильно отличается от работы с другими зависимостями. Однако обозначим несколько моментов.

Pnpm совместим с npm (насколько это возможно). Это значит, что он, как и npm, устанавливает peerDependencies, по умолчанию, только если в peerDependenciesMeta не указано обратного.

Однако, как и в случае с обычными зависимостями, npm уступает pnpm в вопросах безопасности работы с зависимостями.

Пример

Рассмотрим на простом примере, в чем отличие.

Используем npm

Создадим проект, в который добавим пакет react-redux. Так будет выглядеть package.json:

{
  "name": "npm-check",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react-redux": "^9.2.0"
  }
}

Библиотека react-redux имеет в своих зависимостях React: "react": "^18.0 || ^19",. Поэтому при запуске npm i он установится автоматически. Так как в проекте еще нет этого пакета, и других пакетов, где он бы являлся зависимостью, установится самая последняя 19 версия React.

Файл node_modules/react/package.json:

{
  "name": "react",
  "description": "React is a JavaScript library for building user interfaces.",
  "keywords": [
    "react"
  ],
  "version": "19.0.0",
  ...
}

Теперь попробуем установить библиотеку react-dom, которая тоже имеет в peer deps React. Однако установим не последнюю версию, а react-dom@18. Выполним: npm i react-dom@18.

Наш package.json:

{
  "name": "npm-check",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react-dom": "^18.3.1",
    "react-redux": "^9.2.0"
  }
}

Все было бы хорошо, но react-dom@18 зависит не от 19 версии React, а от 18:

  "peerDependencies": {
    "react": "^18.3.1"
  }

Посмотрим, какой React у нас установлен сейчас, после установки react-dom@18.

Файл node_modules/react/package.json:

{
  "name": "react",
  "description": "React is a JavaScript library for building user interfaces.",
  "keywords": [
    "react"
  ],
  "version": "18.3.1",
...
}

Таким образом, мы получили:

  • неявную смену версии пакета React, которая установилась без нашего ведома
  • отсутствие warning-ов при установке react-dom@18, хотя он был несовместим с текущей версией React
  • возможность обратиться к установленной версии React из кода приложения, потому что Node.js никак не следит за тем, какие зависимости явно указаны в package.json, главное, чтобы нужный пакет просто находился в ближайшей по иерархии директории node_modules
npm не выдает никаких ошибок или предупреждений

Используем pnpm

Теперь сделаем все то же самое с помощью pnpm:

Создадим пустой проект с пакетом react-redux последней версии. При выполнении pnpm i увидим, что также установился react@19.0.0. Кроме того, название папки, где будет лежат пакет react-redux имеет следующий вид:

react-redux@9.2.0_react@19.0.0

Так pnpm явно указывает с какой версией peer deps здесь используется эта версия библиотеки.

После этого попробуем установить react-dom@18. Во-первых, мы увидим warning в консоли в момент установки, о том, что версии из peer deps не совпадают. Во-вторых, заметим, что установленная версия React осталась 19. И даже пакет react-dom будет использовать 19 версию, о чем нам говорит название папки:

react-dom@18.3.1_react@19.0.0

В случае использования pnpm мы получаем:

  • warning при установке несогласованных peer deps
  • отсутствие неявных обновлений версии React
  • невозможность обратиться к React, пока он не будет явно установлен у нас в проекте.
pnpm уже выдает предупреждение о неконсистентности зависимостей

Как можно решить такую неконсистентость peer deps?

Есть как минимум два варианта.

Первый – просто пересобрать дерево зависимостей. Можно удалить pnpm-lock.yaml и node_modules и запустить pnpm i снова. Тогда все peer deps разрешатся как надо и оба пакета будут использовать версию react@18.3.1, о чем нам скажут, в частности, названия папок:

react-dom@18.3.1_react@18.3.1

react-redux@9.2.0_react@18.3.1

При переустановке всех зависимостей предупреждения больше нет

Второй – явным образом установить необходимую версию React, в данном случае это 18.

Конкретно эта проблема решается так просто потому, что библиотека react-redux поддерживает в своих peer deps 18 и 19 версию. Однако на практике все может оказаться сложнее. Для таких случаев, в частности, можно использовать блок overrides, который работает и в pnpm, и в npm.

Вывод

На примере работы с peer dependencies, мы вновь убедились, какими преимуществами обладает pnpm по сравнению с npm. Поэтому я по-прежнему считаю, что стоит начинать переход на pnpm, незаслуженно обделенный вниманием.

Полезные ресурсы

Про peerDependencies в npm

Про peerDependencies в pnpm

Report Page