Монорепозиторий с TypeScript: организация кода и лучшие практики

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в монорепозитории с 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

Клю

Блоги

Рекомендаций по книгам