Если вы делаете Ctrl+C
каждый раз при создании нового компонента в реакте, то эта статья точно для вас!
У реакта нет своего CLI, и понятно почему. Не существует определенных правил, как именно должна выглядеть структура компонента, есть только общие рекомендации в документации. Все разработчики используют структуру, которая прижилась в их команде. А иногда и вовсе приходится поддерживать проекты в разных стилях.
Сама структура также зависит и от используемого стека:
- Стили — styled, scss modules, css;
- TypeScript или JavaScript;
- Тесты
Существует несколько способов облегчить себе жизнь при создании новых компонентов. Например, можно создать шаблоны в вашей среде разработки (например в WebStorm). Но сегодня мы рассмотрим как создавать полную структуру компонента из командной строки. В конце статьи мы сможем создавать компоненты при помощи одной команды. Например, такой как:
npm run create components/Home/ComponentName
Подготовка
Для создания проекта будем использовать Create React App
Создаем проект:
npx create-react-app react-cli
Весь наш код будет хранится в одном файле. Создаем в папку cli в корне нашего проекта, а внутри нее файл create.js.
Для работы нам понадобятся 3 модуля, импортируем их в наш файл.
// cli/create.js
const fs = require('fs');
const path = require('path');
const minimist = require('minimist');
fs — модуль для работы с файловой системой.
path — модуль для обработки путей к файлам.
minimist — модуль для преобразования аргументов из командной строки.
Работа с аргументами
Для того чтобы создать компонент нам нужно передать в командрую строку путь и имя компонента. Мы передадим эту информацию в одной строке (например components/folder1/folder2/Menu
), которую потом распарсим на путь и название.
Все аргументы можно достать из объекта process
. Допустим, мы ввели в консоль следующую строку:
node cli/create.js --path components/folder/Menu
В результате получим:
console.log(process.argv);
// [
// '/usr/local/bin/node',
// '/Users/a17105765/projects/react-cli/cli/create.js',
// '--path',
// 'components/folder/Menu'
// ]
Используя модуль minimist, мы можем преобразовать аргументы в объект:
// cli/create.js
// ...
const args = minimist(process.argv);
console.log(args);
// {
// _: [
// '/usr/local/bin/node',
// '/Users/a17105765/projects/react-cli/cli/create.js'
// ],
// path: 'components/folder/Menu'
// }
Замечательно, с этим уже можно работать.
Создание директорий
Для начала подготовим необходимые переменные. Нам нужен полный путь до папки src нашего проекта, путь из аргументов в виде массива и название компонента.
// cli/create.js
// ...
// достаем путь до папки src текущего проекта
const srcPath = [__dirname, '..', 'src'];
// разбиваем путь из аргумента командной строки на массив
const arrPath = args.path.split('/');
// достаем последний элемент массива (название компонента)
const componentName = arrPath[arrPath.length - 1];
Допустим, мы указали несуществующий путь. По-хорошему, мы должны создать все эти вложенные папки, если их нет. Так и сделаем.
// cli/create.js
// ...
// создание директорий из аргумента (при необходимости)
const currentArray = [];
arrPath.forEach(element => {
currentArray.push(element);
const currentResolvePath = path.resolve(...srcPath, ...currentArray);
if (!fs.existsSync(currentResolvePath)) { // проверка - существует такая директория или нет?
fs.mkdirSync(currentResolvePath); // если нет, то создаем новую
}
});
Здесь мы циклом проходимся по всем элементам пути и при необходимости создаем директорию с помощью метода mkdirSync
. До этого нормализуем путь к компоненту в одну строку с помощью метода resolve
. После выполнения данных операций у нас будет создана необходимая структура директорий.
Протестируем написанное. Вводим в командную строку следующую команду (при этом у нас пока нет никаких директорий в папке src
):
node cli/create.js --path components/A/B/C/D/E/CustomComponent
И мы получим следующий результат:
Создание файлов компонента
Отлично, пол дела сделано, осталось создать файлы компонента.
Мы будем использовать самую простую структуру компонента:
- Для стилей обычный css
- Без TS
- Без тестов
- Функциональный компонент
Получается, нам нужно создать 3 файла.
1. Шаблон компонента
import React from 'react';
import './CustomComponent.css';
const CustomComponent = () => {
return (
<div className="wrapper">
</div>
);
};
export default CustomComponent;
2. Шаблон индексного файла
export { default } from './CustomComponent';
3. Шаблон файла стилей
.wrapper {}
Для начала достанем в одну переменную полный путь до компонента (включая личную папку компонента):
// cli/create.js
// ...
const componentPath = [...srcPath, ...arrPath];
Новые файлы создаются при помощи команды writeFileSync
, которая принимает путь до файла и содержимое.
Создание файла компонента:
// cli/create.js
// ...
const componentCode = `import React from 'react';
import './${componentName}.css';
const ${componentName} = () => {
return (
<div className="wrapper">
</div>
);
};
export default ${componentName};`;
fs.writeFileSync(path.resolve(...componentPath, `${componentName}.jsx`), componentCode);
Создание индексного файла:
// cli/create.js
// ...
const indexCode = `export { default } from './${componentName}';`;
fs.writeFileSync(path.resolve(...componentPath, 'index.js'), indexCode);
Создание файла стилей:
// cli/create.js
// ...
const styleCode = '.wrapper {}';
fs.writeFileSync(path.resolve(...componentPath, `${componentName}.css`), styleCode);
Готово!
Теперь посмотрим что у нас получилось.
// cli/create.js
const fs = require('fs'); // модуль для работы с файловой системой
const path = require('path'); // модуль для преобразования пути
const minimist = require('minimist'); // модуль для преобразования строки аргументов в объект
const args = minimist(process.argv);
const srcPath = [__dirname, '..', 'src']; // путь до папки src текущего проекта
const arrPath = args.path.split('/'); // разбиваем путь из аргумента командной строки на массив
const componentName = arrPath[arrPath.length - 1]; // последний элемент - название компонента
// создание директорий из аргумента (при необходимости)
const currentArray = [];
arrPath.forEach(element => {
currentArray.push(element);
const currentResolvePath = path.resolve(...srcPath, ...currentArray);
if (!fs.existsSync(currentResolvePath)) { // проверка - существует такая директория или нет?
fs.mkdirSync(currentResolvePath); // если нет, то создаем новую
}
});
const componentPath = [...srcPath, ...arrPath];
// создание компонента
const componentCode = `import React from 'react';
import './${componentName}.css';
const ${componentName} = () => {
return (
<div className="wrapper">
</div>
);
};
export default ${componentName};`;
fs.writeFileSync(path.resolve(...componentPath, `${componentName}.jsx`), componentCode);
// создание индексного файла
const indexCode = `export { default } from './${componentName}';`;
fs.writeFileSync(path.resolve(...componentPath, 'index.js'), indexCode);
// создание файла стилей
const styleCode = '.wrapper {}';
fs.writeFileSync(path.resolve(...componentPath, `${componentName}.css`), styleCode);
Получилось всего 43 строки с учетом комментариев, неплохо для такой полезной штуки!
Теперь попробуем создать компонент:
node cli/create.js --path components/folder1/folder2/Button
Все получилось! Остался последний штрих...
Добавление команды в package.json
Добавим команду в файл package.json, чтобы каждый раз не писать путь к скрипту
{
"name": "react-cli",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-scripts": "3.2.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"create": "node cli/create.js --path"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Теперь вместо:
node cli/create.js --path components/folder1/folder2/Button
можем просто написать
npm run create components/folder1/folder2/Button
Исходный код проекта можно посмотреть на гитхабе
Автор: vital_pavlenko