Как ускорить создание компонентов с помощью Plop-генератора или автоматизация рутинных задач в React

от автора

Типичная фронтовая команда

Типичная фронтовая команда

Что имеем?

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

Однако, несмотря на наличие единой системы, мы сталкивались с рядом проблем:

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

  • Человеческий фактор. Процесс создания новых компонентов мог растягиваться из-за ошибок или несоблюдения ключевых соглашений. Это особенно остро ощущалось при расширении команды или при найме новых сотрудников, каждый из которых приносил свои привычки и методы работы.

  • Ручной процесс создания компонентов. Сборка компонентов и связанных с ними файлов (стили, тесты, Storybook) занимала больше времени, поскольку всё делалось вручную, что повышало риск допущения ошибок и снижало скорость разработки.

  • Поддержка старого кода. При работе с устаревшими сервисами, несоответствие компонентов новым стандартам вызывало трудности в поддержке и внесении изменений, что замедляло обновления и рост проекта.

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

Как было предложено решать данную проблему?

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

Основные требования к генератору, выдвинутые после обсуждения с командой:

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

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

  3. Независимость генератора. Генератор должен быть оформлен в виде независимой библиотеки, что позволит легко интегрировать его во все сервисы проекта.

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


Начинаем начинать

Что выбрать?

Конечно же сразу пошли искать готовые решения, потому что задача не настолько тривиальная и не мы первые о таком задумались. На текущий момент есть два основных популярных решения: Plop и Hygen. Сравнив две библиотеки, было решено использовать plop, так как он оказался более гибким в настройке, имеет поддержку ts, поддерживает интерактивные команды в CLI, а также имеет более развитой сообщество и подробную документацию.

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

Как использовать-то?

Для начала установим сам plop:

npm install --save-dev plop # или yarn add --dev plop 

Описание всей логики и вообще всех взаимодействий происходит в файле plopfile.js

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

module.exports = function (plop) {     // Создаем новый генератор с именем 'component'     plop.setGenerator('component', {         // Описание генератора         description: 'Создать новый компонент',                  // Промпты для ввода данных         prompts: [             {                 type: 'input',                 name: 'name',                 message: 'Введите имя компонента:',             }         ],          // Действия для создания файла на основе шаблона         actions: [             {                 type: 'add',                 // Путь до создаваемого файла                 path: 'src/components/{{pascalCase name}}/{{pascalCase name}}.tsx',                 // Указываем шаблон                 templateFile: 'plop-templates/component.hbs',             }         ],     }); }; 

Давайте разберем, что делает этот код:

  • plop.setGenerator('component', { ... }) — мы создаем новый генератор с именем component. Когда мы запустим Plop, можно будет вызвать его командой plop component.

  • prompts — это массив с вопросами, которые Plop задаст в итерактивном режиме в консоли. В данном случае мы используем один вопрос: запрос имени компонента. Формат имени будет введен пользователем, а затем автоматически преобразован в формат PascalCase.

  • actions — это действия, которые Plop выполнит после получения данных. В нашем примере это создание файла в директории src/components/{{pascalCase name}}. Путь файла будет динамически изменяться в зависимости от введенного имени компонента. Мы также используем шаблон component.hbs, который создадим дальше.

Создание шаблонов

Шаблоны мы можем описывать как в формате .hbs так и в обычном .js/.ts. В моем случае я предпочел описывать в формате .ts, так как в некоторые шаблоны мне было необходимо передавать параметры и внутри шаблона на основании параметров по условию изменять блоки. Самый простой пример, который у меня был — это в зависимости от того, выбран ли файл стилей к созданию, в шаблоне добавлять или нет строку импорта и подключения стилей (пример шаблона из нашего генератора будет ниже).

Пример простого .hbs шаблона на примере:

import React from 'react';  const {{pascalCase name}} = () => {     return (         <div>             Implement me!         </div>     ); };  export default {{pascalCase name}};

Добавляем больше возможностей и интерактива

Пример одного из шаблонов для создания React компонента из нашего генератора

export default function componentTemplate(     name: string,     withStyle: boolean = false,     withQaAttributes: boolean = false, ): string {     const importStyle = withStyle ? `\n\nimport cx from './${name}.scss';` : '';     const importWithClassName = withStyle         ? `\n\nimport {type IWithClassName} from 'types/withClassName';`         : '';     const importQaAttributes = withQaAttributes         ? `\n\nimport {     type IWithQaAttributes,     prepareQaAttributes, } from 'utilities/qaAttributes/qaAttributes';`         : '';      const interfaceExtensions = [         withStyle ? 'IWithClassName' : '',         withQaAttributes ? 'IWithQaAttributes' : '',     ]         .filter(Boolean)         .join(', ');      const classNameProp = withStyle ? 'className' : '';     const classNameUsage = withStyle         ? ` className={cx('root', className)}`         : '';     const qaAttributesUsage = withQaAttributes         ? ' {...prepareQaAttributes(props)}'         : '';      return `import {type FC, memo} from 'react';${importWithClassName}${importQaAttributes}${importStyle}  export interface I${name}Props${         interfaceExtensions ? ` extends ${interfaceExtensions}` : ''     } {     // define your props here }  const ${name}: FC<I${name}Props> = props => {     const {${classNameProp}} = props;      return (         <div${classNameUsage}${qaAttributesUsage}>             Implement me         </div>     ); };  export default memo(${name}); `; }
Ничего не понятно, но очень интересно

Ничего не понятно, но очень интересно

Одним из условий была возможность масштабировать данный функционал на несколько проектов и возможность немного «дотюнить» в рамках конкретных проектов.

Первую проблему решили тем, что обернули утилиту в npm библиотеку, опубликовали ее и используем во всех проектах где нужно. Для этого после установки, нужно создать в проекте файл plopfile.js и наполнить его следующим содержимым

const {generator} = require('@example/react-components-generator');  module.exports = function (plop) {     generator(plop); }; 

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

"create": "plop --plopfile=scripts/generator/plopfile.js --dest=$PWD"

Чтобы была возможность в конкретных проектах переопределить шаблон или дефолтный путь, мы типизируем возможные шаблоны и прокидываем в основной файл генератора те шаблоны, которые хотим переопределить. Тоже самое делаем с дефолтным путем.

interface ITemplatesTypes {     componentTemplate?: (name: string, withStyle?: boolean) => string;     storybookV6Template?: (name: string) => string;     storybookV8Template?: (name: string) => string;     styleTemplate?: () => string; }  export function generator(     plop: NodePlopAPI,     customTemplates?: ITemplatesTypes,     customBasePath?: string, ): void {     const templates = {         componentTemplate,         storybookV6Template,         storybookV8Template,         styleTemplate,         ...customTemplates,     };     const basePath = customBasePath || './src';     ...   }

Для того чтобы использовать такое переопределение, мы в целевом проекте создаем шаблоны и используем скрипт (plopfile.js) следующим образом:

import {generator} from '@example/react-components-generator'; import customComponentTemplate from './custom-templates/componentTemplate'; import customStyleTemplate from './custom-templates/styleTemplate';  module.exports = function (plop) {     generator(         plop,         {             // Переопределение дефолтных шаблонов своими             componentTemplate: customComponentTemplate,             styleTemplate: customStyleTemplate,         },         './custom/src', // Указание кастомного базового пути для компонентов     ); };

Что имеем в итоге?

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

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

На текущий момент данная библиотека успешно внедрена в тестовом режиме в основные сервисы для того, чтобы собрать обратную связь и понять, чего еще не хватает и где можно еще доработать. В планах на будущее сделать аналогичную генерацию «заглушек» не только для компонентов, но в том числе и для других файлов: утилит, хуков, тестов и тд. Также есть мысли дать возможность пользоваться библиотекой не только внутри команды, но и подключать ее в других сервисах компании (почему бы и нет?).

Скрытый текст

Пользуясь случаем, укажу свой тг канал, в котором открываю что-то новое для себя и делюсь с остальными, если интересно, то велком, как говорится ❤️

Пишите в комментариях, что думаете о такой автоматизации, делитесь своими кейсами
👇👇👇

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Используете генераторы в своей работе?

26.67% Использую4
26.67% Не использую4
46.67% Я и есть генератор7

Проголосовали 15 пользователей. Воздержались 2 пользователя.

ссылка на оригинал статьи https://habr.com/ru/articles/847050/