Open Source
Monorepo для OSS-проектов
Babel поддерживает 75+ npm-пакетов из одного репозитория. React, Vue, TypeScript - все monorepo. Как управлять десятками пакетов, не сходя с ума?
- Babel: 75+ пакетов в одном репозитории, один PR может атомарно изменить парсер и трансформер одновременно
- React: react, react-dom, react-reconciler, scheduler - все в одном репо, гарантированно совместимы при каждом релизе
- Vercel использует Turborepo в своём monorepo и открыла его как OSS - remote cache экономит тысячи минут CI в день
- Remix, Svelte, Astro - все используют Changesets для прозрачного версионирования с человекочитаемыми changelog-записями
Зачем monorepo в OSS
**Monorepo** - это один Git-репозиторий для нескольких пакетов. Противоположность - **polyrepo**: каждый пакет в своём репозитории. Это не про «один репозиторий для всего»; это про размещение связанных пакетов вместе.
Реальные примеры monorepo в OSS: **Babel** (75+ пакетов: `@babel/core`, `@babel/parser`, `@babel/traverse`...), **React** (`react`, `react-dom`, `react-reconciler`, `scheduler`), **Vue 3** (`@vue/reactivity`, `@vue/runtime-core`, `@vue/compiler-core`), **Vitest**, **Changesets**, **TypeScript**.
**Когда monorepo оправдан:** несколько пакетов с общим кодом; atomic changes - один коммит меняет `core` и `parser` одновременно; общий tooling (eslint, tsconfig, jest config); нужно гарантировать что пакеты совместимы друг с другом перед публикацией.
**Когда polyrepo лучше:** пакеты независимы и редко меняются вместе; разные команды с разным ритмом релизов; разные stack-и (backend на Go, frontend на TS).
**npm/yarn/pnpm workspaces** - базовый механизм monorepo. `pnpm-workspace.yaml` или поле `workspaces` в `package.json` говорят менеджеру пакетов, где искать локальные пакеты. Turborepo и Nx - надстройки поверх этого механизма.
В React-репозитории один коммит меняет поведение `react-reconciler` и фиксирует `react-dom`, чтобы они оставались совместимы. В polyrepo такой атомарный PR был бы:
Turborepo и Nx
**Turborepo** (Vercel) - инструмент для запуска задач в monorepo с умным кешированием. Главная идея: если входные файлы не изменились - не запускать задачу повторно, использовать кеш.
**Remote cache** - поделиться кешем между разработчиками и CI. Верифицированные кеши загружаются с сервера Vercel или self-hosted. Разработчик делает `git pull` и сразу получает кеш от CI - не нужно пересобирать.
**Nx** (Nrwl) - альтернатива с более богатыми возможностями: генераторы кода, dependency graph визуализация, affected commands (запустить только задачи для изменённых пакетов). Значительно сложнее Turborepo и требует больше конфигурации.
**Выбор между ними:** Turborepo - минималистичный, идеален если уже есть workspaces и нужно кеширование. Nx - когда нужны генераторы, строгие архитектурные ограничения, или проект уже большой. Babel, Jest, Vercel - используют Turborepo. Angular, NgRx - Nx.
В turbo.json в pipeline для `build` стоит `"dependsOn": ["^build"]`. Символ `^` означает:
Changesets: версионирование в monorepo
В monorepo возникает вопрос: `react` и `react-dom` изменились вместе - как их версионировать? Если просто bumping всё одновременно, теряется семантика версий. **Changesets** решает это через файлы-намерения.
Workflow с Changesets в три этапа: **1)** Разработчик создаёт `.changeset/*.md` вместе с кодом в PR. **2)** CI запускает changeset-bot, который проверяет наличие changeset в PR и оставляет комментарий. **3)** При merge в main специальный PR «Version Packages» аккумулирует все changeset-файлы и применяет их.
**Как это используют известные проекты:** Remix - каждый PR с изменением требует changeset; Svelte и SvelteKit - changeset-bot в каждом PR; Astro - обязательный changeset как часть контрибьюторского workflow; pnpm - тоже Changesets.
**Альтернатива: Lerna + Conventional Commits.** Lerna - старейший инструмент для monorepo, умеет автоматически определять версию из conventional commits. Но Changesets дают контрибьюторам явный контроль и человекочитаемые changelog-записи.
Монорепо означает, что весь код деплоится одновременно. Независимые релизы невозможны
Монорепо - это единый репозиторий, не единый деплой. Google, Meta и Microsoft деплоят компоненты из монорепо независимо через fine-grained build targets. Bazel/Pants разрешают сборку конкретного сервиса без пересборки всего репозитория
Путаница возникает потому что polyrepo создаёт физические boundaries между командами. Монорепо убирает физические barriers, но не устраняет логическую независимость компонентов
Контрибьютор открывает PR в monorepo проект, использующий Changesets. Changeset-bot пишет: «No changeset found». Это значит:
Ключевые идеи
- Monorepo оправдан когда пакеты связаны: нужны atomic changes, общий tooling, гарантия совместимости
- pnpm/yarn/npm workspaces - базовый механизм; Turborepo и Nx - надстройки с кешированием и оркестрацией
- turbo.json pipeline с dependsOn: ['^build'] гарантирует правильный порядок сборки зависимостей
- Turborepo remote cache позволяет разработчикам и CI делиться кешем - пересборка только при реальных изменениях
- Changesets решает versioning в monorepo: явные файлы-намерения в PR → автоматический version bump и changelog
Связанные темы
Monorepo неотделим от CI/CD - без автоматизации преимущества теряются.
- CI/CD для OSS — GitHub Actions + Turborepo remote cache: CI использует кеш разработчиков и наоборот
- RFC и breaking changes — В monorepo breaking change в одном пакете требует координации - именно для этого нужен RFC процесс
Вопросы для размышления
- У вас monorepo с пакетами A, B и C, где B зависит от A, а C зависит от B. Как настроить turbo.json чтобы build запускался в правильном порядке A→B→C?
- Контрибьютор добавил новую функцию в @my/utils и исправил баг в @my/ui в одном PR. Как правильно создать changeset: один файл на оба пакета или два отдельных? Почему?