是的,但是有质的区别。使用 node index.js 这种方式调用的话固然简单灵活,但是严重依赖脚本路径,一旦目录结构发生变动,写在scripts的命令就要更改一次;但是使用npm安装之后,本地的cli脚本就被拉到node_modules里面,目录结构变动对其影响不大。其次是不利于分享与发布,如果你想把你的cli脚本发布出去,那么有一个好听响亮的名字,比起在说明文档里面告诉使用者如何找到你的脚本路径再用node执行它,简直好上那么一万倍不是么?
这里也给我们提供了一个cli开发流程思路:
初期开发可以通过node index.js来看效果。
测试的时候可以通过npm link的方式进行安装测试。
发布
0x3 参数读取:process.argv
名字有了,输出也有了,看看我们跟那些大名鼎鼎的cli工具,在形式上还差点啥?对了,人家可以支持不同参数选项的,还可以根据输入的不同,产生不同的结果。
这样吧,我们给这个cli加一个功能,既然叫 hello-cli ,那不能只会 hello world 吧,必须要见谁就说 hello 才行:
> hello-cli older ## 输出 > hello older
虽然这个功能很简单,但是至少也是实现了“根据输入的不同,产生不同结果”的效果。
命令行上的参数,可以通过 process 这个变量获取, process 是一个全局对象而不是一个包,不需要通过 require 引入。通过 process 这个对象我们可以拿到当前脚本执行环境等一系列信息,其中就包括命令行的输入情况,这个信息,保存在 process.argv 这个属性里。我们可以打印一下:
//index.js console.log(process.argv);
打印结果:
可以看出,argv是个数组,前两位是固定的,分别是node程序的路径和脚本存放的位置,从第三位开始才是额外输入的内容。那么实现上面的功能就很简单了,只要读取argv数组的第三位,然后输出出来就可以了。
//index.js console.log(`hello ${process.argv[2]||'world'}`)
npm社区中也有一些优秀的命令行参数解析包,比如yargs ,tj的commander.js 等等
如果你想使用比较复杂的参数或者命令,建议还是用第三方包比较好,手写解析太耗精力了。
0x4 子进程
现在,你可以自由自在的写你自己的cli脚本了。
如果你希望写一个项目打完包自动推上git的cli,或者自动从git仓库里面拉取项目启动模板,那么,你需要通过node的 child_process 模块开启子进程,在子进程内调用git命令:
//test.js const child_process = require('child_process'); let subProcess=child_process.exec("git version",function(err,stdout){ if(err)console.log(err); console.log(stdout); subProcess.kill() });
不仅是git命令,包括系统命令、其他cli命令都可以在这里执行。特别是系统命令,使用系统命令对文件目录进行操作,效率比fs高到不知道哪里去了。
社区上也有一些不错的包,比如阮一峰老师推荐的shelljs
0x5 美化输出
如果你不那么希望你的cli用起来那么“硬核”,希望更人性化一点,比如提供一些友好的输入、提示啊,给你的输出加点颜色区分重点啊,写个简单的进度条啊等等,那么你就需要美化一下你的输出了。
除了颜色这部分,不使用第三方包实现起来非常繁琐复杂,其他的功能,都可以试试自己写。
颜色部分使用了第三方包 colors ,这里就不演示了。
其他都是由nodejs自带的readline模块实现的。
//index.js const readline = require('readline'); const unloadChar='-'; const loadedChar='='; const rl=readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question('你想对谁说声hello? ',answer=>{ let i = 0; let time = setInterval(()=>{ if(i>10){ clearInterval(time); readline.cursorTo(process.stdout, 0, 0); readline.clearScreenDown(process.stdout); console.log(`hello ${answer}`); process.exit(0) return } readline.cursorTo(process.stdout,0,1); readline.clearScreenDown(process.stdout); renderProgress('saying hello',i); i++ },200); }); function renderProgress(text,step){ const PERCENT = Math.round(step*10); const COUNT = 2; const unloadStr = new Array(COUNT*(10-step)).fill(unloadChar).join(''); const loadedStr = new Array(COUNT*(step)).fill(loadedChar).join(''); process.stdout.write(`${text}:【${loadedStr}${unloadStr}|${PERCENT}%】`) }