Как легко управлять зависимостями в монорепозитории JS

Согласно опросу State of JS, проведенному в 2021 году, управление зависимостями в JavaScript  —  главная проблема для разработчиков, что не удивительно. На втором месте  —  архитектура кода.

Пытаясь эффективно управлять зависимостями в монорепозитории, можно столкнуться с этими двумя проблемами одновременно.

В некоторых случаях монорепозитории упрощают и ускоряют разработку, а также обеспечивают возможность совместно работать над кодом. Многие компании, от Google и Uber до стартапов, порой используют монорепозитории ради этих преимуществ.

Однако выбор архитектуры монорепозитория сопровождается определенными неудобствами. Не исключено, что возникнут проблемы, которые могут осложнить разработку и поддержку программных проектов, особенно при масштабировании.

Уделим внимание одной из самых наболевших проблем монорепозитория  —  управлению зависимостями. Посмотрим, как с помощью инструментов с открытым исходным кодом, таких как рабочие среды Pnpm и Yarn, а также Bit, можно элегантно решить эту задачу и значительно упростить работу с зависимостями.

Болевые точки при управлении зависимостями в монорепозитории

Почему же управление зависимостями в монорепозитории связано с трудностями даже при использовании рабочей среды Yarn или Pnpm?

Предполагалось, что при наличии в монорепозитории лишь одной системы управления зависимостями во время разработки, дела будут идти намного проще. Есть только один набор зависимостей, к которому имеет доступ каждый пакет в монорепозитории. Но все не так-то просто. То, что функционирует в разработке, может отказать в производстве. Посмотрим, как это происходит.

Фантомные зависимости ломают код при публикации

В рабочей среде Pnpm и Yarn каждый пакет в монорепозитории имеет собственный package.json, в котором определены зависимости. Однако все зависимости в рабочей среде находятся в корне монорепозитория.

Сначала это выглядит как стремительный подъем на вершину: каждый проект или пакет в монорепозитории имеет доступ к использованию всех библиотек зависимостей.

Но что произойдет, если пакет, разрабатываемый в монорепозитории, импортирует и использует зависимость, к которой он имеет доступ, но которая не определена в его package.json? В таком случае тесты во время разработки пройдут успешно, а при публикации вас будет ожидать провал.

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

workspace
├── node_modules
│ └── .pnpm
│ ├── [email protected]/node_modules/lodash
│ └── [email protected]/node_modules/ramda
├── button
│ └── node_modules
│ └── lodash --> ../../node_modules/.pnpm/[email protected]/node_modules/lodash
└── card
└── node_modules
└── ramda --> ../../node_modules/.pnpm/[email protected]/node_modules/ramda

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

Версии зависимостей

Еще одна большая проблема  —  работа с версиями зависимостей в монорепозитории.

Дело в том, что у вас может быть установлена зависимость (например, Lodash или Prettier) в корне и другие версии в package.json пакета, разрабатываемого в монорепозитории.

В результате VSCode будет “видеть” одну версию, в то время как при публикации пакета будет использоваться другая версия той же зависимости. Вероятность сбоя в этом случае очень высока.

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

Решение: рабочие среды Yarn или Pnpm с Bit

Bit  —  это проект с открытым исходным кодом, созданный для разработки, основанной на компонентах.

Рабочая среда Bit добавляет виртуальный слой поверх базы кода, что позволяет разрабатывать и комбинировать “компоненты” (в данном случае  —  пакеты).

Bit решает многие проблемы, связанные с разработкой нескольких пакетов в одной базе кода, в том числе версионирование и установку зависимостей  —  как с внутренней, так и с внешней стороны проектов.

Сочетание Bit с pnpm  —  возможность получить самый продвинутый и простой опыт разработчика для работы в монорепозитории. Фактически, Bit использует pnpm для процесса установки “за кадром”, причем оба механизма разработаны одним и тем же человеком в сообществе разработчиков ПО с открытым исходным кодом.

Как это работает

Когда вы занимаетесь разработкой в среде Bit, вам не нужно беспокоиться о зависимостях “компонента” (т. е. пакета), над которым вы работаете. Также не имеет значения, являются ли они зависимостями для разработки или среды выполнения, как и то, какая у них версия.  

Допустим, вы работаете в среде Bit и хотите добавить новый пакет в монорепозиторий под названием new-lib. Вы можете создать его как компонент следующим образом:

bit init
bit create node new-lib

Далее вы редактируете файл кода и добавляете оператор import, чтобы использовать Ramda:

import R from 'ramda';

export function lib() {
  return 'Hello world!';
}

Теперь код использует Ramda, но у вас еще нет зависимости.

При использовании bit-status вам наверняка понадобится Ramda, а, возможно, и React, если придется отображать “композиции” компонента (визуальные примеры). Предположим, необходимо сделать и то, и другое.

Просто запустите следующую команду: bit install react ramda.

Рабочая среда Bit “знает”, какому компоненту не хватает этих зависимостей, и восполнит этот пробел соответствующим образом. Вам не нужно специально определять зависимости. Более того, Bit может “распознать” зависимость для разработки и зависимость среды выполнения, и совершить соответствующее дополнение.

Затем запустите bit show new-lib, и Bit “скажет”, какими зависимостями обладает этот пакет, а также различит зависимости для разработки и другие зависимостями. Вот пример (где new-lib называется просто lib): 

Зависимости, которые больше не используются в коде

Что происходит, когда вы перестаете использовать зависимость в коде? Удаляется оператор import? Bit “узнает”, что эта зависимость больше не используется, и удаляет ее без необходимости делать это вручную (как в рабочих средах pnpm/yarn без Bit).

Удаляя оператор import, вы не можете сказать наверняка, используется ли эта зависимость в другом файле. Если вы ответственный разработчик, вы просто оставите его на месте. Bit устраняет эту проблему.

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

Тестовые зависимости

Если зависимость используется в тестах пакета, но не в коде, и установлена в рабочей среде, без Bit пришлось бы устанавливать ее как зависимость пакета. При использовании Bit, если зависимость определена в рабочей среде, этого не требуется. Запустите bit show new-lib после добавления зависимости в тесты  —  и вы увидите, что она определена для компонента.

Версия зависимости

В отличие от традиционного рабочего пространства, в рабочей среде Bit вы перечисляете все зависимости в одном месте  —  в файле workspace.jsonc. После запуска bit install ramda раздел зависимостей в workspace.jsonc будет выглядеть следующим образом:

"teambit.dependencies/dependency-resolver": {
"packageManager": "teambit.dependencies/pnpm",
"policy": {
"dependencies": {
"ramda": "0.28.0"
}
}
},

Порой в некоторых пакетах нужно использовать другую версию ramda. В традиционном рабочем пространстве вы бы сделали это в файлах package.json. А вот с Bit можно использовать различные варианты для настройки групп компонентов. Например, если нужно использовать "[email protected]" для любого пакета, который находится внутри каталога пользовательского интерфейса, примените такую конфигурацию:

"teambit.dependencies/dependency-resolver": {
"packageManager": "teambit.dependencies/pnpm",
"policy": {
"dependencies": {
"ramda": "0.28.0"
}
}
},
"teambit.workspace/variants": {
"{ui/**}": {
"teambit.dependencies/dependency-resolver": {
"policy": {
"dependencies": {
"ramda": "0.27.0"
}
}
}
}
},

Как это возможно?

Bit  —  это не менеджер пакетов. Он взаимодействует с pnpm и фактически задействует его функционал.

В рабочей среде Bit нет package.json для компонентов (пакетов). Вместо этого, Bit динамически генерирует файлы package.json и передает их непосредственно менеджеру пакетов с помощью программного API. Обработка происходит посредством механизма разрешения зависимостей. В качестве менеджера пакетов используется pnpm, но вы можете изменить его на Yarn в файле workspace.jsonc.

Здесь можно посмотреть, как работает Bit.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Jonathan Saring: How to Easily Manage Dependencies in a JS Monorepo

Предыдущая статьяКак создать тайм-трекер с помощью API Telegram Bot и веб хуков
Следующая статьяRust как часть микросервисной архитектуры