如今基于 node.js
的 cli
工具层出不穷,尤其以前端脚手架工具更迭最为频繁,纯构建工具有 grunt
、gulp
、webpack
、parcel
和 rullup
等,各前端框架也有各自的 cli
工具 create-react-app
、vue-cli
和 angular-cli
,各大厂的集成化解决方案 fis
、jdf
、umi
等。但是每个团队的应用场景不同,以上工具不一定完全满足需求,是时候自己动手开发一个定制化的 cli
工具了。
主要内容
cli
是如何执行命令的- 创建
npm
包 - 创建可执行文件
- 关于命令行参数
- 运行环境检查
- 更新检查
- 测试和发布
要开发 cli
工具首先要知道 cli
是什么,以及如何在 cli
里执行自己开发的命令行工具。
cli
是如何执行命令的
命令行界面(英文名称:Command Line Interface),是一种通过命令行来交互的工具或者应用。不同的操作系统内置了不同的 cli
:
- unix-like
- bash
- sh
- csh
- zsh
- …
- windows
- cmd.exe
- PowerShell
通常情况下,一个命令对应了系统中的一个可执行文件,比如:cd
、ls
。当用户在 cli
中输入要执行的命令时,cli
会去预先配置好的查找路径中去查找同名的可执行文件并运行它。查找路径通常存在一个叫 PATH
的环境变量里。
比如 osx
系统可以通过命令 $PATH
知道其内容:
1 |
|
接下来看看如何创建这个可执行文件。
创建 npm
包
首先通过 npm init
命令来创建一个 npm
包:
1 |
|
创建可执行文件
- 在
cli-test
根目录新建一个文件bin/index.js
1 |
|
注意第一行的 #!/usr/bin/env node
,#!
表示要指定脚本文件的解析程序,/usr/bin/env
表示要去哪里找解析程序,node
是解析程序的名字(表示这个要文件由 node.js
来运行)。
- 并且在
package.json
中添加配置项:
1 |
|
指定在 cli
中的名称为 cli-test
,可执行文件为 bin/index.js
。当用户通过 npm install
安装这个模块的时候,npm
会根据不同的操作系统自动创建一个可执行文件,比如在 osx
中就会创建 /usr/local/bin/cli-test
文件并关联到该可执行文件。
- 为
bin/index.js
添加可执行权限(unix-like 系统):
1 |
|
通过上面的步骤,一个 cli
命令行工具就创建完成了,接下来就可以编写工具的具体执行逻辑了,bin/index.js
也是一个 node.js
模块,可以引用其它 node.js
模块而且不需要添加执行权限:
1 |
|
关于命令行参数
通常命令行工具都会有很多功能,每个功能通过不同的命令来调用,并且每个功能还会接收不同的参数,比如常用的 vue-cli
,在终端输入 vue
并回车就可以看到 vue-cli
所支持的所有命令及其可选参数:
1 |
|
process.argv
在 node.js
中可以使用 process.argv
数组来获取命令行输入的参数:
1 |
|
1 |
|
但是 process.argv
得到的内容不好区分命令
、参数
以及参数的值
,所以这里需要引入一个第三方模块 yargs-parser。
1 |
|
帮助信息
每个命令行工具都应该有一个帮助信息展示功能,例如上文中输入 vue
不加任何命令和参数(也可以添加参数 -h
或者 --help
),输出的内容就是 vue-cli
的帮助信息,里面包含了支持的所有 命令
及 可选参数
。
1 |
|
关于路径
在开发命令行工具时,需要关注两个路径信息:
- 可执行脚本文件的路径
- node.js 进程的当前工作路径
可执行脚本文件的路径 即当前脚本文件路径,比如 bin/index.js
的路径,通过 __dirname
可以获得该路径的值。
1 |
|
1 |
|
通过这个路径可以读取或写入在本命令行工具安装路径里的文件,比如配置信息,缓存等。
node.js 当前工作路径 即用户在终端执行命令行工具的命令时所处的路径,通过 process.cwd()
可以获得该路径的值。
1 |
|
1 |
|
通过这个路径可以知道用户想要在什么位置使用该命令行工具,并且可以读取用户自定义的配置文件,比如 vue
的配置文件 vue.config.js
。
commander.js
如果觉得以上处理 命令
和 参数
的方式很繁琐,又想快速上手制作出一个命令行工具,可以使用第三方模块 commander.js。
commander.js
是一个完整的 node.js
命令行解决方案,灵感来自 Ruby
的 commander。它有很详细的说明文档,并且有官方中文版,这里就不做过多介绍了。
用户行为交互
为了给用户提供更加友好的使用体验,通常会增加一些交互行为反馈功能,比如任务执行等待提示、输出结果高亮提示和用户输入提示等。
任务执行等待提示
ora 是一个不错的等待提示工具,也很容易上手:
1 |
|
输出结果高亮
chalk 可以在命令行终端里定义字符串样式的模块,通过这个模块可以在终端输出各种样式的文本信息:
1 |
|
用户输入交互
inquirer 模块集成了常用的命令行工具的用户交互功能,比如:input, number, confirm, list, rawlist, expand, checkbox, password, editor 等。
- 文本输入
1 |
|
- 单项选择
1 |
|
更多功能请参考官方文档: https://github.com/SBoudrias/Inquirer.js#documentation
运行环境检查
有些命令行工具需要安装第三方工具或者在特殊的环境(比如:node.js 的版本在 v10 及以上)才能运行,所以在运行任何程序之前,需要检查用户当前的运行环境是否能够正常使用本工具。
node.js 版本检查
npm
提供了在 package.json
文件中配置 engines
来声明 node.js
的版本,也可以声明 npm
的版本:
1 |
|
当用户在安装本命令行工具时,npm
检查当前的 node.js
版本,如果不满足要求则会警告提示,如果带上 engine-strict
参数则会直接报错。
当然,开发者也可以在运行命令行工具时用程序去检查,这里借助第三方模块 node-semver:
1 |
|
其它环境检查
如果本工具还需要依赖其它环境,也可以使用类似上面的检查方法,只是各个工具的版本号获取方法可能稍有不同。也可以参考一些复杂的命令行工具,添加一个 doctor
命令(比如 cli-test doctor
)专门做环境检查用。
更新检查
通常一个应用程序都不会只发布一个版本,而是会不断的迭代新的版本,当 npm
包发布新版本时用户是无法感知到的,所以需要在用户每次使用这个工具的时候,主动去检查线上最新的版本,如果用户使用的版本太旧就主动提示用户升级。
获取当前 npm
包的版本很容易,可以读取 package.json
文件中的 version
拿到。获取线上的最新版本号稍麻烦些,需要用到 node.js
的 http
模块发起一个 http
请求到http://registry.npmjs.org/[模块名]
,返回结果是一个 json
字符串,里面包含了 dist-tags.latest
字段可以获取当前最新版本。
为了方便举例,这里使用到一个第三方 http
请求库 axios
1 |
|
测试和发布
测试
命令行工具开发完成后需要测试它的功能是否正确,npm
提供了 npm link
命令模拟安装本地模块,也可以使用 yarn link
。
在发布之前通常要对源代码进行编译并且完整地跑一遍单元测试,编译和单元测试的内容因项目而异,不在这里做过多介绍。需要注意的一点时,为了防止发布时忘记了编译和测试代码,可以通过 package.json
中配置 scripts.prepublish
来解决。
1 |
|
发布
npm
提供了 dist-tag
来标记当前发布的版本,默认是 latest
,可以自定义。比如要发布一个测试版本,可以使用 npm publish --tag next
,通常情况下 npm install cli-test
只会安装 latest
标记的最新版本,要安装到 --tag next
的版本,需要指明安装的版本号:npm install cli-test@x.x.x
或执行 npm install cli-test@next
指定要安装 --tag next
的最新版本。