If you do Ctrl+C
every time you create a new component in the reaction, then this article is for you!
The reaction does not have its own CLI, and it is understandable why. There are no specific rules for how the structure of the component should look like, there are only general recommendations in the documentation. All developers use a structure that has taken root in their team. And sometimes you have to support projects in different styles.
The structure itself also depends on the stack used:
- Styles - styled, scss modules, css;
- TypeScript or JavaScript;
- Tests
There are several ways to make your life easier when creating new components. For example, you can create templates in your development environment (for example, in WebStorm). But today we will look at how to create a complete component structure from the command line. At the end of the article, we can create components with a single command. For example, such as:
npm run create components/Home/ComponentName
Training
We will use the Create React App to create the project .
Create a project:
npx create-react-app react-cli
All our code will be stored in one file. We create the cli folder in the root of our project, and inside it create.js file.
To work, we need 3 modules, we import them into our file.
// cli/create.js const fs = require('fs'); const path = require('path'); const minimist = require('minimist');
fs - module for working with the file system.
path - a module for processing file paths.
minimist - a module for converting arguments from the command line.
Work with Arguments
In order to create a component, we need to pass the path and name of the component to the command line. We will pass this information on one line ( for example, components/folder1/folder2/Menu
), which we then parse into the path and name.
All arguments can be retrieved from the process
object. Suppose we enter the following line into the console:
node cli/create.js --path components/folder/Menu
As a result, we get:
console.log(process.argv); // [ // '/usr/local/bin/node', // '/Users/a17105765/projects/react-cli/cli/create.js', // '--path', // 'components/folder/Menu' // ]
Using the minimist module, we can convert the arguments to an object:
// 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' // }
Great, you can already work with this.
Creating Directories
First, prepare the necessary variables. We need the full path to the src folder of our project, the path from the arguments as an array and the name of the component.
// cli/create.js // ... // src const srcPath = [__dirname, '..', 'src']; // const arrPath = args.path.split('/'); // ( ) const componentName = arrPath[arrPath.length - 1];
Suppose we have indicated a nonexistent path. In a good way, we must create all these subfolders if they are not there. So let's do it.
// cli/create.js // ... // ( ) const currentArray = []; arrPath.forEach(element => { currentArray.push(element); const currentResolvePath = path.resolve(...srcPath, ...currentArray); if (!fs.existsSync(currentResolvePath)) { // - ? fs.mkdirSync(currentResolvePath); // , } });
Here we loop through all the elements of the path and, if necessary, create a directory using the mkdirSync
method. Before that, we normalize the path to the component in one line using the resolve
method. After performing these operations, we will create the necessary directory structure.
Test written. We enter the following command into the command line (at the same time, we don’t have any directories in the src
folder yet):
node cli/create.js --path components/A/B/C/D/E/CustomComponent
And we get the following result:
Creating component files
Well done, done, all that remains is to create the component files.
We will use the simplest component structure:
- For styles plain css
- Without TS
- No tests
- Functional component
It turns out that we need to create 3 files.
1. Component template
import React from 'react'; import './CustomComponent.css'; const CustomComponent = () => { return ( <div className="wrapper"> </div> ); }; export default CustomComponent;
2. Index file template
export { default } from './CustomComponent';
3. Style file template
.wrapper {}
First, get the full path to the component in one variable (including the component’s personal folder):
// cli/create.js // ... const componentPath = [...srcPath, ...arrPath];
New files are created using the writeFileSync
, which takes the file path and contents.
Creating a component file:
// 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);
Creating an index file:
// cli/create.js // ... const indexCode = `export { default } from './${componentName}';`; fs.writeFileSync(path.resolve(...componentPath, 'index.js'), indexCode);
Creating a stylesheet:
// cli/create.js // ... const styleCode = '.wrapper {}'; fs.writeFileSync(path.resolve(...componentPath, `${componentName}.css`), styleCode);
Done!
Now let's see what happened.
// 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);
It turned out only 43 lines, taking into account the comments, not bad for such a useful thing!
Now let's try to create a component:
node cli/create.js --path components/folder1/folder2/Button
Everything worked out! There is the last touch ...
Adding a command to package.json
Add the command to the package.json file so that each time we don’t write the path to the script
{ "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" ] } }
Now instead of:
node cli/create.js --path components/folder1/folder2/Button
we can just write
npm run create components/folder1/folder2/Button
The source code of the project can be viewed on the github