第一行代码
命令行上简单的输入一行代码,然后马上就建立了一个模板工程,这种方式想想都会觉得很酷。例如使用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...
安装:
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#description() —— 添加命令描述
Command#alias() —— 设置命令别名
Command#usage() —— 设置/获取用法
处理用户输入
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
})
}
参考
Node.js的模块载入方式与机制
教你从零开始搭建一款前端脚手架工具
异步流程控制:7 行代码学会 co 模块
【译】使用Node.js创建命令行脚本工具
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。