Pull to refresh

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

Reading time5 min
Views6.6K

Если вы делаете 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

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

Tags:
Hubs:
Total votes 9: ↑9 and ↓0+9
Comments6

Articles