Монорепозиторий с TypeScript: эффективная организация кода для масштабируемых проектов
Разработка современных приложений часто требует разделения кода на несколько независимых модулей или пакетов. При этом возникает дилемма: хранить каждый пакет в отдельном репозитории (полирепозиторий) или объединить все в одном (монорепозиторий). Для проектов на TypeScript монорепозиторий становится особенно привлекательным решением, обеспечивая единообразие типов, упрощение рефакторинга и централизованное управление зависимостями. В этой статье мы разберем, как организовать монорепозиторий с TypeScript, настроить его и эффективно использовать в разработке.
Что такое монорепозиторий и зачем он нужен TypeScript?
Монорепозиторий (monorepo) — это подход, при котором код нескольких проектов (пакетов, библиотек, микросервисов) хранится в одном репозитории. В контексте TypeScript это особенно полезно, так как:
- Общие типы и интерфейсы: Вы можете определить общие типы в одном пакете и переиспользовать их во всех остальных, избегая дублирования и рассинхронизации.
- Единая конфигурация:
tsconfig.json, линтеры и форматтеры настраиваются один раз для всего проекта. - Упрощенный рефакторинг: Изменение общего кода или интерфейса сразу отражается на всех зависимых пакетах, а TypeScript подскажет о несоответствиях.
- Атомарные коммиты: Изменения, затрагивающие несколько пакетов, можно закоммитить и запушить одной операцией.
Однако монорепозиторий требует правильной организации, иначе он превращается в хаос. Ключевые инструменты для работы с монорепозиториями в TypeScript — это npm/yarn/pnpm workspaces и TypeScript Project References.
Структура монорепозитория с TypeScript
Рассмотрим типичную структуру монорепозитория для веб-приложения, состоящего из фронтенда, бэкенда и общей библиотеки утилит:
my-monorepo/
├── packages/
│ ├── shared/ # Общие типы, утилиты, константы
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── tsconfig.json
│ │ └── package.json
│ ├── backend/ # Серверное приложение (например, Express)
│ │ ├── src/
│ │ │ └── app.ts
│ │ ├── tsconfig.json
│ │ └── package.json
│ └── frontend/ # Клиентское приложение (например, React)
│ ├── src/
│ │ └── App.tsx
│ ├── tsconfig.json
│ └── package.json
├── tsconfig.base.json # Базовый конфигурационный файл TypeScript
├── package.json # Корневой package.json (workspaces)
└── .gitignore
В этой структуре:
packages/*— директория, содержащая все пакеты проекта.tsconfig.base.json— общая конфигурация TypeScript, от которой наследуются все пакеты.package.jsonв корне объявляет рабочие пространства (workspaces).
Настройка монорепозитория: пошаговое руководство
1. Инициализация корневого проекта и настройка Workspaces
Создайте корневую директорию и файл package.json:
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"build": "npm run build --workspaces",
"lint": "eslint packages/*/src --ext .ts,.tsx",
"test": "jest"
}
}
Параметр "private": true предотвращает случайную публикацию корневого пакета. "workspaces": ["packages/*"] указывает npm, где искать дочерние пакеты.
2. Создание базовой конфигурации TypeScript
Файл tsconfig.base.json в корне проекта:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"composite": true
},
"exclude": ["node_modules", "dist"]
}
Ключевые опции: "composite": true — обязательна для использования Project References, "declaration": true — генерирует файлы .d.ts для переиспользования типов между пакетами.
3. Конфигурация пакетов с Project References
Каждый пакет должен иметь свой tsconfig.json, который наследует базовую конфигурацию и указывает свои зависимости. Пример для пакета shared:
// packages/shared/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}
Пример для пакета backend, который зависит от shared:
// packages/backend/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"references": [
{ "path": "../shared" }
],
"include": ["src"]
}
В references перечисляются пути к пакетам, от которых зависит текущий. TypeScript будет следить за изменениями в этих пакетах и пересобирать их при необходимости.
4. Настройка package.json в пакетах
Важно правильно указать зависимости между пакетами. В packages/backend/package.json:
{
"name": "@my-monorepo/backend",
"version": "1.0.0",
"scripts": {
"build": "tsc -b",
"start": "node dist/app.js"
},
"dependencies": {
"@my-monorepo/shared": "1.0.0