前端架构: 脚手架框架之commander从基础到高级应用教程

commander


1 )概述

  • commander 是一个更为知名的脚手架框架
  • 进入它的npm官网: https://www.npmjs.com/package/commander
  • 目前版本: 12.0.0
  • Weekly Downloads 133,822,797 (动态数据)
  • 最近更新:15 days ago (npm)
  • 说明这是一个更优质的库
  • 同时使用commander的案例也更为知名:vue-cli,webpack-cli,create-react-app
  • 所以commander是我们开发脚手架过程中首选的框架

2 )快速实现一个 commander 脚手架

  • 仍旧在 xyzcli 这个脚手架项目之下,之前是使用 yargs, 这时候把 bin/index.js 修改成 bin/yargs.js (用于备份)
  • 进行安装 $ npm i commander -S
  • 新建 bin/index.js 并编写
    #!/usr/bin/env node
    
    const pkg = require('../package.json')
    const commander = require('commander');
    // const { program } = commander; // 脚手架实例 program是一个单例
    const program = new commander.Command(); // 这种手动实例化单例,同上,二取一
    
    program
      .version(pkg.version) // 设定版本
      .parse(process.argv) // 解析所有参数
    
  • 执行 $ xyzcli --version, 并查看结果
    1.0.0
    
  • 执行 $ xyzcli --help, 并查看结果
    Usage: xyzcli [options]
    
    Options:
      -V, --version  output the version number
      -h, --help     display help for command
    
  • 这时候,使用 commander 完成了最小粒度的脚手架
  • 这时候,可见,-V, -h 这种别名短称都已经默认给出了

3 )commander 通用全局配置

3.1 配置 usage

const program = new commander.Command(); // 这种手动实例化单例,同上,二取一

program
	.usage(' [option]')
	.version(pkg.version) // 设定版本
	.parse(process.argv) // 解析所有参数
  • 执行 $ xyzcli -h, 查看输出
    Usage: xyzcli <command> [option]
    
    Options:
      -V, --version  output the version number
      -h, --help     display help for command
    
    • 可以看到 Usage: xyzcli [option]

3.2 配置 name

const program = new commander.Command(); // 这种手动实例化单例,同上,二取一

program
	.name(pkg.name)
	.usage(' [option]')
	.version(pkg.version) // 设定版本
	.parse(process.argv) // 解析所有参数
  • 这里修改下 package.json 中的 name,比如修改成 xyzcli111

  • 执行 $ xyzcli -h, 查看输出

    Usage: xyzcli111 <command> [option]
    
    Options:
      -V, --version  output the version number
      -h, --help     display help for command
    
    • 注意这里的,Usage: xyzcli111 [option] 变成了 xyzcli111
    • 验证完成,将 package.json 中的 name 从 xyzcli111 再修改回 xyzcli
  • 在实际应用中,我们会取 bin 属性中的第一个key (一般而言,bin里的配置和包名一致, 但是最好取bin的第一个key值)

    const program = new commander.Command();
    
    program
      // .name(pkg.name)
      .name(Object.keys(pkg.bin)[0]) // 注意这里
      .usage(' [option]')
      .version(pkg.version) // 设定版本
      .parse(process.argv) // 解析所有参数
    
  • 这里不做输出测试

3.3 配置 option

const program = new commander.Command(); // 这种手动实例化单例,同上,二取一

program
	// .name(pkg.name)
	.name(Object.keys(pkg.bin)[0])
	.usage(' [option]')
	.version(pkg.version) // 设定版本
	.option('-f, --first', '第一个')
    .option('-s, --separator ', '使用分隔符分割', ',')
	.option('-d, --debug', '开启调试模式', false)
	.option('-e, --envName ', '获取环境变量名称')
	.parse(process.argv) // 解析所有参数
  • 执行 $ xyzcli -h, 查看输出
    Usage: xyzcli <command> [option]
    
    Options:
      -V, --version            output the version number
      -f, --first              第一个
      -s, --separator <char>   使用分隔符分割 (default: ",")
      -d, --debug              开启调试模式 (default: false)
      -e, --envName <envName>  获取环境变量名称
      -h, --help               display help for command
    
  • 对参数进行解析和判断处理
    const program = new commander.Command(); // 这种手动实例化单例,同上,二取一
    
    program
      // .name(pkg.name)
      .name(Object.keys(pkg.bin)[0])
      .usage(' [option]')
      .version(pkg.version) // 设定版本
      .option('-f, --first', '第一个') // 选择第一个参数
      .option('-s, --separator ', '使用分隔符分割', ',')
      .option('-d, --debug', '开启调试模式', false)
      .option('-e, --envName ', '获取环境变量名称')
      .parse(process.argv) // 解析所有参数
    
    const options = program.opts(); // 获取所有可用的 opt 作为参数
    const limit = options.first ? 1 : undefined; // 存在第一个参数, 则选1,不存在则不定义
    console.log(program.args[0].split(options.separator, limit)); // 基于分隔符分割获取参数
    
  • 执行,$ xyzcli -f x,y,z,查看输出结果
    [ 'x' ]
    
  • 执行,$ xyzcli -s / -f o/p/q, 查看输出结果
    [ 'o' ]
    

4 ) 配置 command 命令

  • 大类上还分两种小类型
    • 调用 command api 注册命令
    • 调用 addCommand api 注册命令

4.1 command

const program = new commander.Command(); // 这种手动实例化单例,同上,二取一

program
	// .name(pkg.name)
	.name(Object.keys(pkg.bin)[0])
	.usage(' [option]')
	.version(pkg.version) // 设定版本
	.option('-f, --first', '第一个') // 选择第一个参数
    .option('-s, --separator ', '使用分隔符分割', ',')
	.option('-d, --debug', '开启调试模式', false)
	.option('-e, --envName ', '获取环境变量名称')


// 定义一个克隆的命令
const clone = program.command('clone');
clone
  .description('克隆仓库')
	.action(() => {
		console.log('do clone');
	});

program.parse(process.argv) // 解析所有参数, 注意这里的调用位置
  • 直接执行 $ xyzcli, 并输出
    Usage: xyzcli  [option]
    
    Options:
      -V, --version            output the version number
      -f, --first              第一个
      -s, --separator    使用分隔符分割 (default: ",")
      -d, --debug              开启调试模式 (default: false)
      -e, --envName   获取环境变量名称
      -h, --help               display help for command
    
    Commands:
      clone                    克隆仓库
      help [command]           display help for command
    
    • 这里可以看到存在两个 command: clone 和 help
  • 执行 $ xyzcli clone -h, 并输出
    Usage: xyzcli clone [options]
    
    克隆仓库
    
    Options:
      -h, --help  display help for command
    
  • 执行 $ xyzcli clone, 并输出
    do clone
    
  • 对 clone 命令进行扩展
    // 定义一个克隆的命令
    const clone = program.command('clone  [destination]');
    clone
      .description('克隆仓库')
      .action((source, destination) => {
        console.log('do clone: ', source, destination);
      });
    
    program.parse(process.argv) // 解析所有参数, 注意这里的调用位置
    
  • 执行 $ xyzcli clone, 并输出
    error: missing required argument 'source'
    
    • 可见source是必传项
    • 而 destination 是可选项
  • 执行 $ xyzcli clone ss dd, 并输出
    do clone:  ss dd
    
  • 在clone命令中添加针对该命令的option
    // 定义一个克隆的命令
    const clone = program.command('clone  [destination]');
    clone
      .description('克隆仓库')
      .option('--fc, --force', '是否强制克隆', false)
      .action((source, destination, cmdObj) => {
        console.log('do clone: ', source, destination, cmdObj.force);
      });
    
    program.parse(process.argv) // 解析所有参数, 注意这里的调用位置
    
  • 执行 $ xyzcli clone ss dd, 并输出
    do clone:  ss dd false
    
  • 执行 $ xyzcli clone ss dd --fc, 并输出
    do clone:  ss dd true
    
    • 注意,这里简称不能与前面定义的冲突,如果是多个字符,用 --, 不能用一个 -
    • 这里 --fc--force
  • 执行 $ xyzcli clone --force ss dd, 并输出
    do clone:  ss dd true
    
    • 注意这里更换了option的位置,只要在 clone 后面,就不会受到影响

4.2 addCommand

  • addCommand 的特殊之处是可以注册子命令
    // 定义service脚手架
    const service = new commander.Command('service');
    service
      .command('start [port]')
      .description('start service at some port')
      .action((port) => {
        console.log('service port @', port)
      })
    
    program.addCommand(service);
    program.parse(process.argv) // 解析所有参数, 注意这里的调用位置
    
  • 执行 $ xyzcli -h, 查看输出
    Usage: xyzcli  [option]
    
    Options:
      -V, --version                           output the version number
      -f, --first                             第一个
      -s, --separator                   使用分隔符分割 (default: ",")
      -d, --debug                             开启调试模式 (default: false)
      -e, --envName                  获取环境变量名称
      -h, --help                              display help for command
    
    Commands:
      clone [options]  [destination]  克隆仓库
      service
      help [command]                          display help for command
    
  • 执行 $ xyzcli service -h, 查看输出
    Usage: xyzcli service [options] [command]
    
    Options:
      -h, --help      display help for command
    
    Commands:
      start [port]    start service at some port
      help [command]  display help for command
    
    • 这里看到 service 命令下存在一个子命令 start
  • 可以看到,这种方式可以对命令进行分组,比如在 service 下 有start, 还可以有stop
    // 定义service脚手架
    const service = new commander.Command('service');
    service
      .command('start [port]')
      .description('start service at some port')
      .action((port) => {
        console.log('service port @', port)
      })
    
    service
      .command('stop')
      .description('stop service')
      .action((port) => {
        console.log('stop service')
      })
    
    program.addCommand(service);
    program.parse(process.argv) // 解析所有参数, 注意这里的调用位置
    
  • 执行 $ xyzcli service -h, 查看输出
    Usage: xyzcli service [options] [command]
    
    Options:
      -h, --help      display help for command
    
    Commands:
      start [port]    start service at some port
      stop            stop service
      help [command]  display help for command
    
  • 执行 $ xyzcli service stop, 查看输出
    stop service
    
  • 通过子命令功能可大大扩展脚手架功能
  • 注意,分组下的 子命令必须如上分开写,不能连写

4.3 )对命令注册进行自动匹配

  • 可以通过 program.arguments 来监听所有命令输入
  • 除了上面注册的命令,其他命令都会命中这里的 arguments 中
    // 命令匹配
    program
      .arguments(' [options]')
      .description('test command')
      .action((cmd, options)=> {
        console.log(cmd, options);
      })
    
    program.parse(process.argv) // 解析所有参数, 注意这里的调用位置
    
  • 执行 $ xyzcli test, 查看输出
    test undefined
    
  • 执行 $ xyzcli, 查看输出
    error: missing required argument 'cmd'
    
    • 可以看到,这里 cmd 是要求强制输入的
  • 执行 $ xyzcli tt ss, 查看输出
    tt ss
    
  • 执行 $ xyzcli -h, 查看输出
    Usage: xyzcli  [option]
    
    test command
    
    Options:
      -V, --version                           output the version number
      -f, --first                             第一个
      -s, --separator                   使用分隔符分割 (default: ",")
      -d, --debug                             开启调试模式 (default: false)
      -e, --envName                  获取环境变量名称
      -h, --help                              display help for command
    
    Commands:
      clone [options]  [destination]  克隆仓库
      service
    
    • 可以看到这里没有什么信息,只有 一个 test command 的描述
  • 执行 $ xyzcli test -h, 同样也看不到其他实用的信息
  • 现在,对其再次进行修改,测试
    // 命令匹配
    program
      .arguments(' [options]')
      .description('test command', {
        cmd: 'command to run',
        options: 'options for command'
      })
      .action((cmd, env)=> {
        console.log(cmd, env);
      })
    
    program.parse(process.argv) // 解析所有参数, 注意这里的调用位置
    
  • 执行 $ xyzcli test -h, 查看输出结果
    Usage: xyzcli  [option]
    
    test command
    
    Arguments:
      cmd                                     command to run
      options                                 options for command
    
    Options:
      -V, --version                           output the version number
      -f, --first                             第一个
      -s, --separator                   使用分隔符分割 (default: ",")
      -d, --debug                             开启调试模式 (default: false)
      -e, --envName                  获取环境变量名称
      -h, --help                              display help for command
    
    Commands:
      clone [options]  [destination]  克隆仓库
      service
    
  • 可以看到,这里多了 Arguments 这一项,里面的描述配置都有说明
  • 它的强大之处在于,可以匹配到所有输入的命令, 同时强制必须传 cmd 命令
  • 这个功能和在yargs框架中的 demandCommand 功能类似
  • 还有一种方式,也同样强大,如下
    program
      .command('install [name]', 'install package')
    
    program.parse(process.argv) // 解析所有参数, 注意这里的调用位置
    
  • 执行 $ xyzcli -h, 查看输出结果
    Usage: xyzcli  [option]
    
    test command
    
    Arguments:
      cmd                                     command to run
      options                                 options for command
    
    Options:
      -V, --version                           output the version number
      -f, --first                             第一个
      -s, --separator                   使用分隔符分割 (default: ",")
      -d, --debug                             开启调试模式 (default: false)
      -e, --envName                  获取环境变量名称
      -h, --help                              display help for command
    
    Commands:
      clone [options]  [destination]  克隆仓库
      service
      install [name]                          install package
    
    • 可以看到,最后的 install
  • 如果执行 $ xyzcli install -h, 查看发现报错
    • 报错信息为:Error: 'xyzcli-install' does not exist
    • 它会把当前脚手架 xyzcli 加上 -install 组成一个新的命令 xyzcli-install
    • 而这个命令在我们电脑上是没有的
    • 这个命令有些像 $ npm init install 当输入这个方法的时候也会报错
    • 它会告诉你,'create-install@latest' is not in the npm registry.
    • 这是因为,npm init 命令后面加上参数时,比如 加上 abc, 就是 npm init abc
    • 默认会在前面加上 create, 然后找这个包,也就是 create-abc 这个包
    • 如果这个包存在,则动态下载并执行这个包, 这块了解即可
  • 还可以加上别名
    program
      .command('install [name]', 'install package')
      .alias('i')
    
    program.parse(process.argv) // 解析所有参数, 注意这里的调用位置
    
  • 执行 $ xyzcli -h, 查看输出结果
    Usage: xyzcli  [option]
    
    test command
    
    Arguments:
      cmd                                     command to run
      options                                 options for command
    
    Options:
      -V, --version                           output the version number
      -f, --first                             第一个
      -s, --separator                   使用分隔符分割 (default: ",")
      -d, --debug                             开启调试模式 (default: false)
      -e, --envName                  获取环境变量名称
      -h, --help                              display help for command
    
    Commands:
      clone [options]  [destination]  克隆仓库
      service
      install|i [name]                        install package
    
    • 可以看到上述别名起作用了
  • 同时,还可以添加第三个options参数
    program
      .command('install [name]', 'install package', {
        executableFile: 'npm', // 手动修改可执行文件
      })
      .alias('i')
    
    program.parse(process.argv) // 解析所有参数, 注意这里的调用位置
    
  • 这时候我们执行 $ xyzcli i i vue -S
    • 这里的 xyzcli i 或 xyzcli install 等价于 npm
    • 所以,这时候,xyzcli i i vue -S 等价于 npm i vue -S
    • 执行后,可见 package.json 中存在了 vue
    • 这里很奇怪的是: 两个 i, 但是这个问题不大
  • 还有一些其他的配置,比如:
    program
      .command('install [name]', 'install package', {
        executableFile: 'npm', // 手动修改可执行文件
        isDefault: true, // 默认会匹配这个
        // hidden: true, // 不再帮助信息中显示
      })
      .alias('i')
    
    program.parse(process.argv) // 解析所有参数, 注意这里的调用位置
    
    • 这里 isDefault 设置为 true时,当执行 $ xyzcli 就直接会匹配到 npm 命令的帮助信息
    • 这里 hidden 设置为 true时,在帮助信息中就看不到 install 命令了

全部example参考

#!/usr/bin/env node

const pkg = require('../package.json')
const commander = require('commander');
// const { program } = commander; // 脚手架实例 program是一个单例
const program = new commander.Command(); // 这种手动实例化单例,同上,二取一

program
	// .name(pkg.name)
	.name(Object.keys(pkg.bin)[0])
	.usage(' [option]')
	.version(pkg.version) // 设定版本
	.option('-f, --first', '第一个') // 选择第一个参数
  	.option('-s, --separator ', '使用分隔符分割', ',')
	.option('-d, --debug', '开启调试模式', false)
	.option('-e, --envName ', '获取环境变量名称')


// 定义一个克隆的命令
const clone = program.command('clone  [destination]');
clone
	.description('克隆仓库')
	.option('--fc, --force', '是否强制克隆', false)
	.action((source, destination, cmdObj) => {
		console.log('do clone: ', source, destination, cmdObj.force);
	});

// 定义service脚手架
const service = new commander.Command('service');
service
	.command('start [port]')
	.description('start service at some port')
	.action((port) => {
		console.log('service port @', port)
	})

service
	.command('stop')
	.description('stop service')
	.action((port) => {
		console.log('stop service')
	})

program.addCommand(service);

// 设定 install 命令
program
	.command('install [name]', 'install package', {
		executableFile: 'npm', // 手动修改可执行文件
		// isDefault: true,
		// hidden: true,
	})
	.alias('i')

// 命令匹配,用于兜底
program
	.arguments(' [options]')
	.description('test command', {
		cmd: 'command to run',
		options: 'options for command'
	})
	.action((cmd, env)=> {
		console.log(cmd, env);	
	})

program.parse(process.argv) // 解析所有参数, 注意这里的调用位置

你可能感兴趣的:(FE,Architecture,前端,前端框架)