Монорепозиторий с JavaScript: организация кода на практике
Разработка сложных проектов на JavaScript (Node.js) часто сталкивается с проблемой управления кодом. Когда у вас есть несколько пакетов, библиотек или микросервисов, каждый из которых живет в своем репозитории, это приводит к дублированию кода, сложности синхронизации версий и замедлению CI/CD. Решение — монорепозиторий (monorepo). В этой статье мы разберем, как организовать монорепозиторий с JavaScript на практике: от выбора инструментов до настройки сборки и публикации.
Что такое монорепозиторий и зачем он нужен?
Монорепозиторий — это подход к управлению кодом, при котором все пакеты, библиотеки и приложения проекта хранятся в одном репозитории. В отличие от мультирепозиториев (каждый пакет в своем репо), монорепозиторий предлагает:
- Единую историю изменений: легко отслеживать, как изменения в одной части влияют на другую.
- Переиспользование кода: общие утилиты и компоненты живут в одном месте без публикации в npm.
- Упрощенный рефакторинг: изменение API одного пакета сразу видно во всех зависимых.
- Единые настройки CI/CD: один конфигурационный файл для всех пакетов.
Для JavaScript (Node.js) монорепозиторий особенно актуален, так как позволяет избавиться от «ада зависимостей» и ускорить разработку. Рассмотрим практическую реализацию.
Инструменты для монорепозитория в JavaScript
На рынке есть несколько популярных инструментов. Мы рассмотрим два основных: npm workspaces (встроенный в npm) и Lerna (расширенный функционал).
1. npm workspaces (встроенная поддержка)
Начиная с npm v7, пакетный менеджер поддерживает workspaces. Это самый простой способ начать. Пример структуры проекта:
my-monorepo/
├── packages/
│ ├── core/
│ │ ├── package.json
│ │ └── index.js
│ ├── utils/
│ │ ├── package.json
│ │ └── helpers.js
│ └── app/
│ ├── package.json
│ └── server.js
├── package.json
└── lerna.json (опционально)
Корневой package.json:
{
"name": "my-monorepo",
"private": true,
"workspaces": ["packages/*"]
}
Теперь все пакеты в packages/* автоматически линкуются. Чтобы установить зависимости, достаточно выполнить npm install в корне. Зависимости между пакетами указываются стандартно:
// packages/app/package.json
{
"name": "@my/app",
"dependencies": {
"@my/core": "*",
"@my/utils": "*"
}
}
npm workspaces создает симлинки в папке node_modules, поэтому импорты работают как с обычными пакетами.
2. Lerna (продвинутое управление)
Lerna — это инструмент для управления монорепозиториями с возможностью публикации, версионирования и выполнения скриптов параллельно. Установка:
npm install --global lerna
Инициализация в проекте:
lerna init
Файл lerna.json:
{
"version": "independent",
"npmClient": "npm",
"packages": ["packages/*"]
}
Режим independent позволяет каждому пакету иметь свою версию. Основные команды:
lerna bootstrap— устанавливает зависимости и линкует пакеты.lerna run build— выполняет скриптbuildво всех пакетах.lerna publish— публикует измененные пакеты в npm.
Практическая организация кода
Рассмотрим пример настройки монорепозитория для веб-приложения с бэкендом на Node.js и общими утилитами.
Шаг 1: Базовая структура
Создадим три пакета:
@my/core— общие функции (валидация, логирование).@my/api— REST API сервер.@my/worker— фоновый обработчик задач.
Корневой package.json:
{
"name": "my-monorepo",
"private": true,
"workspaces": ["packages/*"],
"scripts": {
"build": "lerna run build",
"test": "lerna run test",
"start:api": "lerna run start --scope=@my/api"
},
"devDependencies": {
"lerna": "^7.0.0",
"typescript": "^5.0.0"
}
}
Шаг 2: Настройка TypeScript (опционально)
Если вы используете TypeScript, создайте корневой tsconfig.json:
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"declaration": true,
"composite": true,
"outDir": "./dist",
"rootDir": "./packages"
},
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/api" },
{ "path": "./packages/worker" }
]
}
В каждом пакете создайте свой tsconfig.json с ссылкой на корневой:
// packages/core/tsconfig.json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
Шаг 3: Пример кода с переиспользованием
Пакет @my/core (src/logger.ts):
export class Logger {
log(message: string): void {
console.log(`[${new Date().toISOString()}] ${message}`);
}
}
Пакет
Блоги
Рекомендаций по книгам
Читайте также
Разбираем foreach в PHP: от А до Я (с примерами)
GraphQL API с TypeScript: пошаговый туториал на практике
Поймать ошибку за хвост: как превратить stack trace в строку в Java
Работа с временными зонами в C#: как не опоздать на встречу с сервером
Контейнеризация JavaScript: Docker для Node.js и фронтенда