Node Cli 入门
第一行代码
命令行上简单的输入一行代码,然后马上就建立了一个模板工程,这种方式想想都会觉得很酷。例如使用 vue-cli 工具的 vue 命令可以创建一个高配的模板工程。作为第一行代码我们就先不玩那么高深的,就来个hello world
。
我们可以新建一个项目目录叫做 test,然后进入该目录下 npm init 一路执行回车,最后在项目目录下新建一个 bin 文件夹,创建一个 hello.js 文件,然后写上:
#!/usr/bin/env node
console.log("hello world");
修改 package.json 文件:
{
"name": "test",
"bin": {
"test": "bin/hello.js"
}
}
然后执行 npm link 命令:
$ npm link
...
C:\Users\Administrator\AppData\Roaming\npm\test -> C:\Users\Administrator\AppData\Roaming\npm\node_modules\test\bin\hello.js
C:\Users\Administrator\AppData\Roaming\npm\node_modules\test -> E:\github-code\test
命令行执行 test,会打印出:hello world。
node 全局路径
我们的 hello world 程序跑完了,这里重点聊聊两个问题:
#!/usr/bin/env node
在这里有什么作用?- npm link 到底在这里有什么作用?
要理解这两个问题,首先我们要知道操作系统中都会有一个 PATH 环境变量,当系统调用一个命令的时候,就会在 PATH 变量中注册的路径中寻找,如果注册的路径中有就调用,否则就提示命令没找到。我们可以通过process.env
获取本机系统中所有的环境变量。
对于模块的载入及缓存机制可以分为以下几中情况:
- 载入内置模块(A Core Module)
- 载入文件模块(A File Module)
- 载入文件目录模块(A Folder Module)
- 载入 node_modules 里的模块
- 自动缓存已载入模块
这里我们重点关注的是载入 node_modules 里的模块。如果模块名不是路径,也不是内置模块,Node 将试图去当前目录的 node_modules 文件夹里搜索。如果当前目录的 node_modules 里没有找到,Node 会从父目录的 node_modules 里搜索,这样递归下去直到根目录。
我们通过全局命令安装的模块会保存在全局目录下,如:
npm install -g vue-cli
我们可以在{prefix}/node_modules
目录下找到vue-cli
文件夹,这个就包含了 vue-cli 的包。对于不同的系统,{prefix}
值不同,这里我们可以使用 npm prefix 命令获取:
npm prefix -g
当然我们也可以通过npm config命令的 get 和 set 方法操作这个路径。
那么再去理解上面的两个问题就简单了,#!/usr/bin/env node
主要是帮助脚本找到 node 的脚本解释器。npm link 的作用相当于是将我们的工程进行了全局安装,但是不同的是我们可以在命令行进行使用 package.json 文件中 bin 字段下的命令。如上面的 test 工程通过 npm link,这一步可以将本地目录安装到模块全局目录{prefix}/node_modules
下,并且会在{prefix}
文件夹下生成 test 文件和 test.cmd 文件。另外最关键的时候会建立链接,当本地目录如 test 文件夹变动,全局模块目录下的 test 文件夹也会相应改变,在其他目录下调用 test 命令同样可以找到命令。
同样我们可以通过下面的命令卸载模块:
npm uninstall -g test
处理命令行参数
node process 对象一个提供有关当前 Node.js 进程的信息和控制的全局对象,在 node 环境下无需通过 require()即可调用。
process.argv 属性返回一个数组,其中包含启动 Node.js 进程时传递的命令行参数。第一个元素是 process.execPath, 如果需要访问 argv [0]的原始值,可以使用 process.argv0,第二个元素将是要执行的 JavaScript 文件的路径, 其余元素将是任何其他命令行参数。
#!/usr/bin/env node
console.log('call %s', process.argv[2]);
然后输入test hello
,打印出call hello
。
对于命令行参数处理,我们一般用现成的模块 commander 或 yargs 处理,提供了用户命令行输入和参数解析强大功能。这里我们就使用轻量级,表达力强大的 commander 进行处理。
官网:http://tj.github.io/commander.js/ 安装:
npm install --save commander
commander 特性:
- 自记录代码
- 自动生成帮助
- 合并短参数(“ABC”==“-A-B-C”)
- 默认选项
- 强制选项
- 命令解析
- 提示符
commander API:
- exports.Command —— 暴露 Command 对象
- exports.Option —— 暴露 Option 对象
- Option() —— 初始化自定义参数对象,设置“关键字”和“描述”
- Command() —— 初始化命令行参数对象,直接获得命令行输入
- Command#command() —— 定义命令名称
- Command#arguments() —— 定义顶级命令的参数语法
- Command#parseExpectedArgs() —— 解析预期参数
- Command#action() —— 注册命令的回调函数
- Command#option() —— 定义参数,需要设置“关键字”和“描述”,关键字包括“简写”和“全写”两部分,以”,”,”|”,”空格”做分隔
- Command#allowUnknownOption() —— 允许命令行未知参数
- Command#parse() —— 解析 argv,设置选项和定义时调用命令
- Command#parseOptions()
- Command#opts()
- Command#description() —— 添加命令描述
- Command#alias() —— 设置命令别名
- Command#usage() —— 设置/获取用法
- Command#name()
- Command#outputHelp()
- Command#help()
处理用户输入
node 提供了标准的命令行输入的 API——readline。
const readline = require('readline');
const rl = readline.createInterface(process.stdin, process.stdout);
rl.question('what is your name? ', function(answer){
console.log(`name is ${answer}`);
rl.close();
});
注意:当调用该代码时,Node.js 程序不会终止,直到 readline.Interface 被关闭,因为接口在等待 input 流中要被接收的数据。
配合异步流程控制典型的co 模块使用:
let readlinePrompt = function (query) {
return new Promise(function (resolve, reject) {
rl.question(query, function(answer){
resolve(answer);
});
});
};
co(function *() {
var template = yield prompt('what is template-name? ');
var project = yield prompt('what is project-name? ');
console.log(`template-name is ${template}`);
console.log(`template-name is ${project}`);
rl.close();
})
为了简便我们一般都会使用co-prompt或者Inquirer.js之类的模块。如使用 co-prompt 模块我们可以这样写:
const prompt = require('co-prompt');
co(function *() {
var template = yield prompt('what is template-name? ');
var project = yield prompt('what is project-name? ');
console.log(`template-name is ${template}`);
console.log(`template-name is ${project}`);
})
下载 git 模板
参考 vue-cli 中下载 git 模板项目的方法,这里主要是使用了 download-git-repo 模块,以及使用 ora 模块显示下载状态。
let templateName = program.args[1]
let templateDir = path.join(home, '.plus-templates', templateName.replace(/\//g, '-'))
let clone = program.clone || false
function downloadAndGenerate(template) {
const spinner = ora('downloading template').start();
download(template, templateDir, { clone: clone }, function (err) {
spinner.stop();
if (err) logger.fatal('Failed to download repo ' + template + ': ' + err.message.trim())
// generate
})
}
参考
写这些代码也许就一两个小时的事,写一篇大家好接受的文章需要几天的酝酿,如果文章对您有帮助请我喝杯咖啡吧!
