Введение в монорепозитории с TypeScript
С ростом сложности современных веб-приложений разработчики всё чаще сталкиваются с проблемой управления кодом. Когда проект состоит из множества взаимосвязанных модулей — фронтенда, бэкенда, библиотек общего назначения — традиционный подход с отдельными репозиториями (polyrepo) может привести к дублированию кода, рассинхронизации версий и сложностям в рефакторинге.
Монорепозиторий (monorepo) — это стратегия, при которой код нескольких проектов хранится в одном репозитории. В сочетании с TypeScript монорепозиторий становится мощным инструментом: строгая типизация обеспечивает безопасность изменений внутри всего стека, а единая система сборки упрощает управление зависимостями.
В этой статье мы рассмотрим, как организовать монорепозиторий с TypeScript: от базовой структуры до продвинутых инструментов вроде Nx и Turborepo.
Преимущества монорепозитория для TypeScript-проектов
Перед тем как перейти к практике, разберёмся, почему монорепозиторий особенно выгоден для TypeScript-проектов:
- Единая типизация — TypeScript позволяет переиспользовать интерфейсы и типы между пакетами без дублирования.
- Атомарные коммиты — изменения в API и его потребителях можно вносить в одном коммите, что упрощает ревью и откат.
- Упрощённый рефакторинг — IDE (например, VS Code) корректно обрабатывает переименования и перемещения файлов во всём репозитории.
- Кэширование сборок — инструменты вроде Nx кэшируют результаты компиляции, ускоряя CI/CD.
Базовая структура монорепозитория
Начнём с простой, но эффективной структуры. Создадим корневой проект и несколько пакетов:
my-monorepo/
├── packages/
│ ├── shared/ # Общие типы и утилиты
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── server/ # Бэкенд (например, Express)
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── web/ # Фронтенд (React)
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
├── package.json # Корневой package.json
├── tsconfig.base.json # Базовый tsconfig для всех пакетов
└── .gitignore
Корневой package.json должен содержать workspace-конфигурацию. Для npm и Yarn это выглядит так:
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"build": "tsc --build",
"clean": "tsc --build --clean"
}
}
Базовый tsconfig.base.json задаёт общие настройки компиляции:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"composite": true
}
}
Каждый пакет расширяет базовый конфиг:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}
Управление зависимостями и TypeScript paths
Одна из ключевых проблем монорепозиториев — правильная настройка импортов между пакетами. Вместо того чтобы публиковать пакеты в npm, мы используем TypeScript Project References и paths.
Настройка Project References
Добавим в корневой tsconfig.json ссылки на проекты:
{
"files": [],
"references": [
{ "path": "./packages/shared" },
{ "path": "./packages/server" },
{ "path": "./packages/web" }
]
}
Теперь при запуске tsc --build TypeScript будет компилировать пакеты в правильном порядке, учитывая их зависимости.
Настройка путей для импортов
Чтобы импортировать из пакета shared в server, используем paths в tsconfig.json пакета-потребителя:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": ".",
"paths": {
"@my-monorepo/shared": ["../shared/src"]
}
},
"include": ["src"],
"references": [
{ "path": "../shared" }
]
}
Пример использования в коде сервера:
// packages/server/src/index.ts
import { User, validateEmail } from '@my-monorepo/shared';
const user: User = { id: 1, email: 'test@example.com' };
console.log(validateEmail(user.email));
Не забудьте также настроить алиасы в package.json пакета shared:
{
"name": "@my-monorepo/shared",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc"
}
}
Продвинутые инструменты: Nx и Turborepo
Для больших проектов ручное управление может стать утомительным. Здесь на помощь приходят специализированные инструменты.
Nx
Nx — это система сборки с умным кэшированием и генераторами кода. Установка:
npx create-nx-workspace@latest my-nx-workspace --preset=ts
Nx автоматически создаёт структуру с поддержкой TypeScript. Пример команды для добавления нового пакета:
nx g @nx/js:lib shared --directory=packages/shared
Клю