摘要:用写一个命令行工具创建组件的命令行工具前言上周,同事抱怨说怎么不能像那样,使用命令行工具来生成一个组件。为什么不能将这个过程交给程序去做呢当天晚上,我就仿照的,写了一个生成组件的命令行工具。
用 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: newg `new` command options: -n, --use-npm Whether to use npm to download dependencies `g` command options: -c, --component 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
rcli new PROJECT-NAME cd PROJECT-NAME yarn start
或者你可以使用 npm 安装依赖
rcli new PROJECT-NAME --use-npm cd PROJECT-NAME npm start
rcli g -c MyNewComponent -p
rcli g -c MyNewComponent
等于:
rcli g -c ./MyNewComponent
rcli g -c MyNewComponent -s
# 默认生成的组件都会都包含在文件夹中的,若不想生成的组件被文件夹包含,则加上 --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 ")
// 对 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 ", "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,看看别人是怎么设计的好用的
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/100082.html
摘要:也是一款优秀的响应式框架站点所使用的一套框架为微信服务量身设计的一套框架一组很小的,响应式的组件,你可以在网页的项目上到处使用一个可定制的文件,使浏览器呈现的所有元素,更一致和符合现代标准。 GitHub 值得收藏的前端项目 整理与收集的一些比较优秀github项目,方便自己阅读,顺便分享出来,大家一起学习,本篇文章会持续更新,版权归原作者所有。欢迎github star与fork 预...
项目开始前,我们先聊一聊关于项目的一些说明。该项目起始于2017年初,当时公司主要技术栈为gulp+angular,鉴于react的火热的生态,在公司决定研发bss管理系统时选用react开发,目的也是为react native打下基础,以解决后期公司大前端技术栈的逐步成熟。(当时没有选择vue开发的主要原因是weex生态还不够特别成熟),既然决定换新,项目的构建也跟着一起换,从gulp转向火热的...
摘要:暴露所有内建配置,项目下会新增或对部分配置文件进行修改。开发环境开发时,前端项目和后端项目运行时端口端口不同,存在跨域问题。项目目录结构优化项目目录结构优化开发目录主要是目录,因此需要修改的目录主要是目录。 1 开发环境准备(windows) 1.1 安装nodejs和npm 1) 下载nodejs安装包:http://nodejs.org/en/download/ nodejs安...
阅读 1439·2023-04-25 18:57
阅读 2466·2023-04-25 16:28
阅读 4443·2021-11-24 09:39
阅读 4175·2021-11-16 11:45
阅读 2144·2021-10-13 09:40
阅读 1457·2019-08-30 15:52
阅读 1928·2019-08-30 10:57
阅读 869·2019-08-29 16:55