Монорепозиторий с JavaScript: организация кода для начинающих
С ростом проекта и появлением общей логики, которую нужно переиспользовать в разных частях приложения, разработчики сталкиваются с проблемой организации кода. Многие начинают копировать файлы, создавать отдельные npm-пакеты или запутанные модули. Решение этой проблемы — монорепозиторий (monorepo). В этой статье мы разберём, что такое монорепозиторий, зачем он нужен в экосистеме JavaScript (Node.js) и как его настроить с помощью популярных инструментов. Статья ориентирована на начинающих разработчиков, которые уже знакомы с основами Node.js и npm.
Что такое монорепозиторий и зачем он нужен?
Монорепозиторий — это подход к организации кода, при котором все проекты, библиотеки и сервисы хранятся в одном репозитории (например, на GitHub). В отличие от мультирепозитория (multirepo), где каждый проект имеет свой собственный репозиторий, монорепозиторий объединяет всё в одном месте.
Основные преимущества:
- Единый контроль версий: Проще управлять изменениями, которые затрагивают несколько пакетов.
- Переиспользование кода: Общие утилиты, типы и конфигурации находятся рядом и не требуют публикации в npm.
- Упрощение рефакторинга: Изменения в одном пакете автоматически тестируются вместе с другими.
- Единая сборка и тестирование: Можно запустить тесты для всех пакетов одной командой.
Однако у монорепозитория есть и недостатки: он может стать слишком большим, а управление зависимостями — сложным. Для решения этих проблем существуют специальные инструменты.
Инструменты для работы с монорепозиторием в JavaScript
В экосистеме Node.js есть несколько популярных инструментов для управления монорепозиторием. Мы рассмотрим два самых распространённых: NPM Workspaces (встроенный в npm) и Lerna.
NPM Workspaces
Начиная с версии npm 7, появилась встроенная поддержка монорепозиториев через workspaces. Это простой и лёгкий способ организовать код без установки дополнительных пакетов.
Для настройки достаточно создать корневой package.json и указать в нём папки с пакетами:
// package.json (корневой)
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*"
]
}
Затем создайте структуру папок:
my-monorepo/
├── package.json
├── packages/
│ ├── utils/
│ │ ├── package.json
│ │ └── index.js
│ └── app/
│ ├── package.json
│ └── index.js
Теперь, если пакет app зависит от utils, вы можете просто указать его в зависимостях:
// packages/app/package.json
{
"name": "@my/app",
"dependencies": {
"@my/utils": "*"
}
}
После выполнения npm install в корне проекта, npm создаст символические ссылки между пакетами. Это значит, что вы можете разрабатывать оба пакета одновременно, и изменения в utils будут сразу доступны в app.
Lerna
Lerna — это более мощный инструмент, который часто используется вместе с NPM Workspaces или Yarn Workspaces. Он добавляет возможности для публикации пакетов, версионирования и выполнения команд во всех пакетах.
Установка Lerna:
npm install -g lerna
Создание конфигурации:
lerna init
Это создаст файл lerna.json и базовую структуру. Пример конфигурации:
// lerna.json
{
"version": "0.0.0",
"packages": ["packages/*"],
"npmClient": "npm"
}
Lerna позволяет запускать команды во всех пакетах:
lerna run test
lerna run build
А также управлять версиями пакетов с помощью lerna version и lerna publish.
Практический пример: структура монорепозитория для Node.js
Давайте создадим небольшой монорепозиторий для веб-приложения. Предположим, у нас есть:
- @my/utils — общие утилиты (например, форматирование дат).
- @my/server — серверная часть на Express.
- @my/client — клиентская часть (простой HTML/JS).
Структура будет выглядеть так:
my-web-app/
├── package.json # корневой с workspaces
├── packages/
│ ├── utils/
│ │ ├── package.json
│ │ └── src/
│ │ └── formatDate.js
│ ├── server/
│ │ ├── package.json
│ │ └── src/
│ │ └── index.js
│ └── client/
│ ├── package.json
│ └── src/
│ └── index.html
Корневой package.json:
{
"name": "my-web-app",
"private": true,
"workspaces": ["packages/*"],
"scripts": {
"start": "lerna run start",
"test": "lerna run test"
}
}
Пакет utils (простая функция):
// packages/utils/src/formatDate.js
function formatDate(date) {
return new Intl.DateTimeFormat('ru-RU').format(date);
}
module.exports = { formatDate };
Пакет server, который использует utils:
// packages/server/src/index.js
const express = require('express');
const { formatDate } = require('@my/utils');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send(`Сегодня: ${formatDate(new Date())}`);
});
app.listen(port, () => {
console.log(`Сервер запущен на http://localhost:${port}`);
});
Теперь, если вы запустите npm install