Nx — это инструмент для работы с фронтендом, который позволяет упростить совместную работу над проектом нескольким командам.
Nx позволяет генерировать код, автоматизировать процессы сборки, тестирования, а также позволяет управлять зависимостями эффективно. Ко всему прочему, nx имеет множество плагинов, которые позволяют использовать в проекте популярные фреймворки из коробки: Angular, React, Vue.js.
За счет модульности кода, которую предлагает nx, улучшается переиспользуемость кода, а также позволяет разным микрофронтам переиспользовать повторяющийся код.
Типы проектов nx:
-
Package-Based (основанный на пакетах): каждый проект в рабочей области является отдельным независимым пакетом. Каждый пакет имеет свой собственный набор зависимостей, скрипты. Публикуются они как отдельных пакеты.
-
Integrated (интегрированный): несколько проектов объединяются в одну рабочую область и взаимодействуют друг с другом. Проекты в рабочей области могут зависеть друг от друга, обмениваться кодом, ресурсами и настройками, их зависимости управляются централизованно, в package.json файле всего репозитория.
-
Standalone (изолированный): это проект, который может функционировать и использоваться независимо от других проектов или компонентов в системе. Такие проекты чаще всего создаются для того чтобы попробовать инструмент nx, а микрофронтендов нет. У нас только есть одно приложение. Следую рекомендациям nx при разработки приложений, у нас есть возможность в бущущем перейти к integrated проекту.
При создании новых библиотек, nx создает алиасы путей paths в файл tsconfig.base.json, это позволяет использовать абсолютные импорты. Также переопределять baseUrl в проектах не получится, т.к. алиасы будут не корректные. Потому что они относительно корня репозитория создаются. Следую подходу создания алиасов в tsconfig.base.json, у нас будет корректно отрабатывать в webstorm автоматический импорт. А также eslint @nx/enforce-module-boundaries. Если использовать npm workspaces, то возникнут проблемы.
Файл nx.json
Одни проекты могут зависеть от других проектов, где изменения в одном проекте влиет на другие проекты. Например, Стандартный Tasks Runner nx, позволяет кэшировать определенные операции в проектах.
Указывается он в файле nx.json в корне репозитория:
{ ... "tasksRunnerOptions": { "default": { "runner": "nx/tasks-runners/default", "options": { "cacheableOperations": ["build", "test", "storybook"], "parallel": 5 } } } ... }
Благодаря графу зависимостей у нас проекты (А), которые зависят от других(Б), являются affected проектами. Например если в проектах Б обновился кэш, то в проектах А будут выполнены операции повторно.
Настраивается это поведение глобально через файл nx.json:
{ ... "namedInputs": { "default": ["{projectRoot}/**/*"], "production": [ "default", "!{projectRoot}/.eslintrc.json", "!{projectRoot}/.stylelintrc.json", "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", "!{projectRoot}/tsconfig.spec.json", "!{projectRoot}/jest.config.[jt]s", "!{projectRoot}/storybook/**/*", "!{projectRoot}/**/*.md", "!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)" ] }, "targetDefaults": { "build": { "inputs": ["default", "^production"] }, "stylelint": { "inputs": [ "{projectRoot}/**/*.styles.ts", "{workspaceRoot}/.stylelintrc.json", "{workspaceRoot}/.stylelintignore", "{projectRoot}/.stylelintrc.json" ] }, "test": { "inputs": [ "default", "^production", "{workspaceRoot}/jest.preset.js", "{workspaceRoot}/jest.config.js", "{projectRoot}/jest.config.ts" ] } } ... }
projectRoot — директории проекта, из которого выполняется задача.
workspaceRoot — директория всего репозитория.
namedInputs
namedInputs — это именованные входные данные, на которые ссылается в targetDefaults.
где default это базовые входные данные.
production — данные для «target», если необходимо обновлять кэш в «урезанном» виде. В приведенном выше примере у нас исключаются файлы конфигов, тесты и storybook файлы. Исключение из выборки указывается через восклицательный знак «!».
Названия default и production могут быть любыми, просто здесь они отражают бизнес логику больше.
targetDefaults
targetDefaults используют эти именованные «входные данные». Например, build любого проекта будет учитывать только собственные кэш и кэш зависимых проектов с входными данными production. Значок «^» как раз означает, зависимые проекты. Зависимые проекты могут быть вычислены при прямом импорте в коде, так и при указании в файле project.json проекта, свойство — implicitDependencies.
Файл project.json
project.json является файлом проекта, который подхватывает nx и учитывает в графе зависимостей. В свойство targets указываются задачи, которые можно выполнить через команды nx.
{ "name": "my-main", ... "targets": { "build": { "executor": "my-plugin:webpack", "outputs": ["{options.outputPath}"], "defaultConfiguration": "production", "options": { "outputPath": "dist/apps/my-main" }, "configurations": { "development": {}, "production": {} } }, "static": { "executor": "my-plugin:static", "defaultConfiguration": "production", "options": { "buildTarget": "my-main:build" }, "configurations": { "development": {}, "production": {} } } } ... }
configurations должны быть указаны, даже если у нас отсутствуют параметры для них, иначе будет браться defaultConfiguration, даже если явно укажем с какой конфигурацией запускаем задачу: nx build my-main -c=development
name в файле указывает на имя проекта, к которому и ссылаемся в команде выше.
«my» — является npm scope, а он предназначен, для того чтобы свои пакеты имели уникальное название, т.к. в npm registry могут быть пакеты со сходными именами.
outputs — это файлы, которые будут попадать в кэш.
executor — это исполнитель, который берется в данном примере из плагина my-plugin. После двоеточия идет название executor.
Рабочее пространство nx
Ключевое понятие в монорепозитории — рабочее пространство (workspace), он объединяет множество проектов. Это пространство обеспечивает единое управление зависимостями, общие настройки и инструменты. Что в конечном итоге позволяет разным командам микрофронтов иметь общее окружение. В принципе, все плюсы и минусы, которые относятся к монорепозиторию, относятся и к workspace, просто это термин которым называют «пространство», в котором находится наше приложение.
-
nx console — расширения по интерактивному запуску различных команд для VSCode, IntelliJ, VIM
-
nx cloud — в основном распределенный кэш, а также распределенный запуск задач по разным «машинам». Позволяет ускорить выполнение задач на устройстве разработчика, а также ускорить процессы CI/CD за счет использования кэша.
-
plugins — различные плагины, которые позволяют упростить работу с монорепозиторием, добавить дополнительную логику в окружение приложения. Плагины могут содержать в свою очередь:
-
generators — позволяют создавать что-то или менять в проекте. Например, новое приложение, библиотеку в проекте. Позволяет упростить создание нового микрофронта используя заготовленный шаблон.
-
executors — «исполняемые» скрипты, которые используются в библиотеках, приложениях, для запуска определенной задачи в приложении. Например, сборка, тестирование, проверка качество кода, запуск микрофронта в режиме разработки.
-
code migrations — скрипты для автоматизации миграции с одной версии библиотеки на новую. Также миграции могут изменять исходных код приложения, чтобы при обновлении библиотеки, приложение не поломалось.
-
-
devkit (@nx/devkit) — пакет «разработчика» для работы с nx, полезен для написания собственных плагинов.
-
nx — сама библиотека nx, которая предоставляет следующие возможности:
-
Task Running — выполнение команд (задач). Приложение или библиотека могут запускать разные команды, как напрямую, так и используя executors.
-
Distribution — связан с nx cloud, позволяет выполнять удаленные команды.
-
Workspace analysis — в проекте nx формирует граф завизимостей, для более быстрого запуска команд и для анализа сброса кэша. Позволяет визуализировать зависимости проекта через команду
nx graphВедь если микрофронтенды используют собственную библиотеку в монорепе, мы хотим иметь актуальную версию микрофронтов, а не доставать из кэша. Т.к. кэш уже не актуальный, из-за того, что зависимость, в лице библиотеки, поменялась.
-
Caching — локальное кэширование executors, кэшируется результат вывода в консоль и выходные файлы.
-
Code generation — включает в себя как plugins generators, так и update generator (который вызывается после обновления nx).
Через code generation мы запускает как собственные generators, которые для своих потребностей «напишем», так и сторонних разработчиков.
-
Automatic migration — позволяет обновить «экосистему» nx. Запускается обновление пакетов, вызываются update generators, если потребуется.
-
nx позволяет запускать команды как в самом корне проекта, тогда нам необходимо указывать название приложения (библиотеки), либо в самой директории проекта без указания названия: nx build app или в директории app: nx build
При разработки собственных плагинов или для уточнения некоторых особенностей использования инструментов nx, не бойтесь ковыряться в исходниках.
Исходники полезны чтобы понять как именно можно создавать свой плагин с executors и/или generators.
CI/CD
При написании скриптов развертывания надо использовать nx affected команды, чтобы при изменении библиотек, приложения которые от них зависят: проверялись на качество кода, проходили тесты, разворачивались и т.д. Примеры ci файлов можете найти здесь.
Микрофронтенды в nx
В файле webpack.config.js мы можем вызывать задействовать ModuleFederationPlugin самого webpack через вызов ф-ции withModuleFederation, разработчики определенные настройки для этого плагина делают за нас. Но данная ф-ция только есть для react и angular. Если не устраивает их ф-ция, то можно напрямую взаимодействовать сModuleFederationPlugin в файле конфигурации.
Также разработчики nx предусмотрели обновление графа зависимостей от файла module-federation.config.js самого проекта. Полный пример можно посмотреть тут.
module.exports = { name: 'shell', remotes: ['shop', 'cart', 'about'], };
Например эта команды запускает в режиме разработки shell, а также cart и shop. Если не указывать devRemotes, то остальные микрофронты будут отдаваться статикой, т.е. на изменения их кода запущенные микрофронты не будут реагировать, за исключением shell.
Shell в данном случае выступает как host приложения для shop, cart, about.
nx serve shell --devRemotes=cart,shop
Так работает у нас статичный запуск module federation.
Есть и динамический запуск, тогда remotes у host приложения должны быть пустыми. Делается это по разным причинам, одна из них это динамический адрес микрофронта, тогда придется в webpack.config файле описывать логику загрузки модуля. Другая причина — подгружать микрофронтенды по требованию (т.е. на запускать сразу все приложения при старте приложения и загружать все модули сразу). В данном случае можно отказаться от использования ф-ции withModuleFederation, а обращаться напрямую к ModuleFederationPlugin.
Но с другой стороны, сами разработчики nx утверждают, что лучше remotes оставлять «пустым», т.к. это убыстряет развертывание приложений.
Переменные окружения (env)
Работа с переменными окружения такая же как и в любом webpack приложении.
Для загрузки переменных окружения используются два подхода:
Использовать файлы в папке environments
Для каждой среды создаем свой файл кофигурации, например environment.prod.ts. environment.ts. Соответственно файл для продуктового и тестового стендов. Замена файла при необходимости будет осуществляться в момент сборки / разработки.
Логика замены описывается в файле package.json проекта:
"fileReplacements": [ { "replace": "apps/products/src/environments/environment.ts", "with": "apps/products/src/environments/environment.prod.ts" } ]
Для данного способа использования в коде приложения переменных окружения будет простым — простой импорт файла environment.ts
Использование файлов .env, либо загрузка из переменных окружения ОС.
Для загрузки и использования в приложении необходимо использовать плагин webpack.DefinePlugin.
Рекомендации по разработке приложений
Микрофронтенды лучше всего делить по бизнес ценностями, по независимым частям, которые можно отдать разным командам для независимой разработки. Иначе мы скатимся к сотнями и тысячам микрофронтам, если мы будем помещать каждый компонент как отдельное приложение.
Если у нас есть общая логика микрофронтендов, мы помещаем их в библиотеку. Чтобы переиспользовать безболезненно между микрофронтендами.
Создание микрофронтенд приложений должно исходить от бизнеса, но при содействии разработчиков.
Согласно правилу команды nx: 20% кода находится в приложениях, остальные 80% в библиотеках. Это может вызвать определенную трудность к перестройству к подобному подходу, но это позволяет нам переиспользовать код и уменьшать связанной внутри того приложения, где используется этот код, плюс упрощает покрытие тестами. А так как у нас множество маленьких библиотек, у нас эффективность кэширования повышается и соответственно скорость сборки за счет того, что у нас собираются маленькие проекты, а не одно большое.
Чтобы уменьшить размер приложения желательно использовать только одну библиотеку по работе с фронтендом: angular или react или что-то еще. Хотя микрофронтенды это позволяют, но это считается плохой практикой в монорепе.
В следующей статье разберу как создавать свой плагин, executor и generator.
Если есть дополнения или исправления к данной статье, готов обсуждать.
ссылка на оригинал статьи https://habr.com/ru/articles/742440/
Добавить комментарий