手把手教你如何使用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 相关文章推荐
使用upstart把nodejs应用封装为系统服务实例
Jun 01 NodeJs
NodeJs基本语法和类型
Feb 13 NodeJs
Nodejs学习笔记之入门篇
Apr 16 NodeJs
nodeJs内存泄漏问题详解
Sep 05 NodeJs
nodejs 实现钉钉ISV接入的加密解密方法
Jan 16 NodeJs
Nodejs+Socket.io实现通讯实例代码
Feb 13 NodeJs
nodejs读写json文件的简单方法(必看)
Mar 09 NodeJs
nodejs+express搭建多人聊天室步骤
Feb 12 NodeJs
nodejs的路径问题的解决
Jun 30 NodeJs
Nodejs中的JWT和Session的使用
Aug 21 NodeJs
nodejs提示:cross-device link not permitted, rename错误的解决方法
Jun 10 NodeJs
详解NodeJS模块化
Jun 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
php中使用getimagesize获取图片、flash等文件的尺寸信息实例
2014/04/29 PHP
thinkphp集成前端脚手架Vue-cli的教程图解
2018/08/30 PHP
PHP实现批量修改文件名的方法示例
2019/09/18 PHP
Javascript条件判断使用小技巧总结
2008/09/08 Javascript
取选中的radio的值
2010/01/11 Javascript
Javascript 判断客户端浏览器类型代码
2010/03/01 Javascript
使用GruntJS构建Web程序之Tasks(任务)篇
2014/06/06 Javascript
JavaScript原生对象之String对象的属性和方法详解
2015/03/13 Javascript
JavaScript使用concat连接数组的方法
2015/04/06 Javascript
jQuery实现返回顶部效果的方法
2015/05/29 Javascript
js检测iframe是否加载完成的方法
2015/11/26 Javascript
Express实现前端后端通信上传图片之存储数据库(mysql)傻瓜式教程(二)
2015/12/10 Javascript
jQuery插件fullPage.js实现全屏滚动效果
2016/12/02 Javascript
AngularJS表单提交实例详解
2017/02/18 Javascript
ES6新特性之类(Class)和继承(Extends)相关概念与用法分析
2017/05/24 Javascript
浅谈基于Vue.js的移动组件库cube-ui
2017/12/20 Javascript
微信小程序学习之自定义滚动弹窗
2020/12/20 Javascript
python MySQLdb Windows下安装教程及问题解决方法
2015/05/09 Python
Python中文分词实现方法(安装pymmseg)
2016/06/14 Python
详解Python map函数及Python map()函数的用法
2017/11/16 Python
python爬虫之自动登录与验证码识别
2020/06/15 Python
Python3中在Anaconda环境下安装basemap包
2018/10/21 Python
Python嵌套式数据结构实例浅析
2019/03/05 Python
Python3实现的判断环形链表算法示例
2019/03/07 Python
tensorflow 限制显存大小的实现
2020/02/03 Python
python利用百度云接口实现车牌识别的示例
2020/02/21 Python
Python使用多进程运行含有任意个参数的函数
2020/05/02 Python
css3中单位px,em,rem,vh,vw,vmin,vmax的区别及浏览器支持情况
2016/12/06 HTML / CSS
女装和独特珠宝:Sundance Catalog
2018/09/19 全球购物
捷克家电和家具购物网站:OKAY.cz
2020/07/23 全球购物
工商学院毕业生个人自我评价
2013/09/19 职场文书
施工安全责任书
2014/04/14 职场文书
小学社会实践活动总结
2014/07/03 职场文书
读后感作文评语
2014/12/25 职场文书
Python类方法总结讲解
2021/07/26 Python
大型强子对撞机再次重启探索“第五种自然力”
2022/04/29 数码科技