cli原理解析

vue create my-vue-projecthtml

用过Vue的同窗基本都接触过vue脚手架:vue-cli,经过上面那行命令,回答几个问题,咱们就能轻松建立出一个Vue项目。由于团队工程化须要,我最近开始学习cli,打算用做团队脚手架或收拢我的经常使用命令(如按照格式提交代码)。vue

vue-cli的原理网上已经有不少不错的分析,而本文将从基础知识学习开始,一步步进阶到实际应用。但愿能帮助你们在学会使用工具的同时,了解背后的原理。养成“打破砂锅问到底”的学习习惯。node

目录

  1. 命令执行过程
  2. npm包打造cli的原理
  3. commander原理简析
  4. vue-cli:create命令执行过程
  5. 总结

一,命令执行过程

当你在控制台敲下npm -v并回车的时候,到底发生了什么?linux

咱们先来看看npm这个命令在哪。经过执行which npm(不了解which命令的同窗能够参考这里:linux命令之which),能够看到,npm命令的可执行文件在/usr/local/bin/npm,打开文件夹一看,是个替身,右键“显示原身”(Mac用户的方法,其余用户请搜索“软连接”),就能定位到该命令在哪:/usr/local/lib/node_modules/npm/bin/npm-cli.js,看到是js代码相信你们已经松了一口气,咱们先不急着看npm-cli.js里面的源,咱们先思考一个问题:git

Q1:系统是怎么找到npm这个这个命令的可执行文件的?github

A1:了解which命令的同窗都知道,which命令会按照PATH变量中的路径顺序来查找可执行文件。执行echo $PATH能够打印出该变量内容:/usr/local/bin:/usr/bin:/bin(例如这是个人部份内容,目录间用‘:’分隔),因此系统会先在/usr/local/bin下面找npm执行文件,/usr/local/bin/npm连接到/usr/local/lib/node_modules/npm/bin/npm-cli.js。因此调用npm命令至关于执行npm-cli.jsvue-cli

总的来讲:在控制台执行命令时,系统会先去环境路径(PATH)中找到可执行文件,而后执行该文件shell

那么,有没有同窗好奇:npm

Q2:系统又是怎么执行这些文件(例如上面的npm-cli)的呢?json

A2:打开npm-cli.js文件,咱们能看到的第一行代码就是:#!/usr/bin/env node,这行代码到底有什么用呢?具体可参考:stackoverflow - #!/usr/bin/env到底有什么用?,大体意思是告知系统用什么解释程序来执行该文件,例如#!/usr/bin/env node就是告知系统,npm-cli.js要用node来执行。所以npm -v至关于node /usr/local/lib/node_modules/npm/bin/npm-cli.js -v

$ node /usr/local/lib/node_modules/npm/bin/npm-cli.js -v
6.4.1
复制代码

二,npm包打造cli的原理

了解完命令执行过程以后,咱们就能够打造本身的cli命令了。

①先编写my-cli.js文件:

#!/usr/bin/env node
console.log('Hello cli!');
复制代码

②在/usr/local/bin(或者PATH里的任意路径下)建立软连接:

ln -s my-cli.js my-cli
复制代码

③给my-cli命令添加可执行权限:(若不添加权限,会报错bash: /usr/local/bin/my-cli: Permission denied)

chmod 777 my-cli
复制代码

④验证效果:

$ my-cli
Hello cli!
复制代码

在上面的基础上,咱们虽然能打造本身的命令,可是这个命令要想给团队使用,就须要每一个人都拷贝my-cli.js文件,建立软连接,添加可执行权限,很是繁琐。怎么将本身的命令分发出去给别人使用呢?

咱们再往前探索一步,一块儿打造一个基于npm分发的命令。

咱们在下载使用一个npm模块命令的时候,咱们会这样:

npm install -g @vue/cli
vue create my-project
复制代码

全局安装vue-cli这个npm模块以后,咱们全局新增了vue命令,这背后到底发生了什么?实际上是npm install帮咱们把上面提到的②③步自动执行了(我的假设,尚未时间去看npm的源码,若有错误,欢迎指出)。既然npm已经帮咱们完成这些简单可是繁琐的脚本操做,那咱们只须要按照npm的规范来配置一下代码便可。流程比较简单,请参考:经过npm包来制做命令行工具的原理

总结一下开发过程:

  1. npm init新建npm模块目录;
  2. 开发命令(例如上面的my-cli.js);
  3. package.json中添加bin字段(bin: { "my-cli": "./my-cli.js" });
  4. 发布npm;
  5. 全局安装便可使用my-cli;

三,commander原理简析

看了前面一二节,咱们已经掌握开发一个团队cli的方法,剩下的内容将参考[vue-cli源码](https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli/bin/vue.js)了解怎么优雅地实现一个cli。

这里直接和你们介绍几个vue-cli里面用到的几个库:

  1. commander - 命令行参数解析库;
  2. Inquirer.js - 命令行经常使用交互形式集合(问答,选择...);
  3. chalk - 在命令行样式美化;
  4. ora - 命令行loader;

更多好用的cli开发库欢迎你们留言补充。

commander几乎是开发cli必不可少的工具,基本使用方法以下:

#!/usr/bin/env node
var program = require('commander');

program
  .version('0.1.0')
  .option('-p, --peppers', 'Add peppers')
  .option('-P, --pineapple', 'Add pineapple')
  .option('-b, --bbq-sauce', 'Add bbq sauce')
  .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
  .parse(process.argv);
复制代码

这里简单分析一下其原理(参考commander源码),核心流程以下:

1. 经过option定义收集命令的功能选项;

2. parse解析命令参数(有process得到);

3. 由命令参数去匹配前面收集到的功能选项,执行前面的方法(将参数传入);

核心源码以下:

/** * Parse `argv`, settings options and invoking commands when defined. * * @param {Array} argv * @return {Command} for chaining * @api public */

Command.prototype.parse = function(argv) {
  // implicit help
  if (this.executables) this.addImplicitHelpCommand();

  // store raw args
  this.rawArgs = argv;

  // guess name
  this._name = this._name || basename(argv[1], '.js');

  // github-style sub-commands with no sub-command
  if (this.executables && argv.length < 3 && !this.defaultExecutable) {
    // this user needs help
    argv.push('--help');
  }

  // process argv
  var parsed = this.parseOptions(this.normalize(argv.slice(2)));
  var args = this.args = parsed.args;

  var result = this.parseArgs(this.args, parsed.unknown);

  // executable sub-commands
  var name = result.args[0];

  var aliasCommand = null;
  // check alias of sub commands
  if (name) {
    aliasCommand = this.commands.filter(function(command) {
      return command.alias() === name;
    })[0];
  }

  if (this._execs[name] && typeof this._execs[name] !== 'function') {
    return this.executeSubCommand(argv, args, parsed.unknown);
  } else if (aliasCommand) {
    // is alias of a subCommand
    args[0] = aliasCommand._name;
    return this.executeSubCommand(argv, args, parsed.unknown);
  } else if (this.defaultExecutable) {
    // use the default subcommand
    args.unshift(this.defaultExecutable);
    return this.executeSubCommand(argv, args, parsed.unknown);
  }

  return result;
};
复制代码

四,vue-cli:create命令执行过程

最后咱们以vue create my-project这个命令执行过程结尾,有兴趣的同窗推荐看vue-cli的源码,看源码是一个很好的学习过程。

vue create my-project命令执行过程:

  1. 【系统】系统定位到bin/vue.js文件,经过node bin/vue.js create my-project来执行该文件;
  2. 【vue.js】bin/vue.js利用commander来定义命令选项create,将create命令匹配到create方法(lib/create.js),执行该方法;
  3. 【create.js】lib/create.js使用Inquirer.js来询问用户,进行项目配置;
  4. 【Creator.js】根据用户配置生成package.json文件(基础信息,从项目配置中注入对应的开发依赖devDependencies);
  5. 【Creator.js】执行npm i来安装依赖;(PS: 这里封装了经常使用的npm操做,能够直接拷贝到本身项目中使用)
  6. 【Creator.js】加载vue-cli插件(@vue/cli-service是第一个被执行的插件);
  7. 【Generator.js】执行全部插件(执行cli-service插件会生成项目文件结构);
  8. 【Creator.js】生成README.md文件;

上面就是create命令的基本执行过程,若是咱们想扩展create方法,例如按照咱们的定义的模板生成目录结构,能够新建一个插件(generator,能够参考cli-service),在插件里生成自定义的目录结构便可。

经过阅读源码有两个收获:

  1. 利用插件形式来扩展,能在保证核心主流程简洁可维护的同时最大限度地提升扩展灵活性(以前已经有所实践,在这里再次确认插件架构的重要意义)
  2. 插件API能够经过调用插件时以参数的形式注入。(相比全局挂载的方式有两个好处:①没有全局变量污染;②能按照插件类别来注入不一样的API,达到权限管控的效果);

五,总结

至此,咱们已经具有写出一个实现良好,易于扩展的团队cli的知识,剩下的事情,就在键盘上完成吧。

此外,我提倡“打破砂锅问到底”,而文章内显然没有作到,例如npm甚至是node的执行过程尚未去深刻了解。时间永远是有限的,先有一个好的思考方向,再逐步去深刻就行了。而这每每也能解决当代人的焦虑,方向和努力缺一不可,共勉。

相关文章
相关标签/搜索