Навигация

Монорепозитории: Turborepo, Nx и workspace’ы

Монорепозиторий — это не «свалить всё в одну папку», а архитектурное решение, при котором несколько независимых проектов живут в одном репозитории с чёткими границами. Google, Meta и Microsoft используют этот подход для снижения дублирования кода и несогласованности версий. Статья разбирает workspace’ы в npm/pnpm/Yarn, инструменты Turborepo и Nx, структуру монорепозитория и оптимизацию CI/CD.

Содержание

  1. Что такое монорепозиторий и почему это не «свалить всё в одну кучу»
  2. Эволюция: от монолита к микросервисам и обратно
  3. Workspace’ы в npm, pnpm и Yarn
  4. Turborepo — инкрементальные сборки для монорепозиториев
  5. Nx — полноценная система управления монорепозиторием
  6. Turborepo vs Nx — что выбрать
  7. Структура монорепозитория
  8. Зависимости между пакетами
  9. CI/CD для монорепозиториев
  10. Проблемы и подводные камни
  11. Когда монорепозиторий не нужен
  12. Ключевые тезисы для интервью
  13. Заключение

Что такое монорепозиторий и почему это не «свалить всё в одну кучу»

Монорепозиторий (monorepo) — это репозиторий, который содержит несколько проектов или пакетов в одном git-репозитории. Это не просто «свалить всё в одну папку» — это архитектурное решение с чёткой структурой, инструментами и процессами.

Представьте большую компанию. У неё есть:

  • Фронтенд-приложение (React)
  • Мобильное приложение (React Native)
  • UI-библиотека компонентов
  • Утилиты и хелперы
  • Документация
  • E2E-тесты

Вы можете организовать это двумя способами:

Полирепозиторий (multirepo): Каждый проект — отдельный репозиторий. UI-библиотека — отдельный репо, утилиты — отдельный репо, фронтенд — отдельный репо.

Монорепозиторий: Всё в одном репозитории, но разделено на пакеты с чёткими границами.

Google, Meta, Microsoft — все используют монорепозитории. Google имеет один репозиторий на миллиарды строк кода. Это не значит, что монорепозиторий подходит всем, но это проверенная архитектура для крупных проектов.

Почему монорепозиторий — это не хаос

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

  • Каждый пакет имеет свою зону ответственности
  • Зависимости между пакетами явно объявлены
  • Сборка и тестирование оптимизированы (инкрементальные, кэширование)
  • CI/CD понимает структуру и запускает только нужные задачи

Это не «один большой проект» — это набор независимых проектов с общими зависимостями, управляемый как единое целое.


Эволюция: от монолита к микросервисам и обратно

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

Эра монолитов (до 2010)

В начале был монолит — один большой проект, который содержит всё: фронтенд, бэкенд, базу данных. Это работало, пока проект был маленьким. Но по мере роста монолит становился неподъёмным:

  • Любое изменение требует пересборки всего проекта
  • Команды мешают друг другу (конфликты в коде)
  • Невозможно использовать разные технологии для разных частей
  • Деплой всего монолита из-за одного изменения

Эра микросервисов (2010–2020)

Микросервисы решили проблемы монолита: каждый сервис — отдельный репозиторий, отдельная команда, отдельный деплой. Это дало гибкость, но создало новые проблемы:

Дублирование кода. Каждая команда пишет свои утилиты, свои компоненты, свои решения. Через год у вас 10 разных UI-библиотек, 5 разных способов работы с датами, 3 разных подхода к валидации.

Несогласованность версий. Сервис A использует React 17, сервис B — React 18, сервис C — всё ещё на React 16. Обновление общей зависимости требует координации всех команд.

Сложность интеграции. Когда каждый сервис в своём репозитории, трудно понять, как они работают вместе. Изменение в одном сервисе может сломать другой, но вы узнаете об этом только в production.

Возвращение к монорепозиториям (2020–настоящее время)

Монорепозитории — это не возврат к монолитам. Это эволюция микросервисной архитектуры, которая решает проблемы дублирования и несогласованности, сохраняя гибкость и независимость команд.

В монорепозитории:

  • Каждый пакет может быть независимым (как микросервис)
  • Общие зависимости (UI-библиотека, утилиты) — в одном месте
  • Изменения в общих пакетах сразу видны всем
  • CI/CD может оптимизировать сборку (только изменённые пакеты)

Это «лучшее из обоих миров» — независимость микросервисов с согласованностью монолита.


Workspace’ы в npm, pnpm и Yarn

Первый шаг к монорепозиторию — это workspace’ы. Workspace’ы позволяют управлять несколькими пакетами в одном репозитории с общим node_modules.

Как работают workspace’ы

Представьте структуру:

my-monorepo/
├── package.json          # Корневой package.json
├── packages/
│   ├── ui/               # UI-библиотека
│   │   ├── package.json
│   │   └── src/
│   ├── utils/            # Утилиты
│   │   ├── package.json
│   │   └── src/
│   └── web/              # Веб-приложение
│       ├── package.json
│       └── src/

В корневом package.json вы объявляете workspace’ы:

{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "packages/*"
  ]
}

Теперь, когда вы запускаете npm install в корне, пакетный менеджер:

  1. Находит все пакеты в packages/*
  2. Создаёт симлинки между ними (если один пакет зависит от другого)
  3. Устанавливает общие зависимости в корневой node_modules
  4. Дедуплицирует зависимости (одна версия React для всех пакетов)

npm workspaces

npm поддерживает workspace’ы из коробки (начиная с npm 7):

# Установка зависимостей для всех пакетов
npm install

# Запуск скрипта во всех пакетах
npm run build --workspaces

# Запуск скрипта в конкретном пакете
npm run test --workspace=packages/web

# Добавление зависимости в конкретный пакет
npm install lodash --workspace=packages/utils

pnpm workspaces

pnpm использует более строгий подход с .npmrc и pnpm-workspace.yaml:

# pnpm-workspace.yaml
packages:
  - 'packages/*'
# Установка зависимостей
pnpm install

# Запуск скрипта во всех пакетах
pnpm -r run build

# Запуск скрипта в конкретном пакете
pnpm --filter @myorg/web run test

# Добавление зависимости
pnpm add lodash --filter @myorg/utils

pnpm использует жёсткие симлинки, что предотвращает «фантомные зависимости» (когда пакет может использовать зависимость, которую явно не объявил).

Yarn workspaces

Yarn был первым, кто популяризовал workspace’ы:

{
  "workspaces": [
    "packages/*"
  ]
}
# Установка зависимостей
yarn install

# Запуск скрипта во всех пакетах
yarn workspaces run build

# Запуск скрипта в конкретном пакете
yarn workspace @myorg/web test

# Добавление зависимости
yarn workspace @myorg/utils add lodash

Преимущества workspace’ы

1. Общие зависимости. Если три пакета используют React, он установится один раз в корневой node_modules. Это экономит место и ускоряет установку.

2. Локальные зависимости. Если web зависит от ui, вы можете объявить это как "@myorg/ui": "workspace:*". Пакетный менеджер создаст симлинк, и изменения в ui сразу видны в web без публикации.

3. Единая версия зависимостей. Все пакеты используют одну версию React, TypeScript и других общих зависимостей. Нет проблем «у меня работает, а у тебя нет».

4. Атомарные изменения. Вы можете изменить ui и web в одном коммите, зная, что они совместимы.


Turborepo — инкрементальные сборки для монорепозиториев

Workspace’ы решают проблему управления зависимостями, но не решают проблему сборки. Если у вас 20 пакетов, и вы изменили один, нужно ли пересобирать все 20? Конечно нет.

Turborepo — это инструмент для оптимизации сборки монорепозиториев. Он понимает зависимости между пакетами и запускает только те задачи, которые действительно нужны.

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

Turborepo строит граф задач (task graph) на основе зависимостей между пакетами. Когда вы запускаете turbo run build, Turborepo:

  1. Анализирует зависимости между пакетами
  2. Определяет, какие пакеты изменились (с момента последнего кэша)
  3. Запускает задачи только для изменённых пакетов и их зависимостей
  4. Кэширует результаты задач (локально и удалённо)
  5. Запускает задачи параллельно, когда это возможно

Базовая конфигурация

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"], // Сначала собрать зависимости
      "outputs": ["dist/**"]   // Что кэшировать
    },
    "test": {
      "dependsOn": ["build"]   // Тесты запускаются после сборки
    },
    "lint": {
      // Линтинг не зависит от других задач
    },
    "dev": {
      "cache": false,          // Dev-сервер не кэшируется
      "persistent": true       // Dev-сервер работает постоянно
    }
  }
}

Разберём эту конфигурацию:

build — задача сборки. dependsOn: ["^build"] означает: «Сначала собери все пакеты, от которых зависит этот пакет». Это гарантирует, что зависимости собраны до того, как начнётся сборка текущего пакета.

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

test — тесты зависят от сборки. Turborepo сначала соберёт пакет, затем запустит тесты.

lint — линтинг не зависит от других задач. Turborepo может запустить его параллельно со сборкой.

dev — dev-сервер не кэшируется и работает постоянно (persistent).

Инкрементальные сборки

Главная магия Turborepo — инкрементальные сборки. Если вы изменили только packages/ui, Turborepo:

  1. Определит, что ui изменился
  2. Найдёт все пакеты, которые зависят от ui (например, web)
  3. Пересоберёт только ui и web
  4. Остальные пакеты возьмёт из кэша
# Первый запуск — сборка всех пакетов
turbo run build
# >>> 20 пакетов собрано за 30 секунд

# Вы изменили packages/ui
turbo run build
# >>> 2 пакета собрано за 3 секунды (ui и web)
# >>> 18 пакетов взято из кэша

Это работает благодаря хэшированию. Turborepo хэширует:

  • Исходный код пакета
  • Зависимости пакета
  • Конфигурацию задач
  • Результаты предыдущих сборок

Если хэш не изменился — задача берётся из кэша.

Удалённое кэширование

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

Turborepo поддерживает удалённое кэширование через Turborepo Remote Caching (или собственный бэкенд). Это означает:

  1. Разработчик A собирает проект и загружает кэш в облако
  2. Разработчик B запускает сборку и скачивает кэш из облака
  3. CI-сервер тоже использует общий кэш
# Логин в Turborepo
npx turbo login

# Связывание с проектом
npx turbo link

# Теперь кэш синхронизируется с облаком
turbo run build

Это может сократить время сборки в CI с 10 минут до 30 секунд (если ничего не изменилось).

Параллелизм

Turborepo автоматически параллелит задачи, которые не зависят друг от друга:

Пакет A (нет зависимостей)  ──→ Сборка A ──→ Тест A
Пакет B (нет зависимостей)  ──→ Сборка B ──→ Тест B
Пакет C (зависит от A, B)   ──→ Сборка C ──→ Тест C

A и B собираются параллельно. C ждёт завершения A и B, затем собирается.


Nx — полноценная система управления монорепозиторием

Nx (произносится «эн-экс») — это более мощный инструмент для управления монорепозиториями. Если Turborepo — это «умный раннер задач», то Nx — это полноценная платформа с генераторами, плагинами, анализом зависимостей и визуализацией.

Что даёт Nx помимо Turborepo

1. Генераторы кода. Nx может генерировать пакеты, компоненты, модули с правильной структурой и конфигурацией:

# Создание нового React-приложения
nx generate @nx/react:app web

# Создание новой библиотеки
nx generate @nx/react:library ui

# Создание нового компонента
nx generate @nx/react:component Button --project=ui

2. Анализ зависимостей. Nx автоматически анализирует импорты в вашем коде и строит граф зависимостей:

# Показать граф зависимостей
nx graph

Это открывает интерактивную визуализацию, где вы видите все пакеты и связи между ними.

3.Affected-команды. Nx может определить, какие проекты затронуты изменением:

# Запуск тестов только для затронутых проектов
nx affected --target=test

# Сборка только затронутых проектов
nx affected --target=build

4. Плагины для фреймворков. Nx имеет плагины для React, Next.js, Angular, Vue, Node.js и других технологий. Эти плагины понимают специфику фреймворка и оптимизируют сборку.

5. Миграции. Nx предоставляет команды для автоматического обновления зависимостей и миграции кода:

# Обновление Nx и всех плагинов
nx migrate latest

# Применение миграций
nx migrate --run-migrations

Конфигурация Nx

Nx использует project.json для каждого проекта и nx.json для глобальной конфигурации:

// packages/ui/project.json
{
  "name": "ui",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "packages/ui/src",
  "projectType": "library",
  "targets": {
    "build": {
      "executor": "@nx/react:build",
      "outputs": ["{options.outputPath}"],
      "options": {
        "outputPath": "dist/packages/ui"
      }
    },
    "test": {
      "executor": "@nx/jest:jest",
      "outputs": ["{workspaceRoot}/coverage/packages/ui"],
      "options": {
        "jestConfig": "packages/ui/jest.config.ts"
      }
    }
  }
}
// nx.json
{
  "$schema": "./node_modules/nx/schemas/nx-schema.json",
  "tasksRunnerOptions": {
    "default": {
      "runner": "nx/tasks-runners/default",
      "options": {
        "cacheableOperations": ["build", "test", "lint"]
      }
    }
  },
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"]
    }
  }
}

Nx Cloud — удалённое кэширование и распределённое выполнение

Аналогично Turborepo, Nx поддерживает удалённое кэширование через Nx Cloud. Но Nx Cloud также поддерживает распределённое выполнение задач — задачи могут выполняться на нескольких машинах параллельно.

Если у вас 100 проектов и 500 тестов, Nx Cloud может распределить их на 10 машин, каждая из которых выполняет свою часть. Это может сократить время сборки с 30 минут до 3 минут.


Turborepo vs Nx — что выбрать

Когда выбрать Turborepo

Turborepo — это «минималистичный» инструмент, который делает одну вещь хорошо: оптимизирует сборку. Выберите Turborepo, если:

  • У вас уже есть монорепозиторий с workspace’ами
  • Вам нужна только оптимизация сборки (кэширование, параллелизм)
  • Вы не хотите менять структуру проекта
  • Вы предпочитаете простоту и минимальную конфигурацию
  • Вы используете Vite или другие современные инструменты

Turborepo не навязывает структуру — он работает с тем, что у вас есть.

Когда выбрать Nx

Nx — это «полноценная платформа», которая управляет всем жизненным циклом монорепозитория. Выберите Nx, если:

  • Вы начинаете новый монорепозиторий и хотите правильную структуру
  • Вам нужны генераторы кода и шаблоны
  • Вы хотите визуализацию зависимостей
  • Вам нужны плагины для фреймворков (React, Next.js, Angular)
  • Вы планируете использовать распределённое выполнение задач
  • У вас большая команда и нужна стандартизация

Nx навязывает структуру, но эта структура проверена и оптимизирована.

Сравнительная таблица

ХарактеристикаTurborepoNx
ФилософияМинимализм, «делай одну вещь хорошо»Полноценная платформа
КонфигурацияПростая (turbo.json)Более сложная (nx.json + project.json)
Генераторы кодаНетДа
Визуализация зависимостейНетДа (nx graph)
Affected-командыДа (через --filter)Да (встроенная)
Плагины для фреймворковНетДа (React, Next.js, Angular, Vue)
Удалённое кэшированиеДаДа (Nx Cloud)
Распределённое выполнениеНетДа (Nx Cloud)
Кривая обученияНизкаяСредняя
Подходит дляСуществующих проектовНовых проектов

Можно ли использовать оба?

Да, некоторые команды используют Nx для генераторов и анализа, а Turborepo для сборки. Но это избыточно — Nx имеет встроенные инструменты для всего, что делает Turborepo.


Структура монорепозитория

Правильная структура монорепозитория — это ключ к его успеху. Плохая структура превратит его в хаос.

Рекомендуемая структура

my-monorepo/
├── package.json              # Корневой package.json с workspaces
├── turbo.json                # Конфигурация Turborepo
├── nx.json                   # Конфигурация Nx (если используете)
├── .gitignore
├── README.md
│
├── apps/                     # Приложения (deployable)
│   ├── web/                  # Веб-приложение
│   │   ├── package.json
│   │   ├── next.config.js
│   │   └── src/
│   ├── mobile/               # Мобильное приложение
│   │   ├── package.json
│   │   └── src/
│   └── docs/                 # Документация
│       ├── package.json
│       └── src/
│
├── packages/                 # Библиотеки (не deployable)
│   ├── ui/                   # UI-библиотека
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── src/
│   │       ├── Button.tsx
│   │       ├── Input.tsx
│   │       └── index.ts
│   ├── utils/                # Утилиты
│   │   ├── package.json
│   │   └── src/
│   ├── config/               # Общие конфигурации
│   │   ├── eslint-config/
│   │   ├── tsconfig/
│   │   └── jest-config/
│   └── types/                # Общие типы
│       ├── package.json
│       └── src/
│
└── tools/                    # Скрипты и утилиты для разработки
    ├── scripts/
    └── generators/

Принципы организации

1. Разделение на apps и packages. Приложения (apps) — это deployable-проекты, которые можно запустить independently. Библиотеки (packages) — это переиспользуемый код, который используется приложениями.

2. Именование пакетов. Используйте scope для именования: @myorg/ui, @myorg/utils, @myorg/web. Это предотвращает конфликты имён и явно указывает на принадлежность к организации.

3. Общие конфигурации. ESLint, TypeScript, Jest конфигурации — в отдельных пакетах (packages/config/*). Это гарантирует консистентность across all packages.

4. Границы между пакетами. Каждый пакет должен иметь чёткую зону ответственности. UI-библиотека — только UI-компоненты. Утилиты — только утилиты. Не смешивайте.

Пример package.json для библиотеки

// packages/ui/package.json
{
  "name": "@myorg/ui",
  "version": "0.1.0",
  "private": true,
  "main": "./dist/index.js",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "require": "./dist/index.js",
      "import": "./dist/index.mjs",
      "types": "./dist/index.d.ts"
    }
  },
  "scripts": {
    "build": "tsup src/index.ts --format cjs,esm --dts",
    "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
    "test": "jest",
    "lint": "eslint src/"
  },
  "dependencies": {
    "react": "^19.0.0"
  },
  "devDependencies": {
    "@myorg/eslint-config": "workspace:*",
    "@myorg/tsconfig": "workspace:*",
    "tsup": "^8.0.0",
    "typescript": "^5.0.0"
  }
}

Обратите внимание на "private": true — это предотвращает случайную публикацию пакета в npm. И на workspace:* для локальных зависимостей — это указывает пакетному менеджеру использовать локальную версию.


Зависимости между пакетами

Управление зависимостями между пакетами — это самая сложная часть монорепозитория. Неправильные зависимости создают циклы, замедляют сборку и делают код хрупким.

Правила управления зависимостями

1. Зависимости должны быть однонаправленными. Если web зависит от ui, то ui не должен зависеть от web. Циклические зависимости — это антипаттерн.

✅ Правильно:
web → ui → utils

❌ Неправильно:
web → ui → web (цикл!)

2. Библиотеки не должны зависеть от приложений. Библиотеки (packages/*) должны быть независимыми от приложений (apps/*). Если ui зависит от web, вы не сможете использовать ui в другом приложении.

3. Избегайте глубоких цепочек зависимостей. Если A зависит от B, B зависит от C, C зависит от D — изменение в D требует пересборки всех четырёх пакетов. Старайтесь держать цепочки короткими.

Визуализация зависимостей

Если вы используете Nx, вы можете визуализировать зависимости:

nx graph

Это откроет интерактивный граф, где вы видите все пакеты и связи между ними. Если вы видите циклы или слишком сложные связи — это сигнал к рефакторингу.

Если вы используете Turborepo, вы можете использовать сторонние инструменты:

# madge — анализ зависимостей
npx madge --image dependency-graph.svg packages/

Запрет циклических зависимостей

Вы можете настроить ESLint для запрета циклических зависимостей:

// .eslintrc.js
module.exports = {
  rules: {
    'import/no-cycle': 'error',
  },
};

Это гарантирует, что циклические зависимости будут обнаружены на этапе линтинга, а не в production.


CI/CD для монорепозиториев

CI/CD для монорепозиториев имеет свою специфику. Если у вас 20 пакетов, и вы изменили один, CI должен запустить тесты только для изменённых пакетов, а не для всех 20.

Affected-команды

И Turborepo, и Nx поддерживают affected-команды — запуск задач только для пакетов, которые изменились с момента последнего успешного запуска.

Turborepo:

# Запуск сборки только для изменённых пакетов
turbo run build --filter=...origin/main

# Фильтр по изменённым файлам
turbo run build --filter='[origin/main]'

Nx:

# Запуск тестов только для затронутых проектов
nx affected --target=test --base=origin/main

# Сборка только затронутых проектов
nx affected --target=build --base=origin/main

Это может сократить время CI с 30 минут до 2 минут (если изменился только один пакет).

Пример GitHub Actions для монорепозитория

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Нужна полная история для affected-команд
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - run: npm ci
      
      # Кэширование Turborepo
      - name: Cache Turborepo
        uses: actions/cache@v4
        with:
          path: .turbo
          key: ${{ runner.os }}-turbo-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-turbo-
      
      # Запуск линтинга для всех пакетов
      - name: Lint
        run: npm run lint --workspaces
      
      # Запуск тестов только для изменённых пакетов
      - name: Test affected
        run: npx turbo run test --filter=...origin/${{ github.base_ref }}
      
      # Сборка только для изменённых пакетов
      - name: Build affected
        run: npx turbo run build --filter=...origin/${{ github.base_ref }}

Обратите внимание на fetch-depth: 0 — это нужно для того, чтобы Turborepo мог сравнить с origin/main и определить изменённые пакеты.

Параллелизация в CI

Turborepo и Nx автоматически параллелят задачи в CI. Если у вас есть 5 независимых пакетов, их сборка запустится параллельно.

Но если вы используете Nx Cloud, вы можете пойти дальше — распределённое выполнение. Задачи распределяются на несколько машин, что может сократить время сборки в 10 раз.

# Nx Cloud с распределённым выполнением
- name: Build affected
  run: npx nx affected --target=build --base=origin/main --parallel=5
  env:
    NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

Проблемы и подводные камни

Монорепозитории — не серебряная пуля. У них есть свои проблемы.

1. Размер репозитория

Монорепозиторий может стать очень большим. Если у вас 100 пакетов с тысячами файлов, git может замедлиться.

Решение:

  • Используйте git sparse checkout для работы только с нужными пакетами
  • Настройте .gitignore правильно (исключите node_modules, dist, .turbo)
  • Используйте shallow clone в CI (fetch-depth: 1)

2. Сложность CI/CD

CI/CD для монорепозитория сложнее, чем для обычного проекта. Нужно правильно настроить affected-команды, кэширование, параллелизм.

Решение:

  • Используйте Turborepo или Nx для оптимизации
  • Настройте удалённое кэширование
  • Тестируйте CI локально (например, через act для GitHub Actions)

3. Конфликты версий

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

Решение:

  • Используйте overrides в package.json для принудительного указания версий
  • Старайтесь использовать одну версию общих зависимостей (React, TypeScript)
  • Если нужны разные версии, изолируйте пакеты (используйте отдельные node_modules)

4. Долгая установка зависимостей

Установка зависимостей в монорепозитории может занимать минуты.

Решение:

  • Используйте pnpm (быстрее npm и Yarn)
  • Кэшируйте node_modules в CI
  • Используйте npm ci вместо npm install в CI

5. Сложность для новых разработчиков

Новому разработчику трудно разобраться в структуре монорепозитория.

Решение:

  • Напишите подробный README с объяснением структуры
  • Используйте генераторы кода (Nx) для стандартизации
  • Создайте документацию для каждого пакета

Когда монорепозиторий не нужен

Монорепозиторий — не для всех. Вот признаки, что он вам не нужен:

1. У вас один проект. Если у вас одно приложение и одна библиотека, монорепозиторий избыточен. Используйте обычную структуру.

2. Маленькая команда. Если у вас 2-3 разработчика, координация не проблема. Монорепозиторий решает проблемы больших команд.

3. Независимые проекты. Если ваши проекты не связаны (разные продукты, разные технологии), монорепозиторий не даст преимуществ.

4. Нет общих зависимостей. Если проекты не используют общий код (UI-библиотеку, утилиты), монорепозиторий не нужен.

5. Вы не готовы к сложности. Монорепозиторий требует настройки Turborepo/Nx, правильной структуры, CI/CD. Если вы не готовы инвестировать время — используйте полирепозиторий.

Альтернативы монорепозиторию

1. Полирепозиторий с общими пакетами. Каждый проект — отдельный репозиторий, но общие пакеты публикуются в npm. Это проще, но требует публикации при каждом изменении.

2. Git submodules. Общие пакеты — отдельные репозитории, которые подключаются как submodules. Это сложно в управлении, но даёт гибкость.

3. Monorepo-as-a-Service. Сервисы вроде Bit или Lerna (устарел, но всё ещё используется) предоставляют инструменты для управления монорепозиториями без полной настройки.


Ключевые тезисы для интервью

  • Монорепозиторий — это набор независимых пакетов с общими зависимостями в одном git-репозитории, а не просто «всё в одной папке».
  • Workspace’ы (npm, pnpm, Yarn) создают общий node_modules и симлинки между локальными пакетами через workspace:*.
  • pnpm строже npm и Yarn: жёсткие симлинки предотвращают «фантомные зависимости» — использование пакетов без явного объявления.
  • Turborepo строит граф задач, кэширует результаты и запускает только задачи для изменённых пакетов и их зависимостей.
  • Удалённый кэш Turborepo позволяет разработчикам и CI использовать общий кэш — после первой сборки последующие занимают секунды.
  • Nx — полноценная платформа: генераторы кода, nx graph для визуализации зависимостей, плагины для React/Next.js/Angular.
  • Affected-команды (nx affected, turbo --filter) запускают задачи только для пакетов, затронутых изменением — критично для быстрого CI.
  • Зависимости должны быть однонаправленными (web → ui → utils); циклы ломают граф сборки.
  • Монорепозиторий нужен при нескольких связанных проектах с общим кодом; для одного проекта или маленькой команды он избыточен.

Заключение

Монорепозитории — это мощный инструмент для управления большими проектами с множеством пакетов. Они решают проблемы дублирования кода, несогласованности версий и сложности интеграции.

Ключевые выводы:

  1. Монорепозиторий — это не свалка. Это структурированная экосистема с чёткими границами между пакетами.

  2. Workspace’ы (npm, pnpm, Yarn) — первый шаг к монорепозиторию. Они управляют зависимостями и локальными пакетами.

  3. Turborepo — минималистичный инструмент для оптимизации сборки. Подходит для существующих проектов.

  4. Nx — полноценная платформа с генераторами, визуализацией и плагинами. Подходит для новых проектов.

  5. Affected-команды — запуск задач только для изменённых пакетов. Это критично для быстрого CI.

  6. Структура важна. Разделение на apps и packages, однонаправленные зависимости, общие конфигурации.

  7. Монорепозиторий не для всех. Если у вас один проект или маленькая команда — используйте полирепозиторий.

Практикуйтесь: создайте простой монорепозиторий с двумя пакетами, настройте workspace’ы, добавьте Turborepo. Это даст вам понимание, подходит ли эта архитектура для ваших проектов.