得益于 Node.js,我们可以使用熟悉的 Javascript 语言来开发跨平台的命令行程序。
图形界面:操作简单、门槛低;
命令行界面:轻量、高效、自由。
本文将从最简单的例子出发,然后介绍如何使用 commander 来简化开发,接着列出一些有用的工具。
Hello World
CLI (Command Line Interface) 允许我们在命令行调用自定义的指令,达到执行某些操作的目的。
webpack main.js bundle.js --config webpack.config.js
上面命令使用 Webpack 来打包代码,webpack
命令可以接受参数 (argument) 和选项 (option) 。在这个例子中,参数是 main.js
和 bundle.js
,选项是--config webpack.config.js
,表示把 main.js
文件中的脚本打包输出到 bundle.js
文件中,并使用 webpack.config.js
文件的配置。
那么如何开发一个自定义命令呢。
首先,使用 npm init
初始化一个项目。
# new folder
mkdir mycli
# init a project
npm init
新建 bin/mycli.js
文件,添加以下代码。
// mycli.js
console.log('This is my first CLI.')
使用 node
命令执行它。
node ./bin/mycli.js
# > This is my first CLI.
我们已经可以得到正确输出了,那么如何将命令名称改成我们的 mycli
呢?npm
允许将可执行文件加入到操作系统的环境变量中。修改 package.json
,添加 bin
字段。
// package.json
{
...
"bin": {
"mycli": "bin/index.js"
}
}
使用 bin
字段时,需要一个命令的名称,和一个对应的可执行文件地址。全局安装该模块时,会在 npm
安装目录下建立与可执行文件的 symlink
(符号链接)。局部安装该模块时,会在当前项目的 node_modules/.bin/
目录下建立链接。
同时在 bin/mycli.js
里声明指定脚本的解释程序为 node
。
#! /usr/bin/env node
console.log('This is my first CLI.');
可以使用 npm link
来调试本地模块。在 mycli
目录下输入以下命令。
npm link
相当于在全局安装了我们的本地模块。现在,我们可以使用 mycli
命令了。
mycli
# > This is my first CLI.
现在,我们能够看到正确的输出了。
有时候,我们希望在命令后面添加一些参数或者配置。使用 process.argv
来查看命令后面带的参数。修改 bin/mycli.js
如下。
#! /usr/bin/env node
console.log(process.argv);
执行 mycli
命令,并在后面加上一些参数。
mycli a -b c
# > [ 'C:\\Program Files\\nodejs\\node.exe',
# > 'C:\\Users\\me\\AppData\\Roaming\\npm\\node_modules\\mycli\\bin\\mycli.js',
# > 'a',
# > '-b',
# > 'c' ]
可以看到,process.argv
是一个数组,数组的前两项用来做执行命令行映射的脚本,从 process.argv[2]
开始才是我们需要的参数。
至此,我们已经完成了一个最简单的命令行程序开发。
使用 commander 来简化开发流程
commander 是一个Node.js命令行程序的完全解决方案。使用 commander 可以很大程度地提升我们的开发效率。
安装 commander 依赖。注意需要用 --save
,因为是代码运行时需要的模块。
# install dependeny
npm install commander --save
在学习 commander 之前,理清两个概念,argument
(参数) 和 option
(选项)。看看最开始的例子。
webpack main.js bundle.js --config webpack.config.js
argument
直接跟在命令后面,不需要加任何前缀,option
需要在前面添加一些前缀如 --config
。所以,这里 main.js
和 bundle.js
是参数,而 --config webpack.config.js
是选项。
修改 bin/mycli.js
如下。
#! /usr/bin/env node
var program = require('commander');
program
.version(require('../package.json').version)
.usage('<path> [options]')
.parse(process.argv);
console.log(program.args);
version
方法用来配置命令行程序的版本,这里取了 package.json
的 version
字段。usage
方法用来配置使用说明,示例表示 mycli
接受一个路径参数和可选的一个或多个选项。然后把 process.argv
传给 parse
方法用于处理参数。program.args
是命令接收的参数,已经过滤掉 process.argv
前两项的路径了。
执行如下命令。
# show version
mycli -V
# > 1.0.0
# show help
mycli -h
# >
# > Usage: mycli <path> [options]
# >
# >
# > Options:
# >
# > -V, --version output the version number
# > -h, --help output usage information
commander 默认存在两个选项 -V
和 -h
,代表查看版本和查看帮助。
继续执行。
mycli a -b c
# > [ 'a' ]
可以看到只打印了 a
参数,因为 -b c
被当作选项了。继续修改 bin/mycli.js
,来解析选项。
#! /usr/bin/env node
var program = require('commander');
program
.version(require('../package.json').version)
.usage('<path> [options]')
.option('-v --verbose', '启用啰嗦模式')
.option('-o --output <path>', '输出到路径')
.option('-l --list [item]', '输出列表')
.parse(process.argv);
console.log('Arguments: ' + program.args);
console.log('Verbose: ' + program.verbose);
console.log('Output: ' + program.output);
console.log('List: ' + program.list);
option
方法的第一个参数接受一个包含选项简写、选项全称和可选的接收值 (<>
表示必填,[]
表示选填) 的字符串,第二个参数是使用说明,会在查看帮助时打印出来。
若 --xxx
选项需要接收一个值,program.xxx
可以拿到选项的值,如 program.output
和 program.list
;
若 --xxx
选项无接收值,program.xxx
可以拿到一个布尔值,如 program.verbose
。
执行命令。
mycli value1 -v value2 -o value3 -l value4
# > Arguments: value1,value2
# > Verbose: true
# > Output: value3
# > List: value4
-v
选项无接受值,所以 value2
被当作参数。
一些有用的工具
- left-pad - 常用来制表,对齐(广为流传的一个包..)
const leftPad = require('left-pad')
leftPad('foo', 5) // => " foo"
leftPad('foobar', 6) // => "foobar"
leftPad(1, 2, '0') // => "01"
leftPad(17, 5, 0) // => "00017"
- chalk - 控制命令行文本样式
const chalk = require('chalk');
console.log(chalk.blue('Hello world!'));
- ora - 终端加载动效
const ora = require('ora');
const spinner = ora('Loading unicorns').start();
setTimeout(() => {
spinner.color = 'yellow';
spinner.text = 'Loading rainbows';
}, 1000);
- ink - 使用 React 来开发命令行界面