第一行代码

命令行上简单的输入一行代码,然后马上就建立了一个模板工程,这种方式想想都会觉得很酷。例如使用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:UsersAdministratorAppDataRoamingnpmtest -> C:UsersAdministratorAppDataRoamingnpmnode_modulestestbinhello.js
C:UsersAdministratorAppDataRoamingnpmnode_modulestest -> E:github-codetest

命令行执行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:

处理用户输入

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://www.js-code.com/node-js/node-js_30954.html