用 nodejs 写一个敕令行东西 :建立 react 组件的敕令行东西
媒介
上周,同事埋怨说 react 怎样不能像 angular 那样,运用敕令行东西来天生一个组件。对呀,日常平凡事情时,想要建立一个 react 的组件,都是直接 copy 一个组件,然后做一些修正。为何不能将这个历程交给顺序去做呢?当天晚上,我就模仿 angular-cli 的 api,写了一个天生 react 组件的敕令行东西 rcli。在这里纪录一下完成的历程。
api 设想
0.1.0 版本的 rcli 参照 angular-cli 的设想,有两个功用:
- 运用
rcli new PROJECT-NAME
敕令,建立一个 react 项目,个中天生项目标脚手架当然是 create-react-app 啦 - 运用
rcli g component MyComponent
敕令, 建立一个MyComponent
组件, 这个组件是一个文件夹,在文件夹中包括index.js
、MyComponent.js
、MyComponent.css
三个文件
厥后发明 rcli g component MyComponent
敕令在 日常平凡开辟历程当中是不够用的,由于这个敕令只是建立了一个类组件,且继续自 React.Component
。
在日常平凡开辟 历程当中,我们会用到这三类组件:
- 继续自
React.Component
的类组件 - 继续自
React.PureComponent
的类组件 - 函数组件(无状况组件)
注: 未来能够运用 Hooks 来替代之前的类组件
因而就有了 0.2.0 版本的 rcli
0.2.0 版本的 rcli
用法
Usage: rcli [command] [options]
Commands:
new <appName>
g <componentName>
`new` command options:
-n, --use-npm Whether to use npm to download dependencies
`g` command options:
-c, --component <componentName> The name of the component
--no-folder Whether the component have not it's own folder
-p, --pure-component Whether the component is a extend from PureComponent
-s, --stateless Whether the component is a stateless component
运用 create-react-app
来建立一个运用
rcli new PROJECT-NAME
cd PROJECT-NAME
yarn start
或许你能够运用 npm
装置依靠
rcli new PROJECT-NAME --use-npm
cd PROJECT-NAME
npm start
天生纯组件(继续自 PureComponent,以进步机能)
rcli g -c MyNewComponent -p
天生类组件(有状况组件)
rcli g -c MyNewComponent
即是:
rcli g -c ./MyNewComponent
天生函数组件(无状况组件)
rcli g -c MyNewComponent -s
天生组件不在文件夹内(也不包括 css 文件和 index.js 文件)
# 默许天生的组件都邑都包括在文件夹中的,若不想天生的组件被文件夹包括,则加上 --no-folder 选项
rcli g -c MyNewComponent --no-folder
完成历程
1. 建立项目
- 建立名为
hileix-rcli
的项目 - 在项目根目次运用
npm init -y
初始化一个 npm package 的基本信息(即天生 package.json 文件) - 在项目根建立
index.js
文件,用来写用户输入敕令后的主要逻辑代码 - 编辑
package.json
文件,增加bin
字段:
{
"name": "hileix-rcli",
"version": "0.2.0",
"description": "",
"main": "index.js",
"bin": {
"rcli": "./index.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/hileix/rcli.git"
},
"keywords": [],
"author": "hileix <304192604@qq.com> (https://github.com/hileix)",
"license": "MIT",
"bugs": {
"url": "https://github.com/hileix/rcli/issues"
},
"homepage": "https://github.com/hileix/rcli#readme",
"dependencies": {
"chalk": "^2.4.1",
"commander": "^2.19.0",
"cross-spawn": "^6.0.5",
"fs-extra": "^7.0.1"
}
}
- 在项目根目次下,运用
npm link
敕令,建立软链接指向到本项目标index.js
文件。如许,就可以再开辟的时刻,直接运用rcli
敕令直接进行测试 ~
2. rcli
会依靠一些包:
- commander:tj 大神写的一款特地处置惩罚敕令行的东西。主要用来剖析用户输入的敕令、选项
- cross-spawn:nodejs spawn 的跨平台的版本。主要用来建立子历程实行一些敕令
- chalk:给敕令行中的笔墨增加款式。
- path:nodejs path 模块
- fs-extra:供应对文件操纵的要领
3.完成 rcli new PROJECT-NAME
#!/usr/bin/env node
'use strict';
const program = require('commander');
const log = console.log;
// new command
program
// 定义 new 敕令,且背面跟一个必选的 projectName 参数
.command('new <projectName>')
// 对 new 敕令的形貌
.description('use create-react-app create a app')
// 定义运用 new 敕令以后能够运用的选项 -n(运用 npm 来装置依靠)
// 在运用 create-react-app 中,我们能够能够增加 --use-npm 选项,来运用 npm 装置依靠(默许运用 yarn 装置依靠)
// 所以,我将这个选项增加到了 rcli 中
.option('-n, --use-npm', 'Whether to use npm to download dependencies')
// 定义实行 new 敕令后挪用的回调函数
// 第一个参数就是在定义 new 敕令时的必选参数 projectName
// cmd 中包括了敕令中选项的值,当我们在 new 敕令中运用了 --use-npm 选项时,cmd 中的 useNpm 属性就会为 true,否则为 undefined
.action(function(projectName, cmd) {
const isUseNpm = cmd.useNpm ? true : false;
// 建立 react app
createReactApp(projectName, isUseNpm);
});
program.parse(process.argv);
/**
* 运用 create-react-app 建立项目
* @param {string} projectName 项目称号
* @param {boolean} isUseNpm 是不是运用 npm 装置依靠
*/
function createReactApp(projectName, isUseNpm) {
let args = ['create-react-app', projectName];
if (isUseNpm) {
args.push('--use-npm');
}
// 建立子历程,实行 npx create-react-app PROJECT-NAME [--use-npm] 敕令
spawn.sync('npx', args, { stdio: 'inherit' });
}
上面的代码边完成了 rcli new PROJECT-NAME
的功用:
-
#!/usr/bin/env node
示意运用 node 实行本剧本
4.完成 rcli g [options]
#!/usr/bin/env node
'use strict';
const program = require('commander');
const spawn = require('cross-spawn');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs-extra');
const log = console.log;
program
// 定义 g 敕令
.command('g')
// 敕令 g 的形貌
.description('Generate a component')
// 定义 -c 选项,接收一个必选参数 componentName:组件称号
.option('-c, --component-name <componentName>', 'The name of the component')
// 定义 --no-folder 选项:示意当运用该选项时,组件不会被文件夹包裹
.option('--no-folder', 'Whether the component have not it is own folder')
// 定义 -p 选项:示意当运用该选项时,组件为继续自 React.PureComponent 的类组件
.option(
'-p, --pure-component',
'Whether the component is a extend from PureComponent'
)
// 定义 -s 选项:示意当运用该选项时,组件为无状况的函数组件
.option(
'-s, --stateless',
'Whether the component is a extend from PureComponent'
)
// 定义实行 g 敕令后挪用的回调函数
.action(function(cmd) {
// 当 -c 选项没有传参数进来时,报错、退出
if (!cmd.componentName) {
log(chalk.red('error: missing required argument `componentName`'));
process.exit(1);
}
// 建立组件
createComponent(
cmd.componentName,
cmd.folder,
cmd.stateless,
cmd.pureComponent
);
});
program.parse(process.argv);
/**
* 建立组件
* @param {string} componentName 组件称号
* @param {boolean} hasFolder 是不是含有文件夹
* @param {boolean} isStateless 是不是是无状况组件
* @param {boolean} isPureComponent 是不是是纯组件
*/
function createComponent(
componentName,
hasFolder,
isStateless = false,
isPureComponent = false
) {
let dirPath = path.join(process.cwd());
// 组件在文件夹中
if (hasFolder) {
dirPath = path.join(dirPath, componentName);
const result = fs.ensureDirSync(dirPath);
// 目次已存在
if (!result) {
log(chalk.red(`${dirPath} already exists`));
process.exit(1);
}
const indexJs = getIndexJs(componentName);
const css = '';
fs.writeFileSync(path.join(dirPath, `index.js`), indexJs);
fs.writeFileSync(path.join(dirPath, `${componentName}.css`), css);
}
let component;
// 无状况组件
if (isStateless) {
component = getStatelessComponent(componentName, hasFolder);
} else {
// 有状况组件
component = getClassComponent(
componentName,
isPureComponent ? 'React.PureComponent' : 'React.Component',
hasFolder
);
}
fs.writeFileSync(path.join(dirPath, `${componentName}.js`), component);
log(
chalk.green(`The ${componentName} component was successfully generated!`)
);
process.exit(1);
}
/**
* 猎取类组件字符串
* @param {string} componentName 组件称号
* @param {string} extendFrom 继续自:'React.Component' | 'React.PureComponent'
* @param {boolean} hasFolder 组件是不是在文件夹中(在文件夹中的话,就会自动加载 css 文件)
*/
function getClassComponent(componentName, extendFrom, hasFolder) {
return '省略...';
}
/**
* 猎取无状况组件字符串
* @param {string} componentName 组件称号
* @param {boolean} hasFolder 组件是不是在文件夹中(在文件夹中的话,就会自动加载 css 文件)
*/
function getStatelessComponent(componentName, hasFolder) {
return '省略...';
}
/**
* 猎取 index.js 文件内容
* @param {string} componentName 组件称号
*/
function getIndexJs(componentName) {
return `import ${componentName} from './${componentName}';
export default ${componentName};
`;
}
- 如许就完成了
rcli g [options]
敕令的功用
总结
- api 设想是很主要的:好的 api 设想能让运用者越发方便地运用,且更改少
- 当本身想不到该怎样设想 api 时,能够参考他人的 api,看看他人是怎样设想的好用的