В продолжении к предыдущей статьи, погружение в nx. Если не знакомы с nx, рекомендую сначала прочитать ее.
Практический урок по написанию собственного плагина. Реализуем generator и executor. generator — при запуске которого у нас будет обновлять корневой файл в проекте projects.json, в которой будем вносить все существующие приложения.
Получившийся результат (проект) можно посмотреть здесь.
А executor будет брать этот файл, читать и выводить в консоль содержимое этого файла.
Пример достаточно простой и не является рекомендацией к его применению на реальных проектах, но дает понять как работают generator и executor.
А в конце статьи расскажу про реальные кейсы.
Поговорим о generator
generator чаще всего вызывается через терминал. Создание собственного generator позволяет упростить повторные операции создания файлов и(-или) папок, как в корне монорепы, так и создавать приложения и библиотеки в ней. Также можно вносить изменения уже в существующие файлы и папки. На сайте nx.dev проекта также можете ознакомится с простым примером создания generator. А мы в свою очередь реализуем собственный (листайте ниже).
Например, у плагина @nx/react есть generator создания библиотеки:
nx g @nx/react:lib my-new-lib
или например для нового микрофронта:
nx g @nx/react:app my-new-app
или создания нового реакт компонента
nx g @nx/react:component my-new-component --project=my-new-app
Можно в принципе зайти в исходники его и взять какие-нибудь решения для своего решения.
Поговорим о executor
Позволяет производить различные манипуляции с приложением, библиотекой. Например: запускать тесты, проверка качества кода, сборка, запуск в режиме разработки и т.д. Под свои нужды можно создать что угодно. Чуть больше информации то, для чего нужен executor и как с ним работать тут. Как и для generator, для executor на сайте проекта также есть пример.

Главное, что executor необходимо указывать в файле project.json приложения или библиотеки. Созданный executor помещается в свойство executor, в options передаются параметры, которые принимает executor. Названия, по которым потом мы будем взаимодействовать с приложением или библиотекой, указываются в targets, например: build, test и т.д.
Итак, приступим к практике.
За основу возьмем шаблон приложения для react.
Подготовка проекта
Начнем с создания приложения:
npx create-nx-workspace@latest myorg
-
Выбираем react
-
На вопрос про framework, отвечаем N.
-
Затем выбираем integrated monorepo.
-
Application name оставляем тот же.
-
Сборщик выбираем webpack.
-
По стилизации оставляем css.
-
На вопрос «Enable distributed caching to make your CI faster» отвечаем N, т.к. не планируем использовать распределенных кэш разработчиков nx.
Микрофронтенды (приложения) будут находится в папке apps, а библиотеки в libs.
После выбора необходимо подождать пару минут, чтобы зависимости установились и проект настроился для первоначальной загрузки.
Если у вас не установлен nx глобально, то можем это сделать так:
npm install -g nx
Запустить приложение в режиме разработке можно так nx serve myorg
Где myorg — название проекта в папке apps, а serve это target, который указан в файле project.json (apps/myorg)
Более детально почитать по тому, что можно делать в текущем окружении можно тут. Там описано как добавлять к монорепе еще микрофронты и библиотеки.
Для начала создадим новый микрофронтенд:
nx g @nx/react:app geek
После выполнения команды, в папке apps появится еще приложение(микрофронтенд) — geek и папка для end-to-end тестов. В итоге, в папке apps у нас два приложения.
А также создадим библиотеку:
nx g @nx/react:lib list
На все вопросы отвечаем нет.
В файле tsconfig.base.json можно заметить, что добавилась строка @myorgg/list": ["libs/list/src/index.ts"], ее добавляет generator плагина react (@nx/react). Добавляется для того, чтобы мы могли в коде использовать импорт библиотеки через публичный интерфейс (public api).
Теперь можно приступить к разработке плагина.
Для того чтобы воспользоваться generator плагина, необходимо установить зависимосить:
npm install -D @nx/plugin@latest
Создадим свой плагин:
nx g @nx/plugin:plugin my-plugin
Разработка собственного generator
nx generate @nx/plugin:generator my-generator --project=my-plugin
После выполнения команды в папке libs/my-plugin/src появится папка generator.
Перейдем в нее и найдем generator/my-generator.
В файле schema.json указываются правила к полям, которые будут проверятся при запуске executor или generator.
Непосредственно nx запускает файл generator.ts.
У generator также может быть папка files, эти файлы будут копироваться в место назначения.
Обновим файл schema.json:
{ "$schema": "http://json-schema.org/schema", "$id": "MyGenerator", "title": "", "type": "object", "properties": {} }
Файл schema.d.ts можем удалить, т.к. у нас нет входных параметров. Также удалим файл generator.spec.ts, нас не интересуют тесты в данной статье. Папку files тоже удалим, т.к. мы ничего не копируем, за исключением создания файла apps.json в корне проекта.
Откроем файл generator.ts и заменим содержимое на это:
import { formatFiles, writeJson, Tree, getProjects } from '@nx/devkit'; export async function myGeneratorGenerator( tree: Tree ) { const projects = []; for (const project of getProjects(tree)) { if (project[1].projectType === 'application') projects.push(project[0]); } writeJson(tree, 'apps.json', projects); await formatFiles(tree); } export default myGeneratorGenerator;
Проверить работу generator можем так:
nx generate @myorg/my-plugin:my-generator
Теперь в корне репозитория создается файл apps.json с таким содержимым:
[«geek», «geek-e2e», «myorg», «myorg-e2e»]. В условие можно еще указать, чтобы имена проектов -e2e не попадали в файл.
Разработка собственного executor
nx generate @nx/plugin:executor my-executor --project=my-plugin
Обновим файл project.json библиотеки libs/list:
{ "name": "list", "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "libs/list/src", "projectType": "library", "tags": [], "targets": { "lint": { "executor": "@nx/linter:eslint", "outputs": ["{options.outputFile}"], "options": { "lintFilePatterns": ["libs/list/**/*.{ts,tsx,js,jsx}"] } }, "readApps": { "executor": "@myorg/my-plugin:my-executor" } } }
Мы добавили строку generate: {executor: @myorg/my-executor»}, это значит, что мы можем теперь запустить данный executor так:
nx readApps list
Где generate это имя targets, а list — имя проекта. Например мы не сможем такую же команду использовать для приложения geek или myorg, необходимо явно указать в targets файла project.json.
Внес правки в файл executor.ts
import { readJsonFile } from '@nx/devkit'; import { MyExecutorExecutorSchema } from './schema'; export default async function runExecutor(options: MyExecutorExecutorSchema) { console.log(readJsonFile('apps.json')); return { success: true, }; }
sucess необходимо возвращать, чтобы дать понять nx, что успешно или не успешно выполнилась команда.
После выполнения команды выведется в консоль список приложений из файла apps.json.
Выводы
В примерах выше я постарался как можно проще написать инструкции по взаимодействую с собственным плагином. nx/devkit позволяет использовать граф зависимостей tree в generator, и извлекать полезные данные, не реализую самостоятельно обход по директориям и чтения файлов проектов. А executor в свою очередь кроме options, вторым аргументом принимает executorContext, который содержит множество полезных свойств.
nx это всего лишь инструмент для того, чтобы определенные «сценарии» в проекте упростить, стандартизировать. Но это не значит что вам данный инструмент подойдет. Прежде чем его внедрять, необходимо понимание, что ваших компетенций достаточно чтобы адаптировать этот инструмент для вашего проекта, а также ВРЕМЯ.
Так как будут трудности на начальном этапе при его изучении, так и определенные вызовы, которые вам придется решать. Мне пришлось ни один раз прочитать документацию, чтобы полностью понять основные особенности этого инструмента и то, как следует его применять. А еще полазить по исходникам с отладкой в консоли.
Если возникнут вопросы или трудности, пишите в комментарии, постараюсь ответить.
Реальные кейсы
Начну с того, что у нас проект уже существовал. И необходимо было переехать с минимальными трудностями для разработчиков, чтобы они не заметили переезда. А в конечном итоге упростить их работу. Этих целей удалось достичь.
Можно было ограничиться переездом на yarn workspaces, но тогда нам пришлось писать множество инструментов для работы с пространством, проверки качество кода и т.д.
Поэтому решили пойти по пути меньшего сопротивления и большей эффективности в нашем случае.
Но на yarn мы все равно перешли, но только в роли пакетного менеджера. Но учитываем, что pnp у yarn по умолчанию, необходимо создать файл .yarnrc в проекте и указать nodeLinker: node-modules, чтобы nx взаимодействовал с пакетами корректно.
Переход на yarn увеличил скорость установки пакетов в разы. Раньше разработчик ждал по 10-15 минут при первоначальной установки пакетов. Теперь это может занимать 1-2 минуты.
Вообще чем nx понравился, так это графом зависимостей, что можно упростить себе жизнь при развертывании приложений. Допустим, у нас изменилась библиотека, а эта библиотека используется в нескольких микрофронтах. Благодаря команде nx affected, у нас есть возможность запустить множественную сборку микрофронтов, которые зависят от данной библиотеки.
А теперь о самом плагине в проекте:
-
реализовали generator для микрофронтедов, библиотек и для создания динамического файла env проекта (берутся переменные окружения и подставляются в результирующий файл, который в свою очередь загружается клиентом и используется микрофронтами).
-
реализовали executor для webpack. Стандартное @nx/webpack решение не подошло по двум причинам:
-
часть плагинов и настройки жестко забиты и их нельзя убрать без костылей, обязательно требует чтобы некоторые файлы в проекте присутствовали. У нас например микрофронты лишены своего файла webpack.config.js, executor использует один общий и подставляет определенные настройки под определенные сценарии.
-
хотелось бы иметь один общий файл для микрофронтов, где будет хранится название микрофронта, занимаемый его порт и название окружения. Это позволяет создавать новые микрофронты с произвольными портами проще, а разработчику сразу понять на каком порту у него будет определенный микрофронтенд и какие окружения он поддерживает.
-
ссылка на оригинал статьи https://habr.com/ru/articles/752760/
Добавить комментарий