关于前端:开发-Nodejs-命令行程序的一些常用工程实践

38次阅读

共计 7155 个字符,预计需要花费 18 分钟才能阅读完成。

介绍

node 开发命令行程序十分不便,咱们罕用的 webpack,babel,http-server,express-generator, yeoman 等等,都是 node.js 开发进去的命令行工具。

原理

Node.js 的程序个别通过 node 指令进行运行。而在 Unix Like 的零碎中,任何一种脚本也能够被零碎当做可执行程序执行。只须要依照如下约定,设置脚本的运行时程序即可,例如对于 Node.js 脚本来说,只须要设置他的运行程序是 node:

#!/usr/bin/env node
console.log('hello world');

这样,该文件就能够间接在 Linux 零碎中输出脚本名进行运行了。Linux 会主动应用 node 来运行该脚本。

假如该脚本文件名为 test, 此时便可在 shell 中间接执行 ./test, 而不须要应用 node ./test. (实际上他们是相等的)。

npm 能够主动生成这种 bash 文件

在 npm 包中,有一种通过 -g 全局装置的包,实际上就是将 npm 包内的 bin 入口 js 主动创立了 /usr/local/bin 上面一个软连贯,连贯到了 npm 装置的该全局包所在位置。如果你也心愿在 /usr/local 下呈现你的可执行文件的话,只须要在 npm 包中将咱们事后写好的入口 js 文件设置为package.json 的 bin 字段即可:

"main": "./lib/echo.js",       # 入口模块地位
"bin" : {"node-echo": "./bin/node-echo"      # 命令行程序名和主模块地位}

其中 node-echo 是一个 JavaScript 编写的 Node.js 代码文件,外面会依照 Unix 格局在顶部表明它须要被 node 来运行。也正因如此,node-echo 咱们就不写扩展名 .js 了。

而后入口文件个别搁置在 package 包目录根目录中的 bin 目录下,如 bin/node-echo 这样写:

#! /usr/bin/env node
require('../src/index.js'); // 也能够写任何你喜爱的逻辑

此时,应用 npm i -g 来装置这个包时,npm 则会主动在 /usr/local/bin 上面生成 node-echo 可执行文件,它是一个软连贯,会链接到:

yourNpmPrefix/node_modules/yourPackageName/bin/node-echo

命令行程序罕用操作

开发命令行程序,有一些根本的套路,比方个别:

  • 输出命令自身 xxxx -h 来展现程序的根本用法(包含 usage, options 列表)
  • 通过输出 -n sheldon--name sheldon 的形式获取用户输出的参数配置 option
  • 通过 -r--recusive 的形式来关上或敞开某个布尔类型的配置 option
  • 通过在最初加 value 参数来获取用户输出的操作对象,如 cp -r aFolder bFolder 中的 aFolder bFolder 就是 value 参数。
  • 通过 xx yy 的形式来反对子命令(yy 是 xx 的子命令)
  • 更高阶一点,遇到某些命令后能够交互式的获取用户输出的一些参数配置,如询问用户要初始化的模板名字是什么等等

对于 options 参数,要记得:如果用户输出 -abc,commander 会当做 -a -b -c 来解析。所以如果你心愿的是 abc 参数的话,请在终端输出 --abc

commander

先来看弱小的 commander

const program = require('commander')
program
  .version('0.1.0')
  .option('-p, --peppers', 'Add peppers')
  .option('-P, --pineapple', 'Add pineapple')
  .option('-b, --bbq-sauce', 'Add bbq sauce')
  .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
  .parse(process.argv);

应用 commaner, 首先要注册上你心愿的 option,最初要调用一下 .parse(process.argv),他才会对过程的入参进行解析并转换为 commander 对象上的属性。

如果是布尔类型的 option(即不须要设置 value),则只须要:

.option('-p, --peppers', 'Add peppers') // 其中第一个参数格局是固定的,第二个参数是 --help 时候要显示的提醒。.option('--no-sauce', 'Remove sauce') // 这是定义了一个 否定用法。即用户如果输出 `--no-sauce`, 则 commander 拿到的 sauce 就是 false

如果是 value 类型的 option,则

  .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble') // 其中第一个引号里要固定加上中括号示意的参数(尖括号示意必填),第二个参数是帮忙文案,第三个参数是 cheese 参数的默认值(如果不须要默认值能够不填)

如果心愿能在 --help 的时候显示用法,只需:

program.usage('[options]')

这样执行命令时便会打印:

  Usage: xxx [options] // 其中 xxx 是你的命令行程序名字,会主动填在后面;前面就是你定义的 usage 字符串

如果心愿拿到用户输出的 value 参数,则能够调用: program.args , commander 会把解析的 value 参数以数组模式搁置在 args 属性上。

如果你的命令行程序有子命令,如 lime init [options] 则能够通过 command 办法增加:

program
  .command('rm <dir>')
  .option('-r, --recursive', 'Remove recursively')
  .action(function (dir, cmd) {console.log('remove' + dir + (cmd.recursive ? 'recursively' : ''))
  }) // 其中 action 回调函数收到的第一个形参为命令中的参数如 dir,最初一个形参是命令的 Command 对象(该对象能够间接拜访来获取相干 option)

command 办法中能够申明命令所需的 value 参数,如果用尖括号包裹 dir, 则 dir 就是必填的;中括号包裹就是选填;也能够申明多个参数。

.command('rmdir <dir> [otherDirs]') // 表明 rmdir 子命令须要 2 个参数,第一个必填,第二个选填
.command('rmdir <dir> [otherDirs...]')
.action(function (dir, otherDirs) {}) // 表明 otherDirs 是可变参数,会以数据模式传递给 action 的 otherDirs 形参。

commander 中还有很多针对参数类型、参数格局的设定的形式,如:

program
  .version('0.1.0')
  .usage('[options] <file ...>')
  .option('-i, --integer <n>', 'An integer argument', parseInt)
  .option('-f, --float <n>', 'A float argument', parseFloat)
  .option('-r, --range <a>..<b>', 'A range', range)
  .option('-l, --list <items>', 'A list', list) // 输出格局为 -l 1,2,3
  .option('-o, --optional [value]', 'An optional value')
  .option('-c, --collect [value]', 'A repeatable value', collect, [])
  .option('-v, --verbose', 'A value that can be increased', increaseVerbosity, 0)
  .parse(process.argv);

或针对选项设定 正则表达式 规定:

program
  .version('0.1.0')
  .option('-s --size <size>', 'Pizza size', /^(large|medium|small)$/i, 'medium')
  .option('-d --drink [drink]', 'Drink', /^(coke|pepsi|izze)$/i) // 输出的 - d 参数必须满足正则,否则会无奈获取到 - d 的输出参数,变成布尔类型。.parse(process.argv);

另外一种设置子命令的形式是通过子文件的形式。

大家能够去参考 commander 官网文档

智能帮忙: 如果你心愿在用户输出 -h 的时候能增加一点本人的货色,能够:

program.on('--help', function(){console.log('')
  console.log('Examples:');
  console.log('$ custom-help --help');
  console.log('$ custom-help -h');
});

如果你心愿在某些场景(例如用户没有输出子命令时),被动打印 help 信息,则能够:

if (program.args.length < 1) {
  // 没有输出子命令,则打印帮忙
  program.outputHelp((txt) => {
    txt += '\n'
    txt += 'Examples: \n\n'
    txt += 'hello'
    return txt
  });
}

yargs 获取命令行参数

获取命令行参数,在很多编程语言中都相似。node 中 process.argv 示意执行该脚本时传入的参数的数组。其中,前两个参数别离就是 node 可执行程序的地位和以后脚本的地位,如我的 mac 上的执行成果如下:

[ '/usr/local/Cellar/node/8.6.0/bin/node',
  '/Users/cuiyongjian/Code/lime-cli/lime-cli/bin/lime' ]
  1. 然而,在命令行程序中,常常须要用户设置命令的 options,如:
node hello --name=tom
node hello -n tom

这时咱们须要获取 shell 中用户输出的 name 或 n 参数,除了本人解析 process.argv 数组外,还可通过第三方包 yargs 来更不便获取这些入参

var argv = require('yargs').argv; // argv 拿到的就是
console.log(argv.n) // yargs.argv 会采集所有输出转为 key-value 对的模式

若想让 n 是 name 的别名,则能够设置 yargs:

var argv = require('yargs')
  .alias('n', 'name')
  .argv;

通过 argv._ 能够获取到所有非 options 的参数,如上面这条命令的 argv._ 的后果就是 ['A', 'B', 'C']:

  hello A -n tom B C
  1. 应用命令行参数时,还须要常常接管 -h 参数展现帮忙信息;对参数必填选填进行校验;布尔性质的 option 等操作。yargs 都具备了这些性能,例如能够设置一个参数的各种个性:
var argv = require('yargs')
  .option('n', {
    alias : 'name',
    demand: true,
    default: 'tom',
    describe: 'your name',
    type: 'string'
  })
  .argv;

此时,就为 argv 设置了一个 n 参数的详细信息,示意其是必填的,且默认值是 “tom”, 形容信息是 “your name”。在命令行中输出 node yourfile -h, 则 argv 会给显示出你配置这些参数应用办法:

选项:--help      显示帮忙信息                                                [布尔]
  --version   显示版本号                                                  [布尔]
  -n, --name  your name                                          [字符串] [必须]
  1. 当然,这还不够,一个残缺的命令行程序应该还要提醒用法(Usage), 例子(Example),所以 yargs 也提供了一个简略的配置阐明和示例的 API:
var argv = require('yargs')
  .option('f', {
    alias : 'name',
    demand: true,
    default: 'tom',
    describe: 'your name',
    type: 'string'
  })
  .usage('Usage: hello [options]')
  .example('hello -n tom', 'say hello to Tom')
  .help('h')
  .alias('h', 'help')
  .epilog('copyright 2015')
  .argv;
Usage: hello [options]

选项:--version   显示版本号                                                  [布尔]
  -f, --name  your name                          [字符串] [必须] [默认值: "tom"]
  -h, --help  显示帮忙信息                                                [布尔]

示例:hello -n tom  say hello to Tom

copyright 2015

这就比拟残缺了。如果 -n 只是作为一个 option 开关来用,则只需将其设置为 boolean 类型即可:

var argv = require('yargs')
  .option('n', {boolean: true})
  .argv;
  1. 子命令怎么办

咱们应用 git 时,常常会用到相似 git remote ... 这样的命令。其 remote 就是 git 的一个子命令,子命令有本人的 option 和输出值,yargs 反对设置子命令。要留神子命令的 option 配置,要在子命令捕捉后的回调函数里进行设置。

require('shelljs/global');
var argv = require('yargs')
  .command("morning", "good morning", function (yargs) {echo("Good Morning");
    var argv = yargs.reset()
      .option("m", {
        alias: "message",
        description: "provide any sentence"
      })
      .help("h")
      .alias("h", "help")
      .argv;

    echo(argv.m);
  })
  .argv;

应用 inquirer 与用户进行交互

inquirer 能够交互式的提醒用户输出某些参数。

inquirer.prompt([
      {
        type: 'confirm',
        name: 'destOk',
        message: '确认应用指标文件夹:' + destFolder
      }
    ]).then(function(answers){

子过程运行其余命令

有时咱们在命令行程序里须要调用其余 bash 命令,这时能够通过 node 自带的子过程模块,能够执行任意的 shell 程序,并异步接管后果:

#!/usr/bin/env node
var name = process.argv[2];
var exec = require('child_process').exec;

var child = exec('echo hello' + name, function(err, stdout, stderr) {if (err) throw err;
  console.log(stdout);
});

有个第三方包 shelljs, 能够利用其 API 进行各种 Unix 命令操作, 例如:

shelljs.rm(@params, @destinationFile)

shelljs 还有全局 API 模式,能够将一些 linux 命令 API 设置在 node 全局空间内,如:

require('shelljs/global');

if (!which('git')) {echo('Sorry, this script requires git');
  exit(1);
}

mkdir('-p', 'out/Release');
cp('-R', 'stuff/*', 'out/Release');

最好不要应用全局模式咯,毕竟笼罩掉全局空间内的 API 是不太好。

字符图片

为了让你的命令行程序 高大上 ,你可能须要一点 字符图片 作为 logo。能够装置这个 node 程序:

npm install -g figlet-cli

而后执行:

figlet "LIME TOOL"

生成的字符图片如下:

  _     ___ __  __ _____   _____ ___   ___  _
 | |   |_ _|  \/  | ____| |_   _/ _ \ / _ \| |
 | |    | || |\/| |  _|     | || | | | | | | |
 | |___ | || |  | | |___    | || |_| | |_| | |___
 |_____|___|_|  |_|_____|   |_| \___/ \___/|_____|

还能够吧老妹

其余常用工具

colors 一个批改 shell 中打印字符的色彩的工具。
chalk 也是一个色彩库
ora 一个能在 shell 内产生动静 loading 成果的库
rimraf 一个简略的 rm 删除库
conf 一个能够用来存储配置的库,有了它就不必本人写 json 来存储一些配置信息啦
cli-table 一个能在命令行打印表格的模块
boxen 能够在终端命令行内生成字符框框,霎时高大上咯

调试

本人在本地调试 node 命令行程序时,能够采纳 npm link 的形式把以后包 软链 到全局。如果是调试一个本地包也能够把它 link 到部分。应用形式非常简单,能够参考这篇文章: https://github.com/atian25/bl…

我的示例

  • merge-file 能够基于二进制进行 2 个文件的无脑合并
  • lime-cli 青檬脚手架~ 能够用此工具生成各种罕用的前端我的项目脚手架,例如 webapp 我的项目、前端库我的项目等。

正文完
 0