Монорепозиторий с TypeScript: организация кода на практике
В современной веб-разработке, особенно при работе с TypeScript, разработчики часто сталкиваются с дилеммой: хранить весь код в одном репозитории (монорепозиторий) или разделять его на множество маленьких репозиториев (полирепозиторий). Для средних и крупных проектов, где важна переиспользуемость кода и единая конфигурация, монорепозиторий становится стандартом де-факто. В этой статье мы разберем, как правильно организовать монорепозиторий на TypeScript, начиная от базовой структуры и заканчивая продвинутыми техниками.
Что такое монорепозиторий и зачем он нужен?
Монорепозиторий — это подход, при котором код нескольких проектов (пакетов, библиотек, приложений) хранится в одном репозитории. В контексте TypeScript это особенно удобно, так как позволяет:
- Переиспользовать типы и интерфейсы между разными частями системы без публикации в npm.
- Упростить рефакторинг: изменение в одном пакете автоматически валидируется во всех зависимых.
- Иметь единую конфигурацию для линтера, форматтера и сборщика.
- Упростить CI/CD: один репозиторий — один пайплайн.
Среди популярных инструментов для работы с монорепозиториями в TypeScript выделяются Nx, Turborepo и npm/yarn/pnpm workspaces. Мы рассмотрим подход с использованием pnpm workspaces и TypeScript Project References, так как это минимальный и гибкий набор.
Структура монорепозитория: базовая настройка
Давайте создадим типовую структуру монорепозитория для TypeScript-проекта. Предположим, у нас есть три пакета: core (общие типы и утилиты), server (бэкенд) и web (фронтенд).
1. Корневая конфигурация
Начнем с корня проекта. Создадим файл package.json с workspace:
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"build": "tsc -b",
"clean": "tsc -b --clean",
"lint": "eslint packages/*/src"
}
}
Затем настроим корневой tsconfig.json. Используем Project References — это ключевая фича TypeScript для монорепозиториев:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"composite": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": ".",
"skipLibCheck": true
},
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/server" },
{ "path": "./packages/web" }
]
}
Важно: опция composite: true обязательна для пакетов, на которые ссылаются другие пакеты. Она включает инкрементальную сборку и генерацию .d.ts файлов.
2. Настройка пакета core
Перейдем в packages/core. Создадим package.json:
{
"name": "@my/core",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc -b"
}
}
И tsconfig.json для этого пакета:
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}
Создадим простой модуль src/index.ts:
export interface User {
id: string;
name: string;
email: string;
}
export function greet(user: User): string {
return `Hello, ${user.name}!`;
}
Теперь пакет @my/core готов к использованию.
Организация зависимостей между пакетами
Самое интересное — это настройка кросс-пакетных зависимостей. Допустим, наш server использует типы из core.
1. Настройка пакета server
Создадим packages/server/package.json:
{
"name": "@my/server",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"dependencies": {
"@my/core": "workspace:*"
},
"scripts": {
"build": "tsc -b"
}
}
Обратите внимание на "workspace:*". В pnpm это означает "использовать локальную версию пакета из workspace". В yarn аналогично "*" или "^1.0.0".
packages/server/tsconfig.json:
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"],
"references": [
{ "path": "../core" }
]
}
Теперь в server/src/index.ts мы можем импортировать типы из core:
import { User, greet } from '@my/core';
const user: User = { id: '1', name: 'Alice', email: 'alice@example.com' };
console.log(greet(user));
При запуске pnpm build из корня, TypeScript сначала соберет core, а затем server — порядок гарантирован за счет Project References.
Продвинутые практики: Nx и Turborepo
Для больших проектов ручная настройка может стать обременительной. Здесь на помощь приходят инструменты вроде Nx