手把手教你写一个react脚手架命令行CLI工具

背景

最近封装了一些 react 的组件,每次都需要从头配置,比价麻烦,就动了写一个工具的念头,也想扩展下自己的技术。

准备工作

首先初始化一个简单的 node 项目。

1
npm init -y

也可以在 github 上创建好项目,然后 clone 下来。

先介绍下要用到的插件:

  • commander.js,可以自动的解析命令和参数,用于处理用户输入的命令。
  • download-git-repo,下载并提取 git 仓库,用于下载项目模板。
  • inquirer.js,通用的命令行用户界面集合,用于和用户进行交互。
  • ejs.js,模板引擎,将用户提交的信息动态填充到文件中。
  • ora,下载过程久的话,可以用于显示下载中的动画效果。
  • chalk,可以给终端的字体加上颜色。
  • log-symbols,可以在终端上显示出 √ 或 × 等的图标。
  • shelljs,执行终端命令。
1
npm i commander download-git-repo inquirer ejs ora chalk log-symbols shelljs -S

CLI 的命令交互方式就是使用 commander 来实现的。

先体验下自己写的 CLI

为了熟悉下 commander 库,先写一个简单的 cli 工具,简单操作下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env node  //这一行必须有,指定是以node环境来运行
const fs = require("fs");
const { program } = require("commander");
const package = require("./package.json");

program
.version(package.version, "-v --version", "查看当前版本")
.command("init <name>")
.action(name => {
console.log(`the name is :${name}`);
});

// 把进程process的参数传递给commander
program.parse(process.argv);

version 的默认选项标识为-V 和–version,当存在时会打印版本号并退出。上面我们重新定义了选项标识。
查看效果:

1
2
node index.js -v
//1.0.0

配置命令执行文件:

1
2
3
4
// package.json
"bin": {
"c-rc-c": "index.js" // key就是命令名称
},

这里就可以看出 main 和 bin 的区别了,main 是指定引入组件时(import)调用的文件,bin 是安装组件后,指定执行的命令所关联的文件。

上面的代码一个简单的 cli 就已经写好了,安装后,执行c-rc-c -v就会看到结果。

本地测试

这个就是组件本地 link 的功能了;在组件 index.js 所在的目录中执行如下命令:

1
npm link

建一个临时文件夹,安装,执行

1
2
3
4
5
6
mkdir tmp
cd tmp
npm init -y
npm link create-react-component // 安装
c-rc-c -v //1.0.0
c-rc-c init zhangsan // the name is : zhangsan

下面是运行查看各个插件的效果。
cli-1.png
cli-view-2.png

完善脚手架 Cli

脚手架 cli 的功能说白了,就是生成开发组件的代码。这些代码已经帮你把要使用到的依赖已经安装好,你只要专心写你的组件就行了。

定义你的脚手架模板

由于开发用到的依赖和环境基本上是相同的,有极个别是根据个人需要设置的,所以我们可以写好一个模板,把需要变动的部分动态生成即可。
模板文件你可以放到 github 仓库里面,也可以和 cli 文件放在一起;

我这里主要设置了项目名称、作者、关键字、github 的仓库、和 css 预处理器等这些是动态设置的,涉及到文件有 package.json 和 webpack.base.js 这两个。其他的文件都不变,所以直接下载下来就行。

模板引擎的选择

说到模板引擎,不得不说handlebarsejs;另外看到还有使用consolidateMetalsmith

我使用的是ejs,它的文件格式是*.ejs,你也可以选择handlebars,它的文件格式是*.hbs

package.json的内容是json,读取出来可以直接修改,然后再写入文件即可,这就不用再专门使用一个模板引擎了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const packagePath = path.resolve(process.cwd(), name, "package.json");
console.log(chalk.yellow(`package.json地址:${packagePath}`));
const packageString = fs.readFileSync(packagePath, "utf8");
let packageJson = JSON.parse(packageString);
packageJson = {
...packageJson,
author,
description,
keywords: keywords ? keywords.split(",") : [],
repository: {
type: `git`,
url: `git+${repository}`,
},
bugs: {
url: `${subRepository}/issues`,
},
homepage: `${subRepository}#readme`,
};

fs.writeFileSync(packagePath, JSON.stringify(packageJson));

webpakc的配置文件生成,这个文件不能像package.json那样配置,这个使用ejs模板引擎来处理。

1
2
3
4
5
6
7
8
const webpackTemplatePath = path.resolve(process.cwd(), name, "webpack.base.ejs");
console.log(chalk.yellow(`webpack template 地址:${webpackTemplatePath}`));
const webpackTemplate = fs.readFileSync(webpackTemplatePath, "utf8");
const webpackString = ejs.render(webpackTemplate, { cssPreprocessor });
const webpackFilePath = path.resolve(process.cwd(), name, "webpack.base.js");
fs.writeFileSync(webpackFilePath, webpackString);
webpackSpinner.succeed("webpack 文件配置成功!");
fs.unlinkSync(webpackTemplatePath); // 删除模板文件

安装依赖

安装依赖使用了到了shelljs工具,这个是对nodechild_process的封装。

1
2
3
4
5
6
7
8
9
10
shell.exec(
`cd ${name}
npm i`,
error => {
if (error) {
installDependenciesSpinner.fail(chalk.red(`依赖安装失败:${error}`));
}
installDependenciesSpinner.succeed(chalk.green("依赖安装完成"));
},
);

发布 CLI 到 npm 仓库

登录 npm,执行 npm 的发布命令

1
npm publish

github 仓库地址

文章作者: wenmu
文章链接: http://blog.wangpengpeng.site/2019/04/03/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E5%86%99%E4%B8%80%E4%B8%AAreact%E8%84%9A%E6%89%8B%E6%9E%B6%E5%91%BD%E4%BB%A4%E8%A1%8CCLI%E5%B7%A5%E5%85%B7/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 温木的博客
微信打赏