vue-cli源碼分析(試探篇)

引言:近期在研究怎麼經過node命令生成本身想要的文件夾結構,由於大佬說vue-cli的原理和這個很像,因此抽時間研究了一下,並分享本身的研究心得。但願個人初學經驗對你有用。html


1.vue init命令是怎麼實現的

vue-cli能夠經過命令vue init webpack my-project運行vue

拿到一個項目配置,首先看的是package.jsonnode

"bin": {
    "vue": "bin/vue",
    "vue-init": "bin/vue-init",
    "vue-list": "bin/vue-list",
    "vue-build": "bin/vue-build"
}複製代碼

bin項用來指定各個內部命令對應的可執行文件的位置。因此,vue能夠運行文件bin/vue,讓咱們看看bin/vue裏面是什麼?webpack

// file: ./bin/vue
require('commander')
  //版本號
  .version(require('../package').version)
  .usage('<command> [options]')
  .command('init', 'generate a new project from a template')
  .command('list', 'list available official templates')
  .command('build', 'prototype a new project')
  .parse(process.argv)複製代碼

因而,找到了commander,8000多star,github地址請點這裏 git

正常來講,command的用法有三個參數,以下github

program
   .command('aaa')
   .description('do something')
   //aaa命令調用是回調action函數
   .action(function(env) {
     console.log('deploying "%s"', env);
   });複製代碼

而vue-cli裏面並無顯示調用action(fn)時,則將會使用子命令模式。Commander 將嘗試在入口腳本的目錄中搜索可執行文件,(像./bin/vue)與名稱 program-command,像 vue-init,vue-build。web

.command('init')命令則會找到同文件夾的vue-init,因此會調用文件"bin/vue-init"並執行。vue-cli

而後vue init就能夠解釋了,那麼vue init webpack my-project 怎麼執行?往下看

2.vue-init文件解析

頭部模塊
#!/usr/bin/env node

//從倉庫下載並提取git存儲庫(GitHub,GitLab,Bitbucket)。
var download = require('download-git-repo')
//主要用於建立子命令和切割命令行參數並執行
var program = require('commander')
//檢查文件是否存在
var exists = require('fs').existsSync
//路徑模塊提供用於處理文件和目錄路徑的實用程序。 好比路徑分割,文件路徑格式化,json格式化等
var path = require('path')
//漂亮的loding
var ora = require('ora')
//獲取用戶主目錄的路徑
var home = require('user-home')
//絕對路徑轉換爲相對路徑
var tildify = require('tildify')
//美化
var chalk = require('chalk')
//經常使用的交互式命令行用戶界面的集合。表現是控制檯輸出提問
var inquirer = require('inquirer')
var rm = require('rimraf').sync
var logger = require('../lib/logger')
//輸出信息
var generate = require('../lib/generate')
var checkVersion = require('../lib/check-version')
var warnings = require('../lib/warnings')
var localPath = require('../lib/local-path')

var isLocalPath = localPath.isLocalPath
var getTemplatePath = localPath.getTemplatePath複製代碼
主體部分
//help部分太簡單跳過
/*** 用法舉例: 1.path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb'); Returns: '../../impl/bbb' 2.path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif'); 若是當前路徑是/home/myself/node, Returns: '/home/myself/node/wwwroot/static_files/gif/image.gif' 3.path.join('/foo', 'bar', 'baz/asdf', 'quux', '..'); Returns: '/foo/bar/baz/asdf' */

/** * 配置 */
//假設是vue init webpack my-project,第一個參數是webpack
var template = program.args[0]
var hasSlash = template.indexOf('/') > -1
var rawName = program.args[1]
var inPlace = !rawName || rawName === '.'

//path.relative()方法根據當前工做目錄返回相對路徑。 
//若是從每一個解析到相同的路徑(在每一個路徑上調用path.resolve()以後),返回零長度的字符串。
var name = inPlace ? path.relative('../', process.cwd()) : rawName

//合併路徑
var to = path.resolve(rawName || '.')
var clone = program.clone || false
//path.join()方法使用平臺特定的分隔符做爲分隔符將全部給定的路徑段鏈接在一塊兒, 
//而後對結果路徑進行規範化。
//home輸出舉例 => /Users/admin, tmp => /Users/admin/.vue-templates/webpack
var tmp = path.join(home, '.vue-templates', template.replace(/\//g, '-'))
//若是是線下,則template直接取這個路徑,不然須要去線上倉庫下載
if (program.offline) {
  console.log(`> Use cached template at ${chalk.yellow(tildify(tmp))}`)
  template = tmp
}


/** * Padding. */

console.log()
process.on('exit', function () {
  console.log()
})

if (exists(to)) {
    //詢問選擇
  inquirer.prompt([{
    type: 'confirm',
    message: inPlace
      ? 'Generate project in current directory?'
      : 'Target directory exists. Continue?',
    name: 'ok'
  }], function (answers) {
    if (answers.ok) {
      run()
    }
  })
} else {
  run()
}複製代碼

講run函數以前,要先了解下generate命令的調用文件--lib/generate文件,裏面用到了metalsmith,github地址請點這裏 json

/** * Check, download and generate the project. */

function run () {
  // 檢查要下載的模版是不是本地路徑
  if (isLocalPath(template)) {
    var templatePath = getTemplatePath(template)
    //信息輸出
    if (exists(templatePath)) {
      generate(name, templatePath, to, function (err) {
        if (err) logger.fatal(err)
        console.log()
        logger.success('Generated "%s".', name)
      })
    } else {
      logger.fatal('Local template "%s" not found.', template)
    }
  } else {
      //檢查版本並輸出信息
    checkVersion(function () {
      if (!hasSlash) {
        // use official templates
        var officialTemplate = 'vuejs-templates/' + template
        if (template.indexOf('#') !== -1) {
          downloadAndGenerate(officialTemplate)
        } else {
          if (template.indexOf('-2.0') !== -1) {
            warnings.v2SuffixTemplatesDeprecated(template, inPlace ? '' : name)
            return
          }

          // warnings.v2BranchIsNowDefault(template, inPlace ? '' : name)
          downloadAndGenerate(officialTemplate)
        }
      } else {
        downloadAndGenerate(template)
      }
    })
  }
}

/** * 從模版倉庫下載模版 * * @param {String} template */

function downloadAndGenerate (template) {
  //啓動控制檯loading
  var spinner = ora('downloading template')
  spinner.start()
  // 若是存在本地模版則移除
  if (exists(tmp)) rm(tmp)
  //下載
  download(template, tmp, { clone: clone }, function (err) {
    spinner.stop()
    //日誌
    if (err) logger.fatal('Failed to download repo ' + template + ': ' + err.message.trim())
    //控制檯打印出模版信息,好比 Generated "my-project".
    generate(name, tmp, to, function (err) {
      if (err) logger.fatal(err)
      console.log()
      logger.success('Generated "%s".', name)
    })
  })
}複製代碼

3.總結

vue init webpack my-project 的執行大體以下:

1.經過program.argv拿到參數[webpack,my-project]

2.根據參數經過拼接、path處理等操做,拿到下載模版路徑,而後根據本地和在線的區別,作不一樣的操做去下載便可。複製代碼

以上有說得不對的地方,還請你們多多指教,共同進步。bash

參考資料:

【1】html-js.site/2017/05/26/…

相關文章
相關標籤/搜索