浅谈node.js 命令行工具(cli)


Posted in Javascript onMay 10, 2018

一. 先了解一下package.json

每个项目的根目录都有一个 package.json 文件,定义了这个项目所需要的各种模块,以及项目的配置信息,下面是一个比较完整的package.json文件

{
 "name": "vue-cli",
 "version": "2.9.3",
 "description": "A simple CLI for scaffolding Vue.js projects.",
 "preferGlobal": true,
 "bin": {
 "vue": "bin/vue",
 "vue-init": "bin/vue-init",
 "vue-list": "bin/vue-list"
 },
 "repository": {
 "type": "",
 "url": ""
 },
 "keywords": [
 ],
 "author": "litongqian",
 "license": "MIT",
 "bugs": {
 "url": ""
 },
 "homepage": "",
 "scripts": {
 "test": "npm run lint && npm run e2e",
 "start": "node index.js"
 },
 "dependencies": {
 "async": "^2.4.0",
 "chalk": "^2.1.0",
 },
 "devDependencies": {
 "chai": "^4.1.2",
 "eslint": "^3.19.0",
 },
 "engines": {
 "node": ">=6.0.0"
 }
}

1. 其中scripts字段

指定了运行脚本命令的npm命令行缩写,比如start指定了运行 npm run start 时,所要执行的命令。

2. bin字段

bin项用来指定各个内部命令对应的可执行文件的位置

"bin": {
 "vue": "bin/vue",
 "vue-init": "bin/vue-init",
 "vue-list": "bin/vue-list"
 },

上面代码指定, vue 命令对应的可执行文件为 bin 子目录下的 vue 。

3. npm link

开发NPM模块的时候,有时我们会希望,边开发边试用,比如本地调试的时候, require('myModule') 会自动加载本机开发中的模块。Node规定,使用一个模块时,需要将其安装到全局的或项目的 node_modules 目录之中。对于开发中的模块,解决方法就是在全局的 node_modules 目录之中,生成一个符号链接,指向模块的本地目录。

npm link 就能起到这个作用,会自动建立这个符号链接。

请设想这样一个场景,你开发了一个模块 myModule ,目录为 src/myModule ,你自己的项目 myProject 要用到这个模块,项目目录为 src/myProject 。首先,在模块目录( src/myModule )下运行 npm link 命令。

src/myModule$ npm link

上面的命令会在NPM的全局模块目录内,生成一个符号链接文件,该文件的名字就是 package.json 文件中指定的模块名。

/path/to/global/node_modules/myModule -> src/myModule

这个时候,已经可以全局调用 myModule 模块了。但是,如果我们要让这个模块安装在项目内,还要进行下面的步骤。

切换到项目目录,再次运行 npm link 命令,并指定模块名。

src/myProject$ npm link myModule

上面命令等同于生成了本地模块的符号链接。

src/myProject/node_modules/myModule -> /path/to/global/node_modules/myModule

然后,就可以在你的项目中,加载该模块了。

var myModule = require('myModule');

这样一来, myModule 的任何变化,都可以直接反映在 myProject 项目之中。但是,这样也出现了风险,任何在 myProject 目录中对 myModule 的修改,都会反映到模块的源码中。

如果你的项目不再需要该模块,可以在项目目录内使用 npm unlink 命令,删除符号链接。

src/myProject$ npm unlink myModule

二. 可执行脚本

写一个简单的脚本hello

$ mkdir hello #创建一个文件夹

$ cd hello && touch hello #创建命令文件

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

文件的头部务必加入#!/usr/bin/env node这行代码,这里表示使用node作为脚本的解释程序,node的路径通过env来查找,可以避免node安装路径不一带来的问题。

打开 /usr/bin/env,可以查看到PATH,操作系统通过路径找到node

浅谈node.js 命令行工具(cli)

然后,修改 hello 的权限。

$ chmod 755 hello
$./hello

浅谈node.js 命令行工具(cli)

如果想把 hello 前面的路径去除,可以将 hello 的路径加入环境变量 PATH。但是,另一种更好的做法,是在当前目录下新建 package.json ,写入下面的内容。

{
 "name": "hello",
 "bin": {
 "hello": "./hello"
 }
}

然后执行 npm link 命令。不明白的看上面

$ npm link

浅谈node.js 命令行工具(cli)

执行后会产生一个全局的映射关系,就可以全局使用hello命令了

三.命令行参数

命令行参数可以用系统变量 process.argv 获取。

修改hello脚本

#!/usr/bin/env node
console.log('hello ', process.argv);

其中process为node进程中的全局变量,process.argv为一数组,数组内存储着命令行的各个部分,argv[0]为node的安装路径,argv[1]为主模块文件路劲,剩下为子命令或参数,如下:

$ hello a b c

# process.argv的值为[ '/usr/local/bin/node', '/usr/local/bin/hello', 'a', 'b', 'c' ]

脚本可以通过 child_process 模块新建子进程,从而执行 Unix 系统命令,修改hello

exec 方法用于执行bash命令, exec 方法最多可以接受两个参数,第一个参数是所要执行的shell命令,第二个参数是回调函数,该函数接受三个参数,分别是发生的错误、标准输出的显示结果、标准错误的显示结果。

#!/usr/bin/env node
var name = process.argv[2];
var exec = require('child_process').exec;

var child = exec('echo hello ' + name, function(err, stdout, stderr) {
 if (err) throw err;
 console.log(stdout);
});

执行$ hello litongqian

如果我们想查看所有文件,修改hello

var name = process.argv[2];
var exec = require('child_process').exec;

var child = exec(name, function(err, stdout, stderr) {
 if (err) throw err;
 console.log(stdout);
});

执行$ hello ls 

hello目录下有三个文件

浅谈node.js 命令行工具(cli)

四、shelljs 模块

shell.js 模块重新包装了 child_process,调用系统命令更加方便。它需要安装后使用。

npm install --save shelljs

然后,改写脚本。

#!/usr/bin/env node
var name = process.argv[2];
var shell = require("shelljs");

shell.exec("echo hello " + name);

五、yargs 模块

shelljs 只解决了如何调用 shell 命令,而 yargs 模块能够解决如何处理命令行参数。它也需要安装。

$ npm install --save yargs

yargs 模块提供 argv 对象,用来读取命令行参数。请看改写后的 hello 。

#!/usr/bin/env node
var argv = require('yargs').argv;

console.log('hello ', argv.name);

使用时,下面两种用法都可以。

$ hello --name=tom
hello tom

$ hello --name tom
hello tom

也就是说,process.argv 的原始返回值如下。

$ node hello --name=tom
[ 'node',
 '/usr/local/bin/hell',
 '--name=tom' ]

yargs 可以上面的结果改为一个对象,每个参数项就是一个键值对。

六.发布命令包

通过npm publish进行发布,前提是有npm帐号。如何操作可以查看npm 官方文档。

本文是通过原生node.js来开发命令工具,而vue-cli是采用commander.js来简化命令工具开发,

了解了执行流程,去学习对应的模块,就很好知道原理了!,本文抛个砖头

浅谈node.js 命令行工具(cli)

最后:有时我们用到的命令行不是全局安装的,而是本地安装的

1. package.json bin字段

bin项用来指定各个内部命令对应的可执行文件的位置。

"name":"someTool",
"bin": {
 "someTool": "./bin/someTool.js"
}

上面代码指定,someTool 命令对应的可执行文件为 bin 子目录下的 someTool.js。

当一个项目依赖上面的someTool工具时,同时只是本地安装

{
 "name": "myproject",
 "devDependencies": {
 "someTool": "latest"
 },
 "scripts": {
 start: 'someTool build' //等同于start: './node_modules/someTool/someTool.js build'

 }
}

npm会寻找这个文件,在 node_modules/.bin/ 目录下建立符号链接。在上面的例子中,someTool.js会建立符号链接 npm_modules/.bin/someTool 。由于 node_modules/.bin/ 目录会在运行时加入系统的PATH变量,因此在运行npm时,就可以不带路径,直接通过命令来调用这些脚本。

因此,像上面这样的写法可以采用简写。

scripts: { 
 start: './node_modules/someTool/someTool.js build'
}

// 简写为

scripts: { 
 start: 'someTool build'
}

所有 node_modules/.bin/ 目录下的命令,都可以用 npm run [命令] 的格式运行。在命令行下,键入 npm run ,然后按tab键,就会显示所有可以使用的命令。

1. npm run

上面代码中, scripts 字段指定了两项命令 start ,输入 npm run-script start 或者 npm run start ,就会执行  someTool build 。 npm runnpm run-script 的缩写,一般都使用前者,但是后者可以更好地反应这个命令的本质。

npm run 命令会自动在环境变量 $PATH 添加 node_modules/.bin 目录,所以 scripts 字段里面调用命令时不用加上路径,这就避免了全局安装NPM模块。

npm run 如果不加任何参数,直接运行,会列出 package.json 里面所有可以执行的脚本命令。

npm内置了两个命令简写, npm test 等同于执行 npm run testnpm start 等同于执行 npm run start

npm run 会创建一个Shell,执行指定的命令,并临时将 node_modules/.bin 加入PATH变量,这意味着本地模块可以直接运行。

举例来说,你执行ESLint的安装命令。

$ npm i eslint --save-dev

运行上面的命令以后,会产生两个结果。首先,ESLint被安装到当前目录的 node_modules 子目录;其次, node_modules/.bin 目录会生成一个符号链接 node_modules/.bin/eslint ,指向ESLint模块的可执行脚本。

然后,你就可以在 package.jsonscript 属性里面,不带路径的引用 eslint 这个脚本。

{
 "name": "Test Project",
 "devDependencies": {
 "eslint": "^1.10.3"
 },
 "scripts": {
 "lint": "eslint ."
 }
}

等到运行 npm run lint 的时候,它会自动执行 ./node_modules/.bin/eslint .

如果直接运行 npm run 不给出任何参数,就会列出 scripts 属性下所有命令。

$ npm run
Available scripts in the user-service package:
 lint
  jshint **.js
 test
 mocha test/

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

Javascript 相关文章推荐
对YUI扩展的Gird组件 Part-2
Mar 10 Javascript
细品javascript 寻址,闭包,对象模型和相关问题
Apr 27 Javascript
JS自动缩小超出大小的图片
Oct 12 Javascript
jquery制作select列表双向选择示例代码
Sep 02 Javascript
2014年最火的Node.JS后端框架推荐
Oct 27 Javascript
jQuery中closest()函数用法实例
Jan 07 Javascript
JS获取子窗口中返回的数据实现方法
May 28 Javascript
jquery实现的table排序功能示例
Mar 10 Javascript
JavaScript数组去重的多种方法(四种)
Sep 19 Javascript
Vue组件和Route的生命周期实例详解
Feb 10 Javascript
JavaScript跳出循环的三种方法(break, return, continue)
Jul 30 Javascript
js操作两个json数组合并、去重,以及删除某一项元素
Sep 22 Javascript
Js经典案例的实例代码
May 10 #Javascript
Vue使用vux-ui自定义表单验证遇到的问题及解决方法
May 10 #Javascript
vuex与组件联合使用的方法
May 10 #Javascript
vue项目中公用footer组件底部位置的适配问题
May 10 #Javascript
解决vue 按钮多次点击重复提交数据问题
May 10 #Javascript
vue-router3.0版本中 router.push 不能刷新页面的问题
May 10 #Javascript
vue计算属性和监听器实例解析
May 10 #Javascript
You might like
PHP与MySQL交互使用详解
2006/10/09 PHP
php session 预定义数组
2009/03/16 PHP
完整删除ecshop中获取店铺信息的API
2014/12/24 PHP
document.open() 与 document.write()的区别
2007/08/13 Javascript
读jQuery之九 一些瑕疵说明
2011/06/21 Javascript
js之ActiveX控件使用说明 new ActiveXObject()
2014/03/03 Javascript
使用js dom和jquery分别实现简单增删改
2014/09/11 Javascript
JavaScript组合拼接字符串的效率对比测试
2014/11/06 Javascript
js实现字符串和数组之间相互转换操作
2016/01/12 Javascript
Bootstrap多级导航栏(级联导航)的实现代码
2016/03/08 Javascript
jQuery中借助deferred来请求及判断AJAX加载的实例讲解
2016/05/24 Javascript
js重写方法的简单实现
2016/07/10 Javascript
js 性能优化之快速响应的用户界面
2017/02/15 Javascript
使用jQuery ajaxupload插件实现无刷新上传文件
2017/04/23 jQuery
nodejs实现一个word文档解析器思路详解
2018/08/14 NodeJs
JS中的算法与数据结构之链表(Linked-list)实例详解
2019/08/20 Javascript
Vue快速实现通用表单验证功能
2019/12/05 Javascript
Js on及addEventListener原理用法区别解析
2020/07/11 Javascript
vue-model实现简易计算器
2020/08/17 Javascript
python使用beautifulsoup从爱奇艺网抓取视频播放
2014/01/23 Python
Python基于生成器迭代实现的八皇后问题示例
2018/05/23 Python
Python 面试中 8 个必考问题
2018/11/16 Python
python定时按日期备份MySQL数据并压缩
2019/04/19 Python
python实现文件助手中查看微信撤回消息
2019/04/29 Python
PyQt5 QTable插入图片并动态更新的实例
2019/06/18 Python
Python编程实现tail-n查看日志文件的方法
2019/07/08 Python
Python如何使用BeautifulSoup爬取网页信息
2019/11/26 Python
python关于调用函数外的变量实例
2019/12/26 Python
使用Python求解带约束的最优化问题详解
2020/02/11 Python
pycharm 2018 激活码及破解补丁激活方式
2020/09/21 Python
几个CSS3的flex弹性盒模型布局的简单例子演示
2016/05/12 HTML / CSS
购买限量版收藏品、珠宝和礼品:Bradford Exchange
2016/09/23 全球购物
美国羽绒床上用品第一品牌:Pacific Coast
2018/08/25 全球购物
入股协议书范本
2014/11/01 职场文书
关于调整工作时间的通知
2015/04/24 职场文书
CSS实现单选折叠菜单功能
2021/11/01 HTML / CSS