脚手架系列-commander

3,082 阅读6分钟

开始

最近看了vue/cli的源代码,学习到了许多相关的核心工具库,打算输出一系列的核心知识文章。

本篇为脚手架系列第一篇,后面不定期更新(手动狗头),先挖一个坑。

在阅读源码或者自己实现一个脚手架工具前,先要熟悉并学会使用一些工具。本篇文章先从命令行工具commander开始学习!

commander

commander是一款重量轻,表现力和强大的命令行框架,提供了用户命令行输入和参数解析强大功能。

这个工具就是在你输入对应的命令时返回对应的消息,比如vue-cli,你在系统全局安装了cli工具后,在命令提示框输入对应的命令后会弹出相应的交互信息,如下截图:

使用方式

本次教程使用的版本为:6.2.0

官方地址:github.com/tj/commande…

先安装

npm i commander

引入全局对象

const { program } = require('commander');

官方教程还有一个引入Command来实例化program对象的方式

const { Command } = require('commander');
const program = new Command();

注意:在执行相关的命令后,要在控制台打印相关的信息的话,必须在最后执行以下这句代码。

program.parse(process.argv);

version()方法

这个方法可以用来输出当前你的脚手架工具的版本号。

参数

  • 版本号,必须参数
  • 自定义命令标识,可选参数,默认为-V--version
  • 描述信息,可选参数
// 只有版本信息
program.version('1.0.0');

program.prase(process.argv);

// 自定义命令标识
// 添加了自定义参数后,执行命令就可以使用自己定义的参数了
program.version('1.0.0', '-v, --vers');

program.prase(process.argv);

options()方法

Commander使用option()方法来定义选项,同时可以附加选项的简介。每个选项可以定义一个短选项名称(-后面接单个字符)和一个长选项名称(--后面接一个或多个单词),使用逗号、空格或|分隔。

参数

  • 自定义命令标识,必须参数
    • 一长一断的标识,使用逗号、空格或|分隔
    • 标识后面可以跟参数,<>为必须参数,[]为可选参数,如-t --test <type>
  • 选项描述,可选参数,这个在你使用-h--help参数时会显示对应的信息
  • 选项的默认值,可选参数

基本用法

选项可以通过Commander对象的同名属性获取,对于多个单词的长选项,使用驼峰法获取,例如--template-engine与属性program.templateEngine关联。比如下面演示代码的--debug-test参数

program.version('1.1.0')
  .option('-t, --test', 'test option function')
  .option('-d, --debug-test', 'test option function -- debug test');

// 注意,在执行相关的命令后,要在控制台打印相关的信息的话,必须在最后执行以下这句代码。
program.parse(process.argv);

console.log('----------------');
if(program.test) {
  console.log('you use --test commander');
}
if(program.debugTest) {
  console.log('you use --debug-test commander');
}
console.log('----------------');

当你在执行这段代码对应的文件时,比如node xxx.js -tnode xxx.js --test,会输出you use --test commandernode xxx.js -dnode xxx.js --debug-test,会输出you use --debug-test commander

带参数和加默认值

可以在命令后面带参数,使用<>来声明

program.version('1.1.0')
  .option('-a, --argu-test <name>', 'test option function -- agru test') // 带参数<>

// 注意,在执行相关的命令后,要在控制台打印相关的信息的话,必须在最后执行以下这句代码。
program.parse(process.argv);

console.log('----------------');
if(program.arguTest) {
  console.log(`output: ${program.arguTest}`);
}
console.log('----------------');

执行node xxx.js --argu-test=hellonode xxx.js -a hello,则会输出output: hello

注意:如果我们定义了<>默认参数,但是在使用命令时没传入对应的参数值会报错:error: option '-a, --argu-test <name>' argument missing

我们还可以在上面代码的option选项的第三个参数位置传入默认值,如下:

option('-a, --argu-test <name>', 'test option function -- agru test', 'default value')

这样的话,我们直接执行代码,不带入任何命令和参数,执行node xxx.js,则会输出output: default value

取反命令

在你的命令前面加上--no代表紧跟的这个命令的反面。

program.option('--no-opposite', 'test --no options') // 取反

program.parse(process.argv);

if(program.opposite) {
  console.log('this is a word');
} else {
  console.log('this is a other word');
}

如上面的代码,我们在执行这部分代码不加任何参数的话,默认情况下program.oppositetrue。则输出this is a word。如果我们执行时带上参数--no-opposite,则会输出this is a other word

关于更多的取反解释,请参考:github.com/tj/commande…

自定义选项(函数)

option()这个方法在选项描述的后面还可以跟上自定义函数和初始值两个参数,如:option(命令,描述,自定义函数,自定义函数的参数的初始或默认值)

函数接收两个参数:用户新输入的参数和当前已有的参数。

function increaseFunc(value, preValue) {
  return preValue + 2;
}

program.option('-a, --add', 'add function', increaseFunc, 100);

program.parse(process.argv);

if(program.add > 100) {
  console.log(`current value: ${program.add}`)
}

执行上面的代码node xxx.js -a, 输出102,执行node xxx.js -a -a, 输出104。

变长参数

地址:github.com/tj/commande…

requiredOption()

这个表示设置的选项为必填,其参数的写法与option()一样。举个例子

program
  .requiredOption('-a, --add <type>', 'add type must have be selected');
  
program.parse(process.argv);

上面这个段代码,在你执行时必须传入对应的type参数,否则命令就会报错。

command()

通过command()可以配置命令,比如我们用的vue cli的create add这些命令就是由command()来处理的。

参数

  • 配置命令名称及参数,command('命令名 <必填参数> [可选参数]'),如:command('create <name> [options]')
  • 配置选项,可选。配置noHelp、isDefault这些参数。当opts.noHelp设置为true时,该命令不会打印在帮助信息里。当opts.isDefault设置为true时,若没有指定其他子命令,则会默认执行这个命令。如:command('create <name> [options]', { noHelp: true, isDefault: true })

声明可变参数

在参数名后加上...来声明可变参数,且只有最后一个参数支持这种用法。

// 声明可变参数,可变参数会以数组的形式传递给处理函数。
program.command('start <name> [options...]')
  .action((name, options) => {
    console.log(name);
    console.log(options);
  })

执行上述代码node xxx.js start test argumrnt, 对应输出为:

test
[ 'argument' ]

执行上述代码node xxx.js start test argumrnt 1 2 3, 对应输出为:

test
[ 'argument', '1', '2', '3' ]

description()

用来描述命令的一些提示、说明性的语句,我们在使用help命令时会打印出这些相关的描述

参数

  • 描述语句,如:description('create a proj')

action()

自定义命令执行后的回调函数。

参数

  • 回调函数,包含的参数是可变的,比如说,你的命令没有定义可选和必选参数,则这个回调函数的第一个参数就是commander对象,否则依次往后推,回调函数最后的参数就是commander对象。比如下面代码:
program.command('start')
  .description('start a commander')
  .action((cmd) => {
    console.log(cmd); // 输出commander对象信息
  })

program.parse(process.argv);

执行上面这个代码node xxx.js start,console输出commander对象。

program.command('start <name>')
  .description('start a commander')
  .action((name, cmd) => {
    console.log(name); // 输出name
    console.log(cmd); // 输出commander对象信息
  })

program.parse(process.argv);
program.command('start <name> [options]')
  .description('start a commander')
  .action((name, options, cmd) => {
    console.log(name); // 输出name
    console.log(options); // 输出name
    console.log(cmd); // 输出commander对象信息
  })

program.parse(process.argv);

arguments()

通过arguments可以为最顶层命令指定参数。如下代码:

// 直接引用官方代码
program
  .version('0.0.1')
  .arguments('<cmd> [env]')
  .description('test command')
  .action(function(cmdValue, envValue) {
    console.log('command:', cmdValue);
    console.log('environment:', envValue || 'no environment given');
  });


program.parse(process.argv);

可以看到上面代码,arguments里面声明了一个可选参数,一个必选参数,当我们执行node xxx.js start dev,则对应输出的command: start environment: dev,执行node xxx.js build production,则对应输出的command: build environment: production。其实arguments这个理解为匹配全局命令的一个声明就可以了。

其他

到这里,基本上常用的语法就基本完成了,在官方文档上还包含一些其他知识,比如独立的可执行(子)命令自定义帮助自定义事件监听等,这里不再赘述了,如果需要了解,请移步官方文档查看:github.com/tj/commande…

最后

通过前面的命令学习,接下来可以书写一个可运行的完整命令来进行操作

const { program } = require('commander');

program
  .version('1.0.0', '-v, --vers')
  .command('start <name> [options...]')
  .description('Test a whole commander order')
  .option('-e --extra [exargs]', 'Test a options')
  .action((name, startOptions, cmd) => {
    // 如果你的可选参数是空的,那么这地方startOptions输出的是空数组
    // console.log(name, startOptions, cmd);
    if(cmd.extra) {
      console.log(`cmd.extra's value is ${ cmd.extra }`);
    } else {
      console.log('no cmd.extra');
    }
    if(startOptions.length > 0) {
      startOptions.forEach(function(item) {
        console.log(item);
      })
    }
  })

program.parse(process.argv);

这段代码我命名为commander-final.js。然后执行以下命令:

  • node commander-final.js -vers
1.0.0
  • node commander-final.js -h
Usage: commander-final [options] [command]

Options:
  -v, --vers                           output the version number
  -h, --help                           display help for command

Commands:
  start [options] <name> [options...]  Test a whole commander order
  help [command]                       display help for command

  • node commander-final.js start -h
Usage: commander-final start [options] <name> [options...]

Test a whole commander order

Options:
  -e --extra [exargs]  Test a options
  -h, --help           display help for command

  • node commander-final.js start mycli dev producrion -e hash
cmd.extra's value is hash
dev
producrion

上面就是对应的命令输出的学习。

到这儿commander入门就算结束了,如有疏漏或错误还请大家多多指教!也欢迎大家关注我的公众号:程序曲奇饼!