Что имеем?
А имеем мы довольно крупную команду фронтендеров, которая раздедлена на небольшие подгруппы, каждая из которых отвечает за свои микросервисы. В конечном итоге, эти сервисы интегрируются в единый масштабный проект. Мы придерживаемся общей дизайн-системы, стандартизированных правил, описываем все процессы и тд.
Однако, несмотря на наличие единой системы, мы сталкивались с рядом проблем:
-
Разнообразие реализации компонентов. Несмотря на стандарты, каждая команда имела собственный подход к созданию компонентов, что приводило к разнородности кода, дублированию логики и сложностям при интеграции.
-
Человеческий фактор. Процесс создания новых компонентов мог растягиваться из-за ошибок или несоблюдения ключевых соглашений. Это особенно остро ощущалось при расширении команды или при найме новых сотрудников, каждый из которых приносил свои привычки и методы работы.
-
Ручной процесс создания компонентов. Сборка компонентов и связанных с ними файлов (стили, тесты, Storybook) занимала больше времени, поскольку всё делалось вручную, что повышало риск допущения ошибок и снижало скорость разработки.
-
Поддержка старого кода. При работе с устаревшими сервисами, несоответствие компонентов новым стандартам вызывало трудности в поддержке и внесении изменений, что замедляло обновления и рост проекта.
Эти проблемы вносили значительный вклад в нарастание технического долга и замедляли процесс разработки, создавая неэффективность и затрудняя масштабирование проекта.
Как было предложено решать данную проблему?
Чтобы ускорить разработку и устранить неконсистентность в файлах, папках, структуре и содержимом компонентов между сервисами, было предложено внедрить автоматическую генерацию шаблонов компонентов и других файлов с базовым наполнением.
Основные требования к генератору, выдвинутые после обсуждения с командой:
-
Единый подход к организации компонентов. Генератор должен автоматически создавать структуру папок и файлов в соответствии с общими правилами команды. Это уменьшит вероятность ошибок и расхождений между проектами.
-
Соответствие стандартам проекта. Все компоненты, создаваемые генератором, должны строго соответствовать установленным правилам именования и стандартам проекта. Это гарантирует консистентность и облегчает поддержку кода.
-
Независимость генератора. Генератор должен быть оформлен в виде независимой библиотеки, что позволит легко интегрировать его во все сервисы проекта.
-
Гибкость и конфигурируемость. Несмотря на стандартизацию, генератор должен быть достаточно гибким для настройки под отдельные случаи, где структура компонентов может отличаться по объективным причинам. Это позволяет автоматизировать рутину даже в условиях различий между сервисами.
Начинаем начинать
Что выбрать?
Конечно же сразу пошли искать готовые решения, потому что задача не настолько тривиальная и не мы первые о таком задумались. На текущий момент есть два основных популярных решения: 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', // Указание кастомного базового пути для компонентов ); };
Что имеем в итоге?
Внедрение генератора компонентов значительно улучшило рабочие процессы команды. Автоматизация создания шаблонов компонентов позволила избавиться от ручной рутины, стандартизировать структуру файлов и повысить их согласованность между сервисами. Что позволило уменьшить количество ошибок, итерация ревью и как следствие появляется больше времени пилить фичи.
Также хочется отметить, тот факт, что новым сотрудникам стало значительно проще проходить онбординг и погружаться в процессы
На текущий момент данная библиотека успешно внедрена в тестовом режиме в основные сервисы для того, чтобы собрать обратную связь и понять, чего еще не хватает и где можно еще доработать. В планах на будущее сделать аналогичную генерацию «заглушек» не только для компонентов, но в том числе и для других файлов: утилит, хуков, тестов и тд. Также есть мысли дать возможность пользоваться библиотекой не только внутри команды, но и подключать ее в других сервисах компании (почему бы и нет?).
Скрытый текст
Пользуясь случаем, укажу свой тг канал, в котором открываю что-то новое для себя и делюсь с остальными, если интересно, то велком, как говорится ❤️
Пишите в комментариях, что думаете о такой автоматизации, делитесь своими кейсами
👇👇👇
ссылка на оригинал статьи https://habr.com/ru/articles/847050/
Добавить комментарий