Как стать автором
Обновить

Разработка собственного плагина для nx (executor и generator)

Уровень сложностиСредний
Время на прочтение7 мин
Количество просмотров1.6K

В продолжении к предыдущей статьи, погружение в 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, у нас есть возможность запустить множественную сборку микрофронтов, которые зависят от данной библиотеки.

А теперь о самом плагине в проекте:

  1. реализовали generator для микрофронтедов, библиотек и для создания динамического файла env проекта (берутся переменные окружения и подставляются в результирующий файл, который в свою очередь загружается клиентом и используется микрофронтами).

  2. реализовали executor для webpack. Стандартное @nx/webpack решение не подошло по двум причинам:

    1. часть плагинов и настройки жестко забиты и их нельзя убрать без костылей, обязательно требует чтобы некоторые файлы в проекте присутствовали. У нас например микрофронты лишены своего файла webpack.config.js, executor использует один общий и подставляет определенные настройки под определенные сценарии.

    2. хотелось бы иметь один общий файл для микрофронтов, где будет хранится название микрофронта, занимаемый его порт и название окружения. Это позволяет создавать новые микрофронты с произвольными портами проще, а разработчику сразу понять на каком порту у него будет определенный микрофронтенд и какие окружения он поддерживает.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Планируете ли использовать пользовательские плагины в nx?
83.33% Уже использовал5
16.67% Планирую1
0% Хватает того, что есть0
Проголосовали 6 пользователей. Воздержались 2 пользователя.
Теги:
Хабы:
Рейтинг0
Комментарии2

Публикации

Ближайшие события