手把手教你如何使用nodejs编写cli命令行


Posted in NodeJs onNovember 05, 2018

前端日常开发中,会遇见各种各样的cli,比如一行命令帮你打包的webpack,一行命令帮你生成vue项目模板的vue-cli,还有创建react项目的create-react-app等等等等。这些工具极大地方便了我们的日常工作,让计算机自己去干繁琐的工作,而我们,就可以节省出大量的时间用于学习、交流、开发、 逛steam

但是有时候一些十分特别的需求,我们是找不到适合的cli工具去做的。比如说,你的项目十分庞大,你给项目添加一个新的路由,要经过 创建目录 -> 创建.vue文件 -> 更新vue-router的路由列表 这一趟流程,就算快捷键创建目录文件用得再熟悉,也比不过你一行命令来得快,特别是路由目录嵌套深,.vue文件初始化模板复杂的时候。

所以呢,何不为自己项目写一个cli?就专门做这些繁琐的活?

0x1 hello world

nodejs的cli,本质就是跑node脚本嘛,基本上每位前端er都会:

// index.js
console.log('hello world')

然后命令行调用

> node index.js

## 输出:
> hello world

可以做得更逼真一点,我们在package.json里面的scripts字段上添加一下脚本名:

{
 "scripts":{
  "hello":"node index.js"
 }
}

然后命令行调用:

> npm run hello

手把手教你如何使用nodejs编写cli命令行

但是,看到这里你肯定会说,人家webpack还有vue-cli都是“有名字”的!什么 vue-cli init appwebpack -p 的,多漂亮,看看这个命令行, node index.js ,还 npm run hello ,谁不会啊,丑不拉几的,怕又不是来水文章的哦?差评!!

别急啊各位大人,接下来就说说,如何给这个node脚本起个名字。

0x2 起名字

姑且,先把这个cli的名字命名为 hello-cli ,就是我们能够在命令行里面,输入 hello-cli ,然后它就打印一句 hello world ,没有 node 也没有 npm ,就是:

手把手教你如何使用nodejs编写cli命令行

这里,我们需要做几步操作:

1、index.js文件顶部声明执行环境:

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

添加 #!/usr/bin/env node 或者 #!/usr/bin/node ,这是告诉系统,下面这个脚本,使用nodejs来执行。当然,这个系统不包括windows,因为windows下有个JScript的历史遗留物在,会让你的脚本跑不起来。

#!/usr/bin/env node 的意思是让系统自己去找node的执行程序。

#!/usr/bin/node 的意思是,明确告诉系统,node的执行程序在路径为 /usr/bin/node

2、添加package.json的bin字段。

可以在index.js当前的目录下执行 npm init 创建一个package.json,然后在package.json里面,添加一个bin字段:

{
  "name": "hello-test",
  "version": "1.0.0",
  "bin":{
   "hello-cli":"index.js" 
  }
}

bin字段里面写上这个命令行的名字,也就是 hello-cli ,它告诉npm,里面的js脚本可以通过命令行的方式执行,以 hello-cli 的命令调用。当然命令行的名字你想写什么都是你的自由,比如:

手把手教你如何使用nodejs编写cli命令行

手把手教你如何使用nodejs编写cli命令行

3、 在当前package.json目录下,打开命令行工具,执行 npm link ,将当前的代码在npm全局目录下留个快捷方式。

npm检测到package.json里面存在一个bin字段,它就同时在全局npm包目录下生成了一个可执行文件:

手把手教你如何使用nodejs编写cli命令行

当我们在系统命令行直接执行 hello-cli 的时候,实际上就是执行这里的脚本。

因为安装node的时候,npm将这个目录配置为系统变量环境了,当你执行命令的时候,系统会先找系统命令和系统变量,然后到变量环境里面去查找这个命令名,然后找到这个目录后,发现匹配上了该命令名的可执行文件,接着就直接执行它。vue-cli也好,webpack-cli也好,都是这样执行的。

这样,你的第一个cli脚本就成功安装了,可以在命令行里面,直接敲你的cli名字,看看结果输出吧。

另外,如果你仅希望你的cli脚本仅在项目里执行,则需要在你项目里面新建一个目录,重复上述的操作,只是在第三步的时候,不要llink到全局里面去,而是使用 npm i -D file:<你的脚本cli目录路径> ,把它当成项目的依赖安装到node_modules里面去,如果安装成功,那么在项目的package.json你会看到多了一条依赖,这条依赖的值不是版本号,而是你脚本的路径。然后在node_modules里面会有一个.bin目录,里面就存放着你的可执行文件。

局部安装建议用 npm i -D file:xxx ,这样它会在package.json留条记录,方便其他小伙伴看到。自然,你的脚本最好也是放进项目目录里面。

当然,这样安装的cli脚本,必须在项目的package.json的scripts字段上声明脚本命令,然后通过 npm run 的方式执行。

手把手教你如何使用nodejs编写cli命令行

哦?这样子使用的话不就回到最最最开始的时候那种原始的 npm run hello 一样么。

是的,但是有质的区别。使用 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);

打印结果:

手把手教你如何使用nodejs编写cli命令行

可以看出,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用起来那么“硬核”,希望更人性化一点,比如提供一些友好的输入、提示啊,给你的输出加点颜色区分重点啊,写个简单的进度条啊等等,那么你就需要美化一下你的输出了。

手把手教你如何使用nodejs编写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}%】`)
}

首先,通过 readline.createInterface 方法创建一个 interface 类 ,这个类下面有一个方法 .question ,用这个方法在命令行上抛出一个问题,在第二个参数传入一个函数进行监听。一旦用户输入完毕敲下回车,就会触发回调函数。

然后我们在回调函数里面写了个计时器,假装我们在处理某些事务。

使用 readline.cursorTo 这个方法,可以改变命令行上的光标的位置。

readline.cursorTo(process.stdout, 0, 0); 是移动到第1列第1行上,

readline.cursorTo(process.stdout, 0, 1); 是移动到第1列第2行上。

使用 readline.clearScreenDown 这个方法,是让命令行从当前行开始,到最后一行结束,将这两行之间所有内容清除。

renderProgress 是自己封装的一个方法,通过 process.stdout.write 方法输出一行看起来像是进度条的字符串到命令行上。

所以在计时器里面,当计数小于10的时候,我们让光标移到第一行上,然后清除所有输出,输出进度条字符串;当计数大于10的时候,我们关掉计时器,清除输出,打印结果。

最后不要忘记关掉进程,可以使用 interface 这个类的 .close 方法关掉readline进程,也可以直接调用 process.exit 退出。

绘制的思路跟canvas绘制动画一样,只不过canvas是清除画布,而命令行这里是通过 readline.clearScreenDown 清除输出。

这样,一个简易的,人性化的,带点点进度条动画的命令行cli工具就写好了,你也可以发挥你的想象力,去写一些更有趣的效果出来。

毕竟我们前端,有浏览器我们可以写动画,没了浏览器我们一样可以写动画。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

NodeJs 相关文章推荐
Nodejs获取网络数据并生成Excel表格
Mar 31 NodeJs
3分钟快速搭建nodejs本地服务器方法运行测试html/js
Apr 01 NodeJs
nodejs和C语言插入mysql数据库乱码问题的解决方法
Apr 14 NodeJs
docker中编译nodejs并使用nginx启动
Jun 23 NodeJs
NodeJS自定义模块写法(详解)
Jun 27 NodeJs
详解nodejs通过代理(proxy)发送http请求(request)
Sep 22 NodeJs
nodejs结合socket.io实现websocket通信功能的方法
Jan 12 NodeJs
nodejs图片处理工具gm用法小结
Dec 12 NodeJs
使用nodejs分离html文件里的js和css详解
Apr 12 NodeJs
基于nodejs的微信JS-SDK简单应用实现
May 21 NodeJs
nodejs中内置模块fs,path常见的用法说明
Nov 07 NodeJs
nodejs利用readline提示输入内容实例代码
Jul 15 NodeJs
基于nodejs的雪碧图制作工具的示例代码
Nov 05 #NodeJs
nodejs遍历文件夹下并操作HTML/CSS/JS/PNG/JPG的方法
Nov 01 #NodeJs
nodejs中函数的调用实例详解
Oct 31 #NodeJs
NodeJS 将文件夹按照存放路径变成一个对应的JSON的方法
Oct 17 #NodeJs
Nodejs实现多文件夹文件同步
Oct 17 #NodeJs
深入理解NodeJS 多进程和集群
Oct 17 #NodeJs
CentOS7中源码编译安装NodeJS的完整步骤
Oct 13 #NodeJs
You might like
完美解决:Apache启动问题―(OS 10022)提供了一个无效的参数
2013/06/08 PHP
PHP采用超长(超大)数字运算防止数字以科学计数法显示的方法
2016/04/01 PHP
php使用PDO执行SQL语句的方法分析
2017/02/16 PHP
php 7新特性之类型申明详解
2017/06/06 PHP
prototype Element学习笔记(Element篇三)
2008/10/26 Javascript
Jquery ajax不能解析json对象,报Invalid JSON错误的原因和解决方法
2010/03/27 Javascript
javascript和HTML5利用canvas构建猜牌游戏实现算法
2013/07/17 Javascript
jQuery打印指定区域Html页面并自动分页
2014/07/04 Javascript
JS实现一个列表中包含上移下移删除等功能
2014/09/24 Javascript
JavaScript实现随机替换图片的方法
2015/04/16 Javascript
JavaScript判断变量是否为数组的方法(Array)
2016/02/24 Javascript
Vue计算属性的使用
2017/08/04 Javascript
使用js实现将后台传入的json数据放在前台显示
2018/08/06 Javascript
vue-cli3 引入 font-awesome的操作
2020/08/11 Javascript
node.js文件的复制、创建文件夹等相关操作
2021/02/05 Javascript
[01:08:32]DOTA2-DPC中国联赛 正赛 DLG vs PHOENIX BO3 第二场 1月18日
2021/03/11 DOTA
使用PYTHON接收多播数据的代码
2012/03/01 Python
python高并发异步服务器核心库forkcore使用方法
2013/11/26 Python
Django实现支付宝付款和微信支付的示例代码
2018/07/25 Python
python调用c++ ctype list传数组或者返回数组的方法
2019/02/13 Python
Python django框架输入汉字,数字,字符生成二维码实现详解
2019/09/24 Python
将pymysql获取到的数据类型是tuple转化为pandas方式
2020/05/15 Python
Django+Uwsgi+Nginx如何实现生产环境部署
2020/07/31 Python
解决python和pycharm安装gmpy2 出现ERROR的问题
2020/08/28 Python
PyTorch中的拷贝与就地操作详解
2020/12/09 Python
css3 iphone玻璃透明气泡完美实现
2013/03/20 HTML / CSS
CSS3制作缩略图的详细过程
2016/07/08 HTML / CSS
松本清官方海外旗舰店:日本最大的药妆连锁店
2017/11/21 全球购物
巴西服装和鞋子购物网站:Marisa
2018/10/25 全球购物
2014年社区植树节活动方案
2014/02/28 职场文书
各营销点岗位职责范本
2014/03/05 职场文书
《李时珍夜宿古寺》教学反思
2014/04/09 职场文书
学雷锋活动总结范文
2014/04/25 职场文书
2014年三万活动总结
2014/04/26 职场文书
国家领导干部党的群众路线教育实践活动批评与自我批评材料
2014/09/23 职场文书
网络管理员岗位职责
2015/02/12 职场文书