5 приложений в одном репозитории - Turborepo-монорепа соло-разработчика
Как устроить Turborepo-монорепу для нескольких приложений и сайтов в одном репозитории. Общие конфиги, экономия сборок через кэш и деплой по приложениям - с практической точки зрения.
Главное
Когда делаешь несколько приложений в одиночку, конфиги расползаются. В Turborepo-монорепе eslint и tsconfig выносятся в packages, кэш turbo не пересобирает неизменённые приложения, а --filter деплоит каждое приложение отдельно.
Содержание
Когда делаешь приложения в одиночку, репозитории плодятся как грибы. Одно приложение, один сайт, ещё одно приложение… и вот ты уже пять раз копипастишь конфиг eslint. Я собрал всё это в одну монорепу.
Настоящая проблема - расползшиеся конфиги
Когда репозиториев много, больнее всего не код, а конфиги.
- eslint, prettier и tsconfig копируются в каждый репозиторий
- Поменял одно правило - обходи и правь все по кругу
- Общий UI и утилиты слишком мелкие, чтобы публиковать в npm
Монорепа меняет это на общий доступ из одного места.
Базовая структура
apps/
├── web/ # 랜딩
├── blog/ # 이 블로그
├── wave/ # 오늘의 파도
└── ...
packages/
├── eslint-config/ # 공용 eslint
└── typescript-config/ # 공용 tsconfig
apps/* - это то, что реально деплоится, packages/* - конфиги и код, общие для приложений. tsconfig.json каждого приложения просто наследуется.
{ "extends": "@repo/typescript-config/nextjs.json" }
Теперь правила правятся только в одном месте - в packages.
Кэш turbo - главное оружие
Настоящая сила Turborepo - кэш. Если входы задачи (исходники, конфиги) не изменились, прошлый результат переиспользуется как есть.
turbo run build # 전체 빌드(바뀐 것만 실제로 돎)
turbo run build --filter=blog # blog 앱만
В turbo.json достаточно объявить зависимости между задачами и их выходы.
{
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": [".next/**"] }
}
}
Начиная со второй сборки всё заканчивается за пару секунд с надписью "FULL TURBO".
Деплой по приложениям
Репозиторий один, но деплой у каждого приложения свой. Для Docker командой turbo prune можно вырезать поддерево только нужного приложения и получить лёгкий образ.
turbo prune blog --docker # blog + 내부 의존성만 남긴 out/ 생성
Этот блог я вырезал именно так и поднял в Docker на мини-ПК. Об этом подробнее в статье про self-hosting.
Итоги
apps/*= деплоймент,packages/*= общие конфиги- tsconfig и eslint управляются из одного места через наследование
- Кэш
turboне пересобирает неизменённые приложения --filterиpruneдля деплоя по приложениям
Если вы в одиночку крутите несколько продуктов, монорепа - это не роскошь, а инструмент выживания.
Частые вопросы
Со скольких приложений монорепа начинает окупаться?
Уже с 2, как только появляются общие конфиги (eslint, tsconfig, UI). Суть в том, что конфиг правится в одном месте и применяется ко всем приложениям.
Не замедлится ли сборка?
Наоборот, ускорится. turbo кэширует результаты задач с неизменёнными входами и пересобирает только изменённые приложения.
Похожие статьи
- 💻 Разработка
Если у одного бакета два владельца, побеждает тот, чей apply был последним
Я добавил одно lifecycle-правило для S3, и terraform apply упал. Причина: конфигурацией lifecycle одного и того же бакета владели два ресурса, каждый сам по себе. S3 lifecycle работает не на уровне отдельных правил, а на уровне документа целиком, поэтому последний победитель тихо стирает правила соперника.
- 💻 Разработка
Тесты были зелёными, а уведомление не отправилось ни разу
Код, ставивший задачу в очередь, падал с первого дня, каждый раз. Ошибка проглатывалась, а тесты благодаря мокам оставались зелёными. История о том, как одно двоеточие тихо убило три фичи.
- 💻 Разработка
Пришло оповещение, что база упала. База не падала ни разу
За сутки накопилось четыре оповещения уровня CRITICAL. 504, Prisma P2028, 500 в админке и «сервис DATABASE недоступен». Я первым делом открыл метрики RDS: все 21 час база держала CPU максимум на 19,7% и чувствовала себя прекрасно. Тормозила не база, а круговые поездки через Атлантику, которых на один запрос приходилось двадцать пять. А оповещение о «недоступности» хелсчек сочинил сам про себя.