В этом посте присоединяйтесь ко мне, поскольку я создаю генератор для компонента React. Мы будем использовать действительно классный инструмент под названием Plop, и в итоге мы сможем создавать новые компоненты быстрее и лучше.
Введение
Если вы цените хороший опыт разработчиков (DX) и стремитесь поддерживать хорошее качество/стандарты кодирования, то наличие инструмента для генерации кода в виде лесов компонентов просто необходимо. Независимо от того, поддерживаете ли вы библиотеку компонентов в своей организации, такой инструмент позволит вам и вашим коллегам увеличить скорость разработки и сосредоточиться на действительно важных аспектах разработки компонентов.
Требования
Итак, чего же мы хотим добиться?
Ну, наш генератор должен создать следующее:
- Папка Component с именем, заданным разработчиком
- Файл index.jsx для компонента
- Файл index.css, который файл index.jsx будет импортировать с основными общими правилами
- Файл index.test.js с одним тестом, который проверяет рендеринг компонента
- Файл Storybook, содержащий одну историю для компонента.
Это довольно много. Давайте приступим к этому
Plop
Как уже упоминалось выше, одним из инструментов, который может помочь нам сгенерировать такой код, является Plop. Я нахожу этот инструмент очень интуитивным и с довольно понятной документацией.
Следуя документации по Plop, давайте сначала установим его и убедимся, что мы можем его использовать:
yarn add plop -D
Это, конечно, зависимость от dev.
Давайте создадим наш файл plopfile.js
прямо из примера, используя формат CJS, поскольку проект, для которого я это делаю, в настоящее время не поддерживает формат ESM:
module.exports = function (plop) {
// create your generators here
plop.setGenerator('basics', {
description: 'this is a skeleton plopfile',
prompts: [], // array of inquirer prompts
actions: [], // array of actions
});
};
Прежде чем мы зальем больше контента в этот генератор, давайте проверим, запускается ли Plop как ожидалось, добавив npm-скрипт в наш package.json
и вызвав его:
"scripts": {
. . .
"plop": "plop"
},
Запускаем yarn plop
и… ничего не происходит. Нам нужно ввести генератор. Я возвращаюсь к своему plopfile.js
и добавляю это:
plop.setGenerator('React component generator', {
description: 'A generator for React components',
prompts: [
{
type: 'input',
name: 'name',
message: 'Component name',
},
],
});
Запускаю plop снова и получаю приятную подсказку с запросом имени компонента. Я дал ему имя “Matti”, но получил эту ошибку:
True. Давайте добавим действие.
В нашем действии я хотел бы создать директорию по имени заданного компонента и поместить в нее пустой файл index.jsx (пока). Теперь мой генератор выглядит следующим образом:
plop.setGenerator('React component generator', {
description: 'A generator for React components',
prompts: [
{
type: 'input',
name: 'name',
message: 'Component name',
},
],
actions: [
{
type: 'add',
path: 'src/{{name}}/index.jsx',
},
],
});
Очень интуитивно понятно IMO.
Итак, теперь у меня есть файл index.jsx, расположенный в каталоге Matti
, который находится в каталоге src
. Отлично.
Давайте заполним содержимое этого файла. Для этого мы будем использовать шаблон Handlebars (да, .hbs файл, вы не ослышались), который позволит нам создать содержимое в соответствии с именем компонента, которое мы задали ранее – …
Я создаю plop-templates/react-component/index.hbs
в корне проекта и помещаю туда минимальный код, необходимый для создания React-компонента:
import React from 'react';
import PropTypes from 'prop-types';
const MyComponent = (props)=> {
return <div>MyComponent {props.sampleProp}</div>
}
MyComponent.propTypes = {
sampleProp: PropTypes.number,
};
export default MyComponent;
Обратите внимание, что я пока не использую никаких динамических полей в этом шаблоне.
Теперь я скажу ранее сделанному действию использовать этот шаблон при создании компонента:
actions: [
{
type: 'add',
path: 'src/{{name}}/index.jsx',
templateFile: 'plop-templates/react-component/index.hbs',
},
],
Давайте запустим Plop и посмотрим, что мы получим.
Да, файл создается с ожидаемым содержимым (BTW, если файл уже существует, он предупредит об этом и не будет его переопределять).
Пришло время использовать данное имя компонента внутри шаблона:
import React from 'react';
import PropTypes from 'prop-types';
const {{name}} = (props)=> {
return <div>{{name}} {props.sampleProp}</div>
}
{{name}}.propTypes = {
sampleProp: PropTypes.number,
};
export default {{name}};
Намного лучше! После генерации компонента теперь файл index.jsx
установлен правильно.
Быстро проверяем, что мы имеем на данный момент – папку Component с именем, заданным разработчиком, и файл index.jsx для компонента. Отлично, переходим к созданию файла index.css
для этого компонента.
Я бы хотел, чтобы файл index.css
содержал имя класса в соответствии с именем данного компонента, но в нижнем регистре, и для этого было бы неплохо ввести вспомогательную функцию, которую Handlebars сможет использовать.
В plopfile.js
я добавляю следующее:
// Helpers
plop.setHelper('toLowerCase', function (text) {
return text.toLowerCase();
});
Теперь я создам шаблон index.css
в plop-templates/react-component/index.css.hbs
со следующим содержанием:
.{{toLowerCase name}} {
color: aqua;
}
Имея шаблон .css
, я хотел бы, чтобы plop action генерировал его вместе с файлом index.jsx
. Как мне это сделать?
Для этого нам потребуется использовать другой api Plop под названием “addMany” и немного изменить имена шаблона index.jsx
. Давайте начнем с изменения имени шаблона, и вы скоро поймете почему –
index.hbs
переименован в index.jsx.hbs
.
Переходя к plopfile, давайте используем действие “addMany” следующим образом:
actions: [
{
type: 'addMany',
destination: 'src/{{name}}',
base: `plop-templates/react-component/`,
templateFiles: 'plop-templates/react-component/*',
},
],
Вам может быть интересно, что здесь происходит (и в документации это не совсем ясно).
Пункт назначения” – это место, где мы хотим сгенерировать все наши файлы.
“templateFiles” (обратите внимание на маленькую “s” в конце) – это место, где находятся все шаблоны для данного действия. Там хорошо было бы создать директорию для каждого генератора.
Base” – это часть, которую мы хотели бы удалить из имен конечных файлов.
Plop знает, что нужно удалить “.hbs” и “base” из конечного имени файла, и именно поэтому мы изменили имя индексного файла.
Давайте сделаем небольшие изменения в шаблоне index.js.hbs
, чтобы наш компонент импортировал и использовал сгенерированный для него index.css
:
import React from 'react';
import PropTypes from 'prop-types';
import './index.css';
const {{name}} = (props)=> {
return <div className="{{toLowerCase name}}">{{name}} {props.sampleProp}</div>
}
{{name}}.propTypes = {
sampleProp: PropTypes.number,
};
export default {{name}};
Потрясающе! У нас есть сгенерированный css файл, который компонент импортирует и использует. Переходим к тестовому файлу.
Мы хотим создать тестовый файл Jest, который будет тестировать минимальный рендеринг компонента.
Начнем с создания шаблона index.test.hbs
в том же месте, где и остальные шаблоны:
import React from 'react';
import {render, screen} from '@testing-library/react';
import {{name}} from '.';
describe('{{name}} component', () => {
it('should render', () => {
const mockSampleProp = 5;
const textQuery = `{{name}} ${mockSampleProp}`
render(<{{name}} sampleProp={mockSampleProp}/>);
expect(screen.getByText(textQuery)).toBeInTheDocument();
});
});
Нам не нужно ничего менять в нашем plopfile. Этот шаблон будет обнаружен, и будет сгенерирован тестовый файл.
Запускаем Jest, чтобы убедиться, что все прошло, и действительно – у нас есть успех 🙂
Переходим к истории Storybook, ну… вы поняли ее суть. Я создаю шаблон под названием index.stories.jsx.hbs
со следующим содержанием:
import React from 'react';
import {{name}} from './index.jsx';
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
title: 'Components/{{name}}',
component: {{name}},
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
argTypes: {},
};
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template = (args) => <div><{{name}} {...args} /></div>;
export const Simple = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Simple.args = {
sampleProp:5,
};
Запускаю plop снова и файл истории создается и отображается на Story book –
Какой замечательный компонент!
И это, друзья мои, все 🙂
Подведем итоги…
Теперь у нас есть генератор компонентов React, который создает для нас 4 файла с заданным именем компонента. Теперь любой разработчик может создать компонент и начать работу за считанные секунды.
Помимо ускорения процесса создания компонентов и обеспечения лучшего DX, это помогает выровнять стандарты компонентов в большой организации.
Код можно найти на моем монорепозитории Pedalboard.
Как всегда, если у вас есть вопросы или предложения о том, как это можно сделать лучше, обязательно оставьте их в комментариях ниже 🙂
Эй! Если вам понравилось то, что вы только что прочитали, проверьте @mattibarzeev на Twitter 🍻
Photo by KOBU Agency on Unsplash