НейроАгент

Разрешение версий библиотек в Yarn Workspaces

Решение проблемы с разными версиями зависимостей в монорепе Yarn Workspaces. Настройка nohoist и оптимизация разрешения модулей.

Вопрос

Проблема с разрешением версий библиотеки в монорепе с Yarn Workspaces

Добрый день! У нас настроена монорепа с использованием Yarn Workspaces:

json
"workspaces": {
  "packages": [
    "apps/*",
    "packages/*"
  ]
}

В разных пакетах используются разные версии библиотеки “Admiral”:

  • В пакете apps/pkb используется версия 2.0
  • В пакете apps/opsnext используется версия 1.0

Версии библиотеки нельзя унифицировать. Проблема в том, что Yarn поднимает одну зависимость на уровень монорепы (версия 1.0), а более новая версия (2.0) остается на уровне пакета pkb.

При локальном запуске, по идее, система должна сначала проверять локальные node_modules в пакете, а затем подниматься на уровень монорепы. То есть, при запуске из пакета pkb должна использоваться версия 2.0. Однако на практике всегда берется версия с уровня монорепы (1.0), что приводит к ошибкам.

Я пробовал настроить в Webpack:

  • resolve.alias для библиотеки
  • resolve.modules

Но эти изменения не дали эффекта.

Используемые версии:

  • Yarn: 1.2.22
  • Webpack: 5.88

Подскажите, на что стоит обратить внимание или какие есть предложения по решению этой проблемы?

НейроАгент

В монорепе с Yarn Workspaces при использовании разных версий одной библиотеки проблема возникает из-за механизма hoisting, который поднимает зависимости на уровень корневого проекта. Для решения вашей ситуации с библиотекой “Admiral” в версиях 1.0 и 2.0 необходимо настроить nohoist в корневом package.json, чтобы предотвратить поднятие конфликтующих зависимостей, и использовать правильную стратегию разрешения модулей в Webpack.


Содержание


Принцип работы Yarn Workspaces и hoisting

Yarn Workspaces использует механизм hoisting для оптимизации структуры зависимостей. Когда вы запускаете install, Yarn анализирует все зависимости во всех пакетах и поднимает общие зависимости на уровень корневого node_modules [1]. Это позволяет избежать дублирования и ускоряет установку.

В вашем случае с Yarn 1.2.22, когда одна и та же библиотека используется в разных версиях, Yarn выбирает версию с наибольшим количеством использований и поднимает её в корневой node_modules [6]. Библиотека “Admiral” версии 1.0 поднимается на уровень монорепы, версия 2.0 остаётся в локальном node_modules пакета pkb.

Важно: Версия Yarn 1.2.22 довольно старая и имеет отличия от современных версий в поведении workspaces [10].


Проблема с разными версиями зависимостей {#problema-s-raznymi-versiyami-zavisimostey]

Ваша ситуация классическая проблема монореп с разными версиями зависимостей [5]. Когда версия 1.0 поднята на уровень монорепы, а вам нужно использовать версию 2.0 в пакете pkb, возникает конфликт разрешения модулей.

Node.js использует алгоритм разрешения модулей, который сначала ищет зависимости в локальном node_modules, затем поднимается вверх по иерархии. Однако в случае с workspaces, механизм hoisting нарушает эту логику [8].

Симптомы проблемы:

  • Версия 1.0 используется вместо 2.0 при запуске из пакета pkb
  • Ошибки совместимости из-за разницы в API
  • Непредсказуемое поведение при разработке

Решение через nohoist конфигурацию

Для предотвращения поднятия конфликтующих зависимостей используйте настройку nohoist в корневом package.json [2]:

json
{
  "workspaces": {
    "packages": [
      "apps/*",
      "packages/*"
    ],
    "nohoist": [
      "apps/pkb/node_modules/admiral",
      "apps/pkb/node_modules/admiral/**"
    ]
  }
}

Это скажет Yarn не поднимать библиотеку “Admiral” из пакета pkb на уровень монорепы [4]. После изменения конфигурации необходимо:

  1. Удалить все node_modules папки
  2. Удалить yarn.lock файл
  3. Запустить yarn install заново

Важно: В Yarn 1.x nohoist работает иначе, чем в новых версиях [7]. Для вашего случая с Yarn 1.2.22 синтаксис будет таким же.


Настройка Webpack для разрешения версий

Поскольку вы используете Webpack 5, можно дополнительно настроить разрешение модулей [9]:

javascript
// webpack.config.js
module.exports = {
  resolve: {
    alias: {
      'admiral': path.resolve(__dirname, 'node_modules/admiral')
    },
    modules: [
      path.resolve(__dirname, 'node_modules'),
      // Добавляем локальные модули
      path.resolve(__dirname, 'apps/pkb/node_modules')
    ]
  }
};

Однако, как вы упомянули, resolve.alias и resolve.modules не дали эффекта. Это связано с тем, что проблема кроется в механизме разрешения Node.js, а не в Webpack [3].

Более эффективный подход - использование resolve.fallback в Webpack 5:

javascript
module.exports = {
  resolve: {
    fallback: {
      'admiral': require.resolve('admiral')
    }
  }
};

Альтернативные подходы

1. Yarn Constraints (для современных версий)

Если вы планируете перейти на Yarn 3+, можно использовать плагин yarn-constraints [5], который позволяет контролировать зависимости между workspaces.

2. Ручное управление зависимостями

Добавить библиотеку “Admiral” версии 2.0 в корневой package.json с префиксом private: true:

json
{
  "private": true,
  "dependencies": {
    "admiral": "2.0.0"
  }
}

3. Использование scoped пакетов

Переименовать библиотеку в каждом пакете с использованием scope:

json
{
  "dependencies": {
    "@app/admiral-v1": "1.0.0",
    "@app/admiral-v2": "2.0.0"
  }
}

Проверка и отладка

После внесения изменений убедитесь, что разрешение работает правильно:

  1. Проверьте структуру node_modules:
bash
ls -la apps/pkb/node_modules/admiral
ls -la node_modules/admiral
  1. Запустите тестовый скрипт для проверки разрешения:
javascript
// test-resolution.js
console.log(require.resolve('admiral'));
  1. Используйте команду для проверки зависимостей:
bash
yarn why admiral

Важно: В Yarn 1.2.22 иногда бывает необходимо полностью очистить кэш и переустановить зависимости после изменения nohoist настроек [8].


Источники

  1. nohoist in Workspaces | Yarn Blog
  2. Using different versions of a dependency in different packages of a Yarn Workspace - Stack Overflow
  3. Yarn workspace nohoist option does not seem to work as documentation states… · Issue #6412 · yarnpkg/yarn
  4. Yarn Workspaces hoisting in nested packages with different versions of a dependency results in importing wrong dependency version · Issue #8068 · yarnpkg/yarn
  5. Yarn Constraints: have your monorepo under control | DATAMOLE
  6. Workspaces | Yarn
  7. nohoist with workspaces still hoisting - Stack Overflow
  8. Yarn nohoist without using workspaces - Stack Overflow
  9. A concise guide to configuring React Native with Yarn Workspaces | Medium
  10. Migrating our Monorepo to Yarn 2 | DoltHub Blog

Заключение

  1. Основная проблема связана с механизмом hoisting в Yarn Workspaces, который поднимает общие зависимости на уровень монорепы, игнорируя локальные версии в пакетах.

  2. Ключевое решение - настройка nohoist в корневом package.json для предотвращения поднятия конфликтующих зависимостей, особенно для библиотеки “Admiral” в пакете pkb.

  3. Обязательные шаги после изменения конфигурации: удаление всех node_modules, yarn.lock и повторная установка зависимостей.

  4. Для отладки используйте команды yarn why admiral и проверку путей разрешения через require.resolve('admiral').

  5. Рекомендация по обновлению: рассмотрите возможность перехода на более современную версию Yarn (3+), где механизм workspaces улучшен и имеет больше инструментов для управления версиями зависимостей.