并非严格的教程博文,属于是个人在学习过程中的笔记心得汇总。
内容相对官方文档更通俗一些,入门新人,如果看不懂官方文档,可以大致看一看这个。有错的话请指正,标准的还得看官方文档。
1.commander.js概述
commander.js是一个工具,用来构建node的命令行程序,使得能够使用自定义指令在全局命令行运行node脚本
本来我们只能在脚本所在文件的根目录里通过
node xxx.js
运行脚本,通过commander构建命令行程序后,就能在任意一个目录里,比如桌面,比如用户目录,直接输入自定义的那个指令,就能直接运行脚本,更加简便。
另外还有一种简便的方式,就是打包成exe文件,直接双击就能运行,但是缺点在于适应性,就是如果想要在win、linux等多个平台使用,就得分别打包,而使用命令行形式,可以轻松跨平台使用
2.小案例演示
光说未必能想象出来,还得实际场景看一看,我这就写一个小小案例演示一下。
1.文件目录
├─bin
├─node_modules
│ └─commander
│ ├─lib
│ └─typings
└─src
除此之外还有package.json文件,上面图没有显示
2.文件内容
//index.js
console.log('演示案例');
#!/usr/bin/env node
//my-cli.js
const program=require('commander')
program
.version('1.0.0')
.parse(process.argv)
require('../src/index.js')
{
"name": "cac",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"commander": "^9.4.0"
},
"bin": {
"my-cli":"./bin/my-cli.js"
}
}
3.全局运行脚本
没有用node做前缀运行,没有在根目录,直接在全局cmd就直接运行了脚本,输出了结果,这就是commander的作用。
另外这个命令还能配置很多参数功能,如查看版本等,都可以自定义配置
4.实现方法
①功能写在主文件index.js里
②然后通过bin文件夹下的my-cli引入运行
③最后在package.json里配置bin字段,设置自定义命令
和文件路径
④在根目录使用yarn link/npm link,将软件包链接到全局
3.安装
npm安装:
npm install commander
yarn安装:
yarn add commander
4.声明program变量
commander提供了一个全局对象program
const program= require('commander');
如果要是用多种方式使用commander,可以通过Command类来创建
const {Command}=require('commander')
const program=new Command()
5.选项
选项定义
- 使用
.option()
方法定义- 每个选项可以定义两个名字,一个短选项(
-
开头接单个字符),一个长选项(--
开头接多个字符),逗号隔开(也可以用空格或者|
)
program
.option('-a,--anli','这里是描述语句')
选项获取
- 解析后的选项可以使用.opts()方法获取(选项同时也会传递给命令处理函数–不懂,待补充)
- 如果长选项的命名使用了多单词,比如
max-length
,就会自动被转化为驼峰命名法maxLength
- 没有在命令行使用选项则选项值为
undefined
program
.option('-a,--anli','演示案例选项')
.option('-b,--tuo-feng','转化为驼峰命名')
.parse()
console.log('已经被解析的选项和选项值',program.opts());
console.log('可以直接通过选项名获取选项值:tuoFeng,',program.opts().anli,program.opts().tuoFeng);
PS F:\desk\cac> my-cli
已经被解析的选项和选项值 {}
可以直接通过选项名获取选项值:tuoFeng, undefined
PS F:\desk\cac> my-cli -a -b
已经被解析的选项和选项值 { anli: true, tuoFeng: true }
可以直接通过选项名获取选项值:tuoFeng, true
常用选项类型
- 有两种,一类是boolean选项,无需配置参数,在命令行中不指定选项被定义为undefined,指定则为true
- 另一类是带参数选项,在名字后面用尖括号声明,如
--canshu <value>
,在命令行不指定选项名被定义为undefined,指定选项但没有赋参数则会报错error: option '-a,--anli <value>' argument missing
- 多个布尔值选项连写可以合并,如-a -b -c可以合并为为 -abc,也可以和一个带参数选项连写,带参数的放在最后如-abcp test
- 带参数类型后如果没写参数,它会读取后面的选项作为参数,如
-a -b
,最后a的参数值为‘-b’
program
.option('-a,--anli <value>','带参数选项')
.option('-b,--boolean','布尔值选项')
.parse()
console.log('已经被解析的选项和选项值',program.opts());
PS F:\desk\cac> my-cli
已经被解析的选项和选项值 {}
PS F:\desk\cac> my-cli -a
error: option '-a,--anli <value>' argument missing
PS F:\desk\cac> my-cli -b
已经被解析的选项和选项值 { boolean: true }
PS F:\desk\cac> my-cli -b -a test
已经被解析的选项和选项值 { boolean: true, anli: 'test' }
PS F:\desk\cac> my-cli -a --boolean
已经被解析的选项和选项值 { anli: '--boolean' }
选项默认值
- 带参数选项可以设置默认值,当在命令行没有指定选项时,自动赋默认值。
- 指定选项但没有写参数,则还是会报错
- 指定选项且写了参数,赋写的新参数
program
.option('-a,--anli <value>','描述','moren')
.parse()
console.log('已经被解析的选项和选项值',program.opts());
PS F:\desk\cac> my-cli
已经被解析的选项和选项值 { anli: 'moren' }
PS F:\desk\cac> my-cli -a
error: option '-a,--anli <value>' argument missing
PS F:\desk\cac> my-cli -a qer
已经被解析的选项和选项值 { anli: 'qer' }
PS F:\desk\cac>
取反选项
是一个以no-
开头的布尔值型长选项,作用是在命令行使用时,把选项值赋false
有如下几种情况
- 只使用原选项,赋值为true
- 只使用取反选项,赋值为false
- 先使用原选项,后使用取反选项,赋值为false
- 先使用取反选项,后使用原选项,赋值为true
- 总的来说就是赋值可覆盖,以后者为准
program
.option('-c,--cac','描述')
.option('--no-cac','取反选项')
.parse()
console.log('已经被解析的选项和选项值',program.opts());
PS F:\desk\cac> my-cli
已经被解析的选项和选项值 {}
PS F:\desk\cac> my-cli -c
已经被解析的选项和选项值 { cac: true }
PS F:\desk\cac> my-cli --no-cac
已经被解析的选项和选项值 { cac: false }
PS F:\desk\cac> my-cli -c --no-cac
已经被解析的选项和选项值 { cac: false }
PS F:\desk\cac> my-cli --no-cac -c
已经被解析的选项和选项值 { cac: true }
可选参数选项
- 参数可选,在使用选项但没给参数的情况下会变成布尔值选项
- 且不会像带参数选项那样把后面的选项命令当做参数,会忽略破折号开头的输入参数,如果想让破折号开头的作为参数,可以使用=,如
-i=-5
- 参数使用方括号声明
--icon [value]
,
program
.option('-c,--cac [value]','描述')
.parse()
console.log('已经被解析的选项和选项值',program.opts());
PS F:\desk\cac> my-cli
已经被解析的选项和选项值 {}
PS F:\desk\cac> my-cli -c
已经被解析的选项和选项值 { cac: true }
PS F:\desk\cac> my-cli -c ahh
已经被解析的选项和选项值 { cac: 'ahh' }
PS F:\desk\cac> my-cli -c -666
error: unknown option '-666'
PS F:\desk\cac> my-cli -c=-666
已经被解析的选项和选项值 { cac: '=-666' }
必填选项
- 必填选项要么设置了默认值,要么在命令行必须输入选项,必须保证选项在解析时有赋值
- 使用
.requiredOption()
设置
PS F:\desk\cac> my-cli
error: required option '-c,--cac <value>' not specified
变长参数选项
可以在命令行输入多个参数,解析后会以数组形式存储,在下一个选项之前的输入都会被当做变长参数
program
.option('-c,--cac <value...>','描述')
.parse()
console.log('已经被解析的选项和选项值',program.opts());
PS F:\desk\cac> my-cli -c a b c 1 2 3
已经被解析的选项和选项值 { cac: [ 'a', 'b', 'c', '1', '2', '3' ] }
版本选项
- .
version()
,设置版本,自己只用填写版本号,它是有默认选项的,为-V
(大写),--version
,- 使用选项会输出当前版本号
PS F:\desk\cac> my-cli -V
1.0.0
构造选项
- 上面的选项配置都是通过.option()添加,但是还用一种方式,采用new Option()方式构建
- 优点在于能够做更加详细的配置,用得不多,这里不多说,需要的时候再查
program
.addOption(new Option('-e,--erhen'.hideHelp()))
自定义选项处理
自定义一个函数,放置在选项中,当传入参数时会自动处理参数并返回结果
函数有两个参数,分别为新参数和老参数
function add(value,previous){
return [value,previous]
}
program
.version('1.0.0')
.option('-c,--cac <value...>','描述',add)
.parse()
console.log('已经被解析的选项和选项值',program.opts());
PS F:\desk\cac> my-cli -c 3
已经被解析的选项和选项值 { cac: [ '3', undefined ] }
PS F:\desk\cac> my-cli -c 2 -c 3
已经被解析的选项和选项值 { cac: [ '3', [ '2', undefined ] ] }
6.命令
配置方法有两种方式
一种使用
.command()
配置,一种使用.addCommand()
配置
实现方式也有两种,
一种为命令行绑定处理函数,在内部写一个函数来处理数据,另一种是把命令写成可执行文件,实现方法写在了后面,点击跳转
使用.command()
进行配置,直接添加,直接执行
const {Command}=require('commander')
const program=new Command()
program
.command('add <one> [two]')
.description('描述')
.action((one,two)=>{
console.log('one:',one,'two:',two);
})
.parse()
PS F:\desk\cac> my-cli 2 3
one: 2 two: 3
当然,如果为program配置了多个命令,就要附上命令名了
const {Command}=require('commander')
const program=new Command()
program
.command('add <one> [two]')
.description('描述')
.action((one,two)=>{
console.log('one:',one,'two:',two);
})
program
.command('dd <on> [tw]')
.description('描述')
.action((on,tw)=>{
console.log('on:',on,'tw:',tw);
})
program
.parse()
PS F:\desk\cac> my-cli dd 2 3
on: 2 tw: 3
使用.addCommand()
进行配置,采用了函数方式,执行也是函数名加子命令,相当于打了一个包。
const {Command}=require('commander')
function addcomm(){
const addcom=new Command('addcom')
addcom
.command('asd')
.action(()=>{
console.log('asdd');
})
addcom
.command('asf')
.action(()=>{
console.log('asdf');
})
return addcom
}
program.addCommand(addcomm())
.parse(process.argv)
PS F:\desk\cac> my-cli addcom asf
asdf
命令参数
上面命令的参数是直接在command里指定的,但是还有另一种方法,使用.argument()
添加参数,command只写名称。
注意:一个argument里只能写一个参数,要想写多个参数得用arguments
使用argument
const {Command}=require('commander')
const program=new Command()
program
.command('add')
.argument('<one>','参数描述')
.argument('<two>','参数描述')
.action((one,two)=>{
console.log('one:',one,'two:',two);
})
program
.parse()
使用arguments
.arguments("<one>","[two]")
和选项一样,也能用可变参数,用法一样
.argument('<dirs...>')
同样的,也有自定义参数处理,老一套
.argument('<first>', 'integer argument', myParseInt)
处理函数
- 处理函数就是上面提到过的
.action()
,通过命令传递参数后,使用函数做相应的处理。- 除了命令传递的参数外,处理函数还自带了两个参数,一个是
options
解析的选项,一个是command
命令本身- 可以跳过参数声明直接使用command,使用关键字this操作,注意不能在箭头函数用
.action((one,two,options,command)=>{
console.log('one:',one,'two:',two);
})
.action(function(){
console.log(this.opts());
})
独立可执行命令
> - 如果command带有**参数描述**,就代表可以使用独立的可执行文件作为处理函数 > - commander就会在脚本根目录搜索`command-subcommand`类型文件,就比如,我的指令是`my-cli`,命令的名字是`add`,他就会去找`my-cli-add`文件去处理参数program
.command('add [name]','xxx')
看下面的报错,我执行了指令和命令,带上了参数,他就会去根目录找相应文件,但是这时候没有这个文件,就报错了
后面建立了该文件,就成功执行,输出了‘233’
PS F:\desk\cac> my-cli add 233
node:internal/modules/cjs/loader:936
throw err;
^
Error: Cannot find module 'F:\desk\cac\my-cli-add'
at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
at Function.Module._load (node:internal/modules/cjs/loader:778:27)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
at node:internal/main/run_main_module:17:47 {
code: 'MODULE_NOT_FOUND',
requireStack: []
}
PS F:\desk\cac> my-cli add 233
生命周期钩子
和vue之类的一样,commander也有生命周期,设置回调函数就可以在对应的时机执行
钩子 | 触发时机 | 参数 |
---|---|---|
preAction/postAction | 本命令的处理函数执行前后 | thisCommand,actionCommand |
preSubcommand | 直接子命令解析之前 | thisCommand,subcommand |
.hook('preAction',(thisCommand, actionCommand)=>{
console.log('生命周期函数用法');
})
补充
.parse()
作用就是解析,参数就是要解析的字符串,一般使用时参数就是用process.argv,就是用户输入参数
(这些都是比较常用的部分,有一些功能可以去官方文档看,看不懂可以留言,我要是看得懂就在这补充上去)