本文带你了解创建一个 Node-CLI 工具所需知识点.
一, 命令行参数解析
在 Node.JS 中可以通过以下代码获取命令行中传递的参数:
process.argv.slice(2)
但是这对于构建一个 CLI 工具远远不够, 首先需要考虑参数输入的各种风格:
Unix 参数风格: 前面加 -, 不过后面跟的是单个字符, 例如 - abc 解析为 ['a', 'b', 'c'].
GNU 参数风格: 前面加 --, 例如 NPM 中的命令, NPM --save-dev webpack.
BSD 参数风格: 前面不加修饰符.
这里可以通过正则表达式对 process.argv 进行加工:
- /**
- * 解析 Unix,BSD 和 GNU 参数风格
- * @param {Array} argv 命令行参数数组
- * @returns
- */
- function parseArgv (argv) {
- const max = argv.length
- const result = {
- _: []
- }
- for (let i = 0; i <max; i++) {
- const arg = argv[i]
- const next = argv[i + 1]
- if (/^--.+/.test(arg)) {
- // GNU 风格
- const key = arg.match(/^--(.+)/)[1]
- if (next != null && !/^-.+/.test(next)) {
- result[key] = next
- i++
- } else {
- result[key] = true
- }
- } else if (/^-[^-]+/.test(arg)) {
- // Unix 风格
- const items = arg.match(/^-([^-]+)/)[1].split('')
- for (let j = 0, max = items.length; j < max; j++) {
- const item = items[j]
- // 非字母不解析
- if (!/[a-zA-Z]/.test(item)) {
- continue
- }
- if (next != null && !/^-.+/.test(next) && j === max - 1) {
- result[item] = next
- i++
- } else {
- result[item] = true
- }
- }
- } else {
- // BSD 风格
- result._.push(arg)
- }
- }
- return result
- }
通过以上的方法可以得到如下结果:
- node example1.JS --save-dev -age 20 some
- // => 结果
- {
- _: ['some'],
- 'save-dev': true,
- a: true,
- g: true,
- e: 20
- }
上面这个示例不仅仅为了展示解析的结果, 而且还强调了 Unix 参数风格只解析单个字母, 所以这种风格的参数可能表达的意思不太明确并且数量有限, 那么就需要在正确的场景中使用这种风格的参数:
- NPM --save-dev webpack
- NPM -D webpack
NPM 中采用 Unix 参数风格表示简写, 这就是一种很恰当的方式, 那么前面示例中的 - age 按照语义应该改为 --age 更加合理一点.
二, 命令行界面
Node.JS 中的 readline 模块提供 question 和 prompt 方法构建命令行界面, 下面是一个简单的问答式的交互界面:
- const readline = require('readline');
- const question = ['请输入您的姓名', '请输入您的年龄']
- const result = []
- const rl = readline.createInterface({
- input: process.stdin,
- output: process.stdout,
- prompt: `?${question[0]} `
- });
- rl.prompt();
- rl.on('line', (line) => {
- result.push(line.trim())
- const max = result.length
- if (max === question.length) {
- rl.close()
- }
- rl.setPrompt(`?${question[max]} `)
- rl.prompt();
- }).on('close', () => {
- console.log(` 谢谢参与问答 *** 姓名: ${result[0]} 年龄: ${result[1]}`);
- process.exit(0);
- });
当然交互界面的元素并不只有这一种, 在使用各类 CLI 工具时, 你应该会遇到诸如: 单项选择, 下载进度条...
下面可以尝试实现一个单项选择交互界面:
- const readline = require('readline')
- let selected = 0
- const choices = ['javascript', 'CSS', 'html']
- let lineCount = 0
- const rl = readline.createInterface(process.stdin, process.stdout)
- function reader () {
- let str = ''
- for (let i = 0; i <choices.length; i++) {
- lineCount++
- str += `${selected === i ? '[X]' : '[ ]'} ${choices[i]}\r\n`
- }
- process.stdout.write(str)
- }
- reader()
- process.stdin.on('keypress', (s, key) => {
- const name = key.name
- const max = choices.length
- if (name === 'up' && selected> 0) {
- selected--
- } else if (name === 'down' && selected <max - 1) {
- selected++
- } else if (name === 'down' && selected === max - 1) {
- selected = 0
- } else if (name === 'up' && selected === 0) {
- selected = max - 1
- } else {
- return true
- }
- // 移动光标, 并且删除光标右边的内容
- readline.moveCursor(process.stdout, 0, -lineCount)
- readline.clearLine(process.stdout, -1)
- lineCount -= choices.length
- reader()
- })
- rl.on('line', () => {
- console.log(`you choose ${choices[selected]}`)
- process.exit(0)
- }).on('close', () => {
- rl.close()
- })
三, 定制样式
为了有效的区别命令行界面中信息的差异性, 我们可以为这里输出信息添加适当的样式.
这里介绍一下字符串添加样式的语法:
\x1b[背景颜色编号; 字体颜色编号 m
每条样式都要以 \ x1b[开头:
- // \x1b[0m 清除样式
- process.stdout.write('\x1b[44;37m OK \x1b[0m just do it\n')
四, 自定义 Node 命令
接下来就是自定义 Node 命令, 首先需要创建一个命令执行的文件:
- // hello.JS 首行需要指定脚本的解释程序
- #!/usr/bin/env node
- console.log('hello')
再利用 package.JSON 中的 bin 配置:
- {
- "bin": {
- "hello": "./hello.js"
- },
- }
执行 NPM 的 link 命令:
- NPM link
- # 输入自定义命令
- hello
- # 输出 hello
五, 总结
上面介绍了开发 Node-CLI 时所需要的一些基本知识, 但是对于用过诸如 webpack-cli,vue-cli 工具的你可能会发现这些优秀的 CLI 工具还具有:
Git 风格的子命令;
自动化的帮助信息;
来源: https://juejin.im/post/5bc496196fb9a05d0f170694