命令行上简单的输入一行代码,而后立刻就创建了一个模板工程,这种方式想一想都会以为很酷。例如使用vue-cli工具的vue命令能够建立一个高配的模板工程。做为第一行代码咱们就先不玩那么高深的,就来个hello world
。html
咱们能够新建一个项目目录叫作test,而后进入该目录下npm init一路执行回车,最后在项目目录下新建一个bin文件夹,建立一个hello.js文件,而后写上:前端
#!/usr/bin/env node console.log("hello world");
修改package.json文件:vue
{ "name": "test", "bin": { "test": "bin/hello.js" } }
而后执行 npm link 命令:node
$ 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。git
咱们的hello world程序跑完了,这里重点聊聊两个问题:github
#!/usr/bin/env node
在这里有什么做用?vue-cli
npm link 到底在这里有什么做用?npm
要理解这两个问题,首先咱们要知道操做系统中都会有一个 PATH 环境变量,当系统调用一个命令的时候,就会在PATH变量中注册的路径中寻找,若是注册的路径中有就调用,不然就提示命令没找到。咱们能够经过process.env
获取本机系统中全部的环境变量。json
对于模块的载入及缓存机制能够分为如下几中状况:segmentfault
载入内置模块(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}`); })
参考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建立命令行脚本工具