Последние шесть месяцев мы создавали фреймворк для микросервисов node.js для одного из наших клиентов. Эта структура состоит из множества взаимозависимых пакетов.

В этом случае наличие одного репозитория git для каждого пакета будет обременительным. Слишком много ссылок npm и слишком много проектов для управления в нашей среде CI. Также новые функции могут повлиять на многие пакеты, поэтому нам приходится иметь дело с несколькими связанными запросами на вытягивание. Маловероятно, что Github или Bitbucket предоставят нам PR-поддержку для нескольких репозиториев в ближайшее время.

Чтобы преодолеть этот недостаток, в этом проекте мы пошли по пути монорепорации, используя Lerna и Yarn workspaces.

Лерна

Lerna - это инструмент, который оптимизирует рабочий процесс по управлению многопакетными репозиториями с помощью git и npm.

Лерна пытается упростить управление ссылками npm при работе с проектами с несколькими пакетами, размещенными в одном репозитории. Он анализирует пакеты и автоматически создает между ними необходимые npm-ссылки. Он также обрабатывает выполнение задач в нескольких пакетах и ​​облегчает управление версиями и публикацию.

У него есть недостатки, но этим стоит воспользоваться. В больших проектах вроде Babel и Jest используется Лерна.

Рабочие места пряжи

Рабочие области - это новый способ настройки архитектуры вашего пакета (..). Он позволяет вам настроить несколько пакетов таким образом, что вам нужно всего лишь запустить yarn install один раз, чтобы установить их все за один проход.

Ваши зависимости могут быть связаны друг с другом, что означает, что ваши рабочие области могут зависеть друг от друга, при этом всегда используется самый актуальный доступный код. Это также лучший механизм, чем yarn link, поскольку он влияет только на дерево вашей рабочей области, а не на всю систему.

Пряжа во многих отношениях намного лучше, чем npm. Мы любим Yarn и используем ее во всех наших проектах. Вы можете использовать функцию рабочих пространств с Лерной или без нее. Когда оба проекта используются вместе, Лерна делегирует управление зависимостями Yarn.

Испытания

При совместном использовании рабочих пространств Lerna и Yarn существует несколько ограничений.

  • Пакеты должны объявлять свой devDependencies локально, если они хотят использовать двоичные файлы в своих npm scripts. В противном случае вам нужно использовать ../.., и могут возникнуть проблемы, связанные с путями.
  • npm scripts должен быть исполняемым с использованием Lerna в корне монорепозитория и непосредственно внутри каждого пакета. Это необходимо при работе со сценариями, работающими в режиме наблюдения (например, jest --watch).
  • Lerna занимается управлением версиями и публикацией, но не предназначена для работы с частными репозиториями npm.

Структура репо

Это структура нашего проекта:

├── node_modules
├── packages
│   ├── awesome
│   │   └── package.json
│   ├── awesome-module1
│   │   ├── src
│   │   ├── test
│   │   ├── babel.config.js
│   │   ├── eslint.config.js
│   │   ├── jest.config.js
│   │   ├── prettier.config.js
│   │   ├── package.json
│   │   └── task -> ../../scripts/task
│   ├── awesome-module2
│   │   └── ...
│   ├── ...
│   └── awesome-tools
│       ├── src
│       │   └── test-helpers.js
│       ├── babel.config.js
│       ├── eslint.config.js
│       ├── eslint.jest.config.js
│       ├── jest.config.js
│       ├── prettier.config.js
│       ├── package.json
│       └── task -> ../../scripts/task
├── scripts
│   ├── publish
│   └── task
├── lerna.json
└── package.json
  • Yarn обрабатывает зависимости.
  • Lerna обрабатывает задачи, которые затрагивают несколько пакетов (компилировать / тестировать / lint все модули).
  • Одна папка на пакет внутри packages.
  • Все пакеты имеют одинаковую структуру.
  • Каждый пакет определяет только свою среду выполнения dependencies.
  • Все инструменты и devDependencies являются общими и находятся в отдельном пакете.
  • Каждый пакет содержит необходимые файлы конфигурации для инструментария. Каждый файл расширяет общую базовую конфигурацию (мы используем Babel, Jest и ESlint + Prettier для компиляции, тестирования и линтинга / претификации кода).
  • Каждый пакет содержит символические ссылки на общий task скрипт, который определяет, как должны запускаться различные инструменты.
  • Есть пакет «хаб». Это зависит от всех других пакетов и позволяет легко использовать фреймворк (единственная awesome зависимость).
  • Все пакеты имеют номер версии. Мы используем lerna для обновления номера версии одним махом.
  • Публикация обрабатывается специальным publish скриптом, который будет использоваться средой CI.

Добавлять новые пакеты легко, потому что они имеют одинаковую структуру. Также есть множество точек расширения: любой пакет может добавить что-то уникальное, если это необходимо (например, свои собственные правила линтинга).

Рассмотрим каждую часть подробнее.

Основа Monorepo: Лерна и Пряжа

Корень package.json выглядит так:

И файл конфигурации lerna.json:

Мы настраиваем рабочие пространства, используя запись workspaces в package.json.

  • Мы определяем задачи для _18 _ / _ 19 _ / _ 20 _ / _ 21_ всех пакетов с помощью Lerna.
  • Чтобы обновить номер версии, мы используем команду lerna publish. В нашем сценарии мы не позволяем Лерне добавлять коммиты или теги в репо. Мы также избегаем публикации пакетов.
  • В среде CI будет использоваться check-packages задача.
  • Также есть publish-packages задача для CI. Мы обнаружим изменения номера версии и при необходимости опубликуем пакеты.

Общие инструменты

Вместо того, чтобы загрязнять корень package.json всеми инструментами, мы определяем другой пакет для размещения инструментов.

Обратите внимание, что мы никогда не будем определять devDependency для этого пакета. Здесь мы злоупотребляем рабочими местами. Yarn расширит зависимости этих пакетов в корне node_modules, где их смогут найти другие пакеты и задачи.

Помимо зависимостей, этот пакет содержит файлы конфигурации для каждого инструмента и некоторый общий код (например, помощники по тестированию). Например, это наша общая конфигурация babel:

Каждый пакет будет полагаться на эту общую конфигурацию (подробнее об этом позже).

Скрипт task

Сценарий задачи выполняет две функции:

  • Определяет набор общих задач: clean, compile, test и lint. Каждая задача использует необходимый инструмент, заставляя ее использовать файлы конфигурации папки текущего пакета. При необходимости он также исправляет базовый каталог (например, это нужно для jest).
  • Разрешает выполнение любого node_modules двоичного файла, если общих задач недостаточно. Например, пользовательская команда компиляции может быть: ./task babel -d ./lib2 ./src2.

Конфигурация пакета

Каждый пакет просто определяет свою среду выполнения dependencies и набор общих scripts. Скрипты делегируют совместный task скрипт с символической ссылкой.

У каждого пакета также есть свои собственныеbabel.config.js, eslint.config.js, jest.config.js и prettier.config.js. По умолчанию они просто импортируют общую конфигурацию, предоставляемую инструментами:

и при необходимости они могут расширить / переопределить конфигурацию. Это простой JS!

Пакет концентратора

Этот пакет - всего лишь package.json, который зависит от всех других пакетов фреймворка.

Сценарий публикации

Для публикации пакетов мы полагаемся на наш CI-сервер. Используя собственный скрипт, мы проверяем каждый публикуемый пакет на предмет текущей версии в реестре. Если мы выложили новую версию в репо, модуль будет опубликован.

Под лежачий камень вода на течет

Сейчас это может показаться простым, но в то время, когда мы настраивали этот проект, документации было немного. Нам пришлось провести много проб и ошибок и развить эту настройку, чтобы добраться до точки, в которой мы действительно чувствовали себя комфортно.

Использование этого примера в качестве примера может помочь вам настроить собственное монорепозиторий. Должно быть легко добавить то, что нужно вашему проекту (возможно, вы используете webpack или rollup, кто знает). Я намеренно не включил в этот пост нашу конфигурацию ESDoc и то, как мы создаем одну общую документацию для всех пакетов. Оставим это на будущее.