Работая с монорепозиторием, ты наверняка слышал о наборе инструментов Nrwl Nx. Если вкратце, то Nx ускоряет и упрощает работу с монорепой, снабжает полезными утилитами. Держи документацию.

Часто работа с Nx заканчивается на имеющихся дефолтных настройках. Все остальное остается черным ящиком. Время сборки проектов начинает расти, а причины так и остаются неизвестными. Может, есть что-то, что способно улучшить работоспособность монорепы и хотя бы частично решить проблемы?

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

Итак, что же ты мог упустить при работе с Nx:

  • nx run-many

  • dependency graph + nx affected

  • nx-enforce-module-boundaries es-linting

  • computation cache

  • nx cloud

  • buildable libs 

Начнем с примера

Есть монорепозиторий с:

  • приложениями: App1, App2, App3

  • библиотеками: Lib1, Lib2, Lib3.

Вот ссылка для освежения в памяти этих понятий.

На графе представлено их взаимодействие друг с другом.

Граф зависимостей в монорепозитории.
Граф зависимостей в монорепозитории.

Затем мы вносим изменения в Lib3: пилим новую фичу (или докидываем еще больше багов в код, тут как повезет), и в конце всего этого хотим проверить, а не сломали ли мы чего своими правками, надо бы запустить тесты. 

Так как сломаться могло что угодно, то лучше прогнать тесты по всей монорепе.

Начинаем выполнять:

nx test Lib3
nx test App1
nx test App2..
.

Стоп. Слишком долго, попробуем иначе.

Nx run-many

nx run-many
nx run-many

Вот мы и встретили первое упрощение нашей работы (штука популярная, но мало ли, кто не знал). Команда run-many выполняет задачу параллельно для нескольких проектов, можно указать как все (--all), так и какие-то конкретные (-p App1).

В дефолтном состоянии команда выполняет параллельно 3 задачи, это конфигурируется с помощью parallel опции (--parallel=6).

Выполним nx run-many --target=test --all для нашего случая.

Тесты будут запущены для всего: App1, App2, App3, Lib1, Lib2, Lib3. Работоспособно, но если изменения повлияли только на один проект? Не хочется лишний раз прогонять все тесты.

Affected

У Nx есть решение для этого: команда nx affected

Запуская nx affected --target=test, будут выполнены тесты только для Lib3 и App3. С Lib3 все понятно, мы туда внесли изменения. Но откуда взялось App3

Все дело в dependency graph. По сути, граф в начале статьи и является таким графом зависимостей. Визуализировать у себя его можно с помощью команды nx graph.
При запуске nx affected Nx под капотом строит такой граф из имеющихся проектов, запускает анализ изменений в коде и определяет список затронутых изменениями либ/приложений.

Отдельной статьи заслуживает вопрос, а как именно Nx анализирует изменения? Вероятно, руки дойдут, и статья будет. Либо можно самостоятельно ознакомиться с source файлами.

Отлично, теперь прогон тестов занимает не так много времени. Но что может нам подпортить производительность?

Циклические зависимости

Котик запутался в твоих циклических зависимостях.Нужно выручать.
Котик запутался в твоих циклических зависимостях.
Нужно выручать.

Пока в монорепозитории царят циклические зависимости, пользы от nx affected становится с каждым разом все меньше. Настало время рефакторинга!

Чтобы было удобнее отслеживать циклические зависимости, используем следующее правило линтера, которое подсветит все проблемные места: @nrwl/nx/enforce-module-boundaries.
Внимательно изучи документацию, правило имеет разные способы конфигурации.

Супер. Теперь мы запускаем таски только на нужных проектах и не имеем циклических зависимостей. А есть какой-нибудь кэш? 

Cache

Nx cache
Nx cache

Конечно! Nx computation cache.
Здесь все просто: nx перед запуском таски вычисляет хеш. Если такой уже существует в кэше, то повторно выполняться таска не будет, результат возьмем из кэша. Список кэшируемых тасок может настраиваться.

Что учитывается при построении хеша:

  • source файлы и зависимости

  • конфигурация

  • версии внешних зависимостей

  • флаги cli

  • версия ноды (и прочие райнтайм значения)

Можем ли мы как-то кастомизировать это? Да, без проблем! Хеш для линтера, например, может учитывать только исходный код и глобальный конфиг. Подробней здесь.
А где находится кэш? Его стандартное расположение — node_modules/.cache/nx. И даже это можно изменить.

Если в твоей команде нет жадных коллег, то можно с ними обмениваться кэшем.

Remote cache

Remote cache
Remote cache

Для этого нужно либо реализовать собственный кэш, либо воспользоваться готовым решением от Nx — Nx Cloud.

При выполнении таски сперва проверяется наличие хеша в локальном хранилище, а если использовать Nx cloud, то поиск выполняется и в удаленном кэше. То есть запускаешь тесты у себя, а твои коллеги могут воспользоваться этим результатом без затрат времени на повторное выполнение.

Перейдем к пункту со звездочкой?

Buildable libs

Как мы знаем, в качестве target для библиотеки может быть lint или test. Отдельно взять и сбилдить библиотеку просто так мы не можем, только с помощью билда приложения, которое ее использует.

Обычно это происходит так: при выполнении build/serve приложения, webpack отдельно билдит зависимости и кладет их к бандлу приложения. 

Для небольших проектов это еще работает, но с увеличением кодовой базы продолжительность build/serve будет только увеличиваться, здесь нам помогут buildable libs.

С помощью опции buildable: nx g @nx/angular:lib Lib1 --buildable мы даем возможность Lib1 быть так сказать buildable и билдиться отдельно от приложения. Данная опция поддерживается не только для Angular, но и для React, NestJs, Node.

Теперь при повторном выполнении build/serve не придется повторно билдить Lib1, так как Lib1 прекомпилируется и в случае наличия кэша берется из него.

Заключение

Хочу поблагодарить тебя, читатель, что уделил время и дочитал статью до конца.

Мы рассмотрели некоторые возможности и механизмы Nx, которые могли быть пропущены при знакомстве с ним. Имея в своем арсенале хотя бы такое минимальное представление об этом, уже можно наметить пути ускорения работы с Nx у себя в монорепе. 

Возможно, следующие статьи будут посвящены более детальному обзору, если будет потребность. А пока расскажите, удалось узнать что-то новое из статьи?

Комментарии (0)