Управление проектом Monorepo с помощью Turborepo

2 мин

1. Почему стоит выбрать Monorepo?

Monorepo, или единый репозиторий кода, — это стратегия хранения нескольких независимых проектов или пакетов в одном репозитории. По сравнению с моделью Polyrepo, где у каждого проекта отдельный репозиторий, Monorepo дает несколько заметных преимуществ:

  • Совместное использование кода: Обмен компонентами, вспомогательными функциями и определениями типов между разными проектами становится очень простым.
  • Атомарные коммиты: Если изменение одной функции затрагивает несколько пакетов, его можно выполнить одним коммитом, обеспечив согласованность версий.
  • Упрощенное управление зависимостями: Все проекты используют общий node_modules или оптимизируют его через workspace pnpm/yarn, что уменьшает конфликты зависимостей и расхождения версий.

Однако по мере роста репозитория Monorepo приносит и новую проблему: производительность сборки.

2. Проблемы Monorepo

Представьте, что в репозитории есть docs, webapp и общая библиотека UI-компонентов ui. Если вы исправили только одну опечатку в docs, меньше всего хочется, чтобы все проекты репозитория, включая webapp и ui, заново собирались и тестировались.

Традиционным инструментам управления Monorepo, таким как Lerna, не хватает возможностей оркестрации задач и кеширования сборки, что приводит к следующим проблемам:

  • Повторная работа: При каждом запуске CI/CD все собирается и тестируется с нуля, даже если большая часть кода не изменилась.
  • Долгая сборка: Невозможно эффективно использовать несколько ядер CPU для параллельного выполнения задач.
  • Сложные скрипты: Для ручного управления порядком выполнения задач приходится писать сложные скрипты package.json.

3. Turborepo: создан для скорости

Turborepo — высокопроизводительная система сборки для Monorepo на JavaScript/TypeScript, позднее приобретенная Vercel. Она решает описанные проблемы с помощью двух ключевых технологий: инкрементальной сборки и удаленного кеширования.

a. Инкрементальная сборка и конвейеры задач

Turborepo позволяет определять зависимости между задачами в файле turbo.json в корне проекта.

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": []
    },
    "lint": {
      "outputs": []
    },
    "dev": {
      "cache": false
    }
  }
}
  • dependsOn: ["^build"]: Символ ^ означает, что задача build пакета зависит от задач build всех пакетов, от которых он зависит. Turborepo выполняет задачи в наиболее эффективном параллельном порядке на основе этого графа.
  • outputs: Сообщает Turborepo, какие выходные файлы создает задача.

При запуске pnpm turbo build Turborepo вычисляет, какие файлы изменились, и заново собирает только затронутые пакеты. Если исходный код и зависимости пакета не изменились, Turborepo напрямую использует результат предыдущей сборки, что происходит мгновенно.

b. Удаленное кеширование

Это «главная особенность» Turborepo. Система не только кеширует результаты сборки на локальном компьютере, но и может загружать кеш на общий удаленный сервер, например Vercel или в собственный бакет S3.

Это означает:

  • Ваш коллега, получив свежий код, может напрямую скачать кеш, если вы уже собрали нужную ему часть, и не выполнять локальную сборку заново.
  • Сервер CI/CD также может подключаться к этому удаленному кешу. После сборки и кеширования PR в CI другие разработчики и последующие задачи CI смогут немедленно использовать результат.

Это способно сократить время сборки для всей команды с десятков минут до нескольких минут или даже десятков секунд.

Заключение

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