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的執行過程尚未去深刻了解。時間永遠是有限的,先有一個好的思考方向,再逐步去深刻就行了。而這每每也能解決當代人的焦慮,方向和努力缺一不可,共勉。

相關文章
相關標籤/搜索