Монорепозиторий с JavaScript: организация кода для начинающих

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

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

Начать курс

Монорепозиторий с 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

Блоги

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