Пишем собственный CLI для React

Если вы делаете Ctrl+C каждый раз при создании нового компонента в реакте, то эта статья точно для вас!

dpsyxsih4s-gddy28nlimcssbbo.jpeg

У реакта нет своего 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

И мы получим следующий результат:

7vw-rkir4ec-pon0ayw8h4qx1is.png


Создание файлов компонента

Отлично, пол дела сделано, осталось создать файлы компонента.

Мы будем использовать самую простую структуру компонента:


  • Для стилей обычный css
  • Без TS
  • Без тестов
  • Функциональный компонент

Получается, нам нужно создать 3 файла.


1. Шаблон компонента

import React from 'react';
import './CustomComponent.css';

const CustomComponent = () => {
  return (
    
); }; 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 (
    
); }; 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 (
    
); }; 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

qm9rcu2yhjnqbqhgltfyct6mt_0.png

Все получилось! Остался последний штрих…


Добавление команды в 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

Исходный код проекта можно посмотреть на гитхабе

© Habrahabr.ru