CLI的全称是Command-line Interface(命令行界面),即在命令行接受用户的键盘输入并作出响应和执行的程序。

在Node.js中,全局安装的包一般都具有命令行界面的功能,例如我们用于启动web服务的http-server包,在全局安装后,可按下图所示在当前目录启动一个web服务:

如果我们也想编写这样的命令行程序,该如何做呢?很简单,只需先创建下面这样的目录结构:

在bin目录中,我们创建了一个my-cli.js的文件,文件内容如下:

#!/usr/bin/env node

console.log('hello my-cli');

注意:第一行非常重要,标记这是一个node环境可执行文件。

而图中的package.json的内容如下所示:

{
  "author": "Mr.L",
  "license": "MIT",
  "name": "my-cli",
  "version": "1.0.0",
  "description": "my-cli",
  "bin": {
    "my-cli": "bin/my-cli.js"
  },
  "preferGlobal": "true"
}

在package.json文件中,我们指定了bin属性,其中的key就是命令行指令名称,映射到bin/my-cli.js这个可执行文件,最后的`preferGlobal`设置为`true`,表示可在全局安装并运行。

最后,在命令行进入图中的my-cli目录,执行如下命令:

如图所示,我们先执行`npm install -g .`命令,全局安装当前包,在安装完成后,直接在命令行执行`my-cli`,即可运行相关的程序。

到这里我们就完成了自己的一个简单CLI程序,不过通常来说,CLI程序需要一些实际的交互功能,并且最好能够支持两种运行方式:即命令行和作为普通包引入。

我们稍微修改一下上面的目录结构:

如图所示,我们新创建了一个lib目录,用于放置主要的业务逻辑,代码如下:

let chalk = require('chalk');

let sayHello = name => {
    // 使用chalk 设置控制台字体样式
    return chalk.bold('Hello, ') + chalk.bold.bgRed(name);
};

// 导出
exports.sayHello = sayHello;

我们定义了一个`sayHello`方法,根据传递的`name`参数,生成一个问候语,需要注意的是,我们在该文件中引入了`chalk`库,该库用于设置命令行的文字样式。

下面我们再来看看bin/my-cli.js文件的内容:

#!/usr/bin/env node

let readline = require('readline');
let lib = require('../lib/index.js');

// 创建交互界面
let interface = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

// 开始界面交互
interface.question('What is your name? ', answer => {
    let greeting = lib.sayHello(answer);

    // 打印结果
    console.log(greeting);

    interface.close();
});

在上面的代码中,我们引入Node.js内置的`realline`模块,该模块在标准输入和标准输出的基础上做了封装,在命令行中给出提示,并接受用户输入;另外,我们也引入了lib目录中的index.js,在接受用户输入后,调用`sayHello`方法,生成结果并打印。

最后,package.json也要相应做些修改:

{
  "author": "Mr.L",
  "license": "MIT",
  "name": "my-cli",
  "version": "1.0.0",
  "description": "my-cli",
  "bin": {
    "my-cli": "bin/my-cli.js"
  },
  "main": "lib/index.js",
  "preferGlobal": "true",
  "dependencies": {
    "chalk": "^1.1.3"
  }
}

我们添加了一个`main`属性,指向lib/index.js文件,这样便于其他开发者在程序中引入,调用我们的`sayHello`方法,我们稍后会介绍。

现在我们再执行`npm install -g .`命令,全局安装当前包,然后打开新的命令行:

如图所示,该程序接受了命令行参数,并正确打印出了结果。

前面提到,我们的CLI也要作为一个普通包被开发者引入,现在我们就来演示一下。首先,创建一个测试目录,如下图所示:

这个目录很简单,只有一个test.js文件和一个node_modules目录,在node_modules目录中包含我们的my-cli目录:

我们简单看一下test.js文件的内容:

let cli = require('my-cli');

let greeting = cli.sayHello('Jack');

console.log(greeting);

然后,我们在命令行直接执行该文件,打印结果如下图所示:

最后,我们也可以将自己的库发布到npm仓库,按照官方文档指示即可完成。

参考资料:

https://bretkikehara.wordpress.com/2013/05/02/nodejs-creating-your-first-global-module

https://nodejs.org/api/readline.html

Node.js系列文章:编写自己的命令行界面程序(CLI)的更多相关文章

  1. Node.js系列文章:如何进行代码调试

    使用任何一门编程语言,都少不了代码调试这一功能.我们在使用JavaScript编写浏览器端代码时,Chrome提供了强大的调试工具Dev Tools,但是在编写Node.js代码时,大多数人最开始都使 ...

  2. Node.js系列文章:利用console输出日志文件

    通常我们在写Node.js程序时,都习惯使用console.log打印日志信息,但这也仅限于控制台输出,有时候我们需要将信息输出到日志文件中,实际上利用console也可以达到这个目的的,今天就来简单 ...

  3. node.js系列笔记之node.js初识《一》

    node.js系列笔记之node.js初识<一> 一:环境说明 1.1 Linux系统CentOS 5.8 1.2 nodejs v0.10.15 1.3 nodejs源码下载地址 htt ...

  4. Node.js系列——(4)优势及场景

    背景 之前几篇系列文章简单介绍了node.js的安装配置及基本操作: Node.js系列--(1)安装配置与基本使用 Node.js系列--(2)发起get/post请求 Node.js系列--(3) ...

  5. Ember.js系列文章

    JS前端框架之Ember.js系列文章 本文为文章索引,主要是罗列Ember.js的相关文章便于阅读. 相关演示代码:github for free. 基础篇 1. EmberJs之What|Why| ...

  6. 微信JS图片上传与下载功能--微信JS系列文章(三)

    概述 在前面的文章微信JS初始化-- 微信JS系列文章(一)中已经介绍了微信JS初始化的相关工作,接下来本文继续就微信JS的图片上传功能进行描述,供大家参考. 图片上传 $(function(){ v ...

  7. 微信JS分享功能--微信JS系列文章(二)

    概述 在上一篇文章微信JS初始化-- 微信JS系列文章(一)中已经介绍了微信JS初始化的相关工作,接下来本文继续就微信JS的分享功能进行描述,供大家参考. 代码 $(document).ready(f ...

  8. Node.js系列-http

    前言: 最近一直忙着公司项目的事,战友们的留言也没空回复,博客也有段时间没有更新了,年底了就是一个的忙啊~~~(ps:同感的也给个赞吧) 现在前端的就是一直地更新一直有新的东西出来,什么ES2015, ...

  9. Node.js系列-express(上)

    前言 Node.js系列的第一篇:http,大概描述了通过使用node.js内置的api创建一个服务并监听request实现简单的增删改查.现在,我们就通过通读express官网及使用express框 ...

随机推荐

  1. 【Luogu1373】小a和uim之大逃离(动态规划)

    [Luogu1373]小a和uim之大逃离(动态规划) 题面 题目背景 小a和uim来到雨林中探险.突然一阵北风吹来,一片乌云从北部天边急涌过来,还伴着一道道闪电,一阵阵雷声.刹那间,狂风大作,乌云布 ...

  2. PKUWC2018游记

    PKUWC2018游记 Day -inf 从去年的12月底开始停课,到现在也有整整一个月的时间了. 前两周考的是OI赛制,后来就变成了IOI赛制. 整体上考的很炸,虐场的次数远少于被虐的次数. 关于去 ...

  3. static与final的区别

    final被修饰的变量为常量一旦赋值不能修改,被修改的方法为最终方法不能被重写,被修饰的类是最终类,不能被继承static被修饰的变量和方法,为该整个类及其类的对象所共享,一个类或对象修改了被定义的类 ...

  4. lambda表达式Expression<Func<Person, bool>> 、Func<Person, bool>区别

    前言: 自己通过lambda表达式的封装,将对应的表达式转成字符串的过程中,对lambda表达式有了新的认识 原因: 很多开发者对lambda表达式Expression<Func<Pers ...

  5. GeoJSON JS判断某一点是否在某一区域范围之内

    GeoJSON JS判断某一点是否在某一区域范围之内 算法: function isInPolygon(checkPoint, polygonPoints) { var counter = 0; va ...

  6. WordPress非插件实现评论回复邮件提醒通知

    要想在第一时间知道有人在自己博客留言,然后在第一时间回复留言再第一时间通知邮件者,这就需要WordPress博客拥有邮件通知功能. 先看效果: 评论邮件通知的方法: 1.所有回复都发送邮件通知 登陆博 ...

  7. java 多线程(总结)

    今天看了几篇关于java多线程问题的文章,将他们的部分内容引过来总结下,也算是对java多线程这类问题的整理. 在多线程中,必须明白两个问题,一是多线程实现,二是代码同步. 在java中要想实现多线程 ...

  8. Cannot resolve taglib with uri http://java.sun.com/jsp/jstl/core

    问题 <Spring 实战>第5章,在 IDEA 中 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" pre ...

  9. 一名Java架构师分享自己的从业心得,从码农到架构师我用了八年

    工作了挺久,发现有个挺有意思的现象,从程序员.高级程序员,到现在挂着架构师.专家之类的头衔,伴随着技术和能力的提高,想不明白的事情反而越来越多了. 这些疑问有些来自于跟小伙伴的交流,有些是我的自问自答 ...

  10. struts2基于注解配置action

    如果使用struts2,就需要配置文件或者注解,关于struts2的配置文件struts.xml非常熟悉,对于注解可能spring使用的比较多.配置文件的繁琐衬托出了注解的简洁方便,一条或者几条注解解 ...