vue-cli是如何工作的

Heng溫發表於2018-02-07

vue-cli是Vue.js官方腳手架命令列工具,我們可以用它快速搭建Vue.js專案,vue-cli最主要的功能就是初始化專案,既可以使用官方模板,也可以使用自定義模板生成專案,而且從2.8.0版本開始,vue-cli新增了build命令,能讓你零配置啟動一個Vue.js應用。接下來,我們一起探究一下vue-cli是如何工作的。

全域性安裝

首先,vue-cli是一個node包,且可以在終端直接通過vue命令呼叫,所以vue-cli需要全域性安裝,當npm全域性安裝一個包時,主要做了兩件事:

  1. 將包安裝到全域性的node_modules目錄下。
  2. 在bin目錄下建立對應的命令,並連結到對應的可執行指令碼。

看一下vue-cli的package.json,可以發現如下程式碼:

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

這樣在全域性安裝vue-cli後,npm會幫你註冊vue, vue-init, vue-list, vue-build這幾個命令。

vue-cli是如何工作的

專案結構

vue-cli專案本身也不大,專案結構如下:

.├── bin├── docs├── lib└── test    └── e2e複製程式碼

bin目錄下是可執行檔案,docs下是新特性vue build的文件,lib是拆分出來的類庫,test下是測試檔案,我們著重看bin目錄下的檔案即可。

bin/vue

首先看bin/vue,內容很簡短,只有如下程式碼:

#!/usr/bin/env noderequire('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)複製程式碼

vue-cli是基於commander.js寫的,支援Git-style sub-commands,所以執行vue init可以達到和vue-init同樣的效果。

bin/vue-init

接下來看bin/vue-initvue-init的主要作用是根據指定模板生成專案原型。檔案首先是引入一些依賴模組和lib中的輔助函式,因為init命令需要接收至少一個引數,所以vue-init第一個被執行到的就是檢驗入參的help函式,如果沒有傳入引數,則列印提示,傳入引數則繼續執行。

再向下是解析引數的過程:

var template = program.args[0]var hasSlash = template.indexOf('/') >
-1var rawName = program.args[1]var inPlace = !rawName || rawName === '.'var name = inPlace ? path.relative('../', process.cwd()) : rawNamevar to = path.resolve(rawName || '.')var clone = program.clone || falsevar tmp = path.join(home, '.vue-templates', template.replace(/\//g, '-'))if (program.offline) {
console.log(`>
Use cached template at ${chalk.yellow(tildify(tmp))
}
`) template = tmp
}複製程式碼

template是模板名,第二個引數(program.args[1])rawName為專案名,如果不存在或為.則視為在當前目錄下初始化(inPlace = true),預設專案名稱name也為當前資料夾名。to是專案的輸出路徑,後面會用到。clone引數判斷是否使用git clone的方式下載模板,當模板在私有倉庫時用得上。offline引數決定是否使用離線模式,如果使用離線模式,vue-cli會嘗試去~/.vue-templates下獲取對應的模板,可以省去漫長的downloading template的等待時間,但是模板是不是最新的版本就無法確定了。

前面在處理引數時會得到一個變數to,表示即將生成的專案路徑,如果已存在,則會輸出警告,讓使用者確認是否繼續,確認後執行run函式:

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函式。

generate函式是生成專案的核心,主要程式碼:

module.exports = function generate (name, src, dest, done) { 
var opts = getOptions(name, src) // Metalsmith讀取template下所有資源 var metalsmith = Metalsmith(path.join(src, 'template')) var data = Object.assign(metalsmith.metadata(), {
destDirName: name, inPlace: dest === process.cwd(), noEscape: true
}) opts.helpers &
&
Object.keys(opts.helpers).map(function (key) {
Handlebars.registerHelper(key, opts.helpers[key])
}) var helpers = {chalk, logger
} if (opts.metalsmith &
&
typeof opts.metalsmith.before === 'function') {
opts.metalsmith.before(metalsmith, opts, helpers)
} // 一次使用askQuestions, filterFiles, renderTemplateFiles處理讀取的內容 metalsmith.use(askQuestions(opts.prompts)) .use(filterFiles(opts.filters)) .use(renderTemplateFiles(opts.skipInterpolation)) if (typeof opts.metalsmith === 'function') {
opts.metalsmith(metalsmith, opts, helpers)
} else if (opts.metalsmith &
&
typeof opts.metalsmith.after === 'function') {
opts.metalsmith.after(metalsmith, opts, helpers)
} // 將處理後的檔案輸出 metalsmith.clean(false) .source('.') // start from template root instead of `./src` which is Metalsmith's default for `source` .destination(dest) .build(function (err, files) {
done(err) if (typeof opts.complete === '
function') {
var helpers = {chalk, logger, files
} opts.complete(data, helpers)
} else {
logMessage(opts.completeMessage, data)
}
}) return data
}
複製程式碼

首先通過getOptions獲取了一些專案的基礎配置資訊,如專案名,git使用者資訊等。然後通過metalsmith結合askQuestions,filterFiles,renderTemplateFiles這幾個中介軟體完成了專案模板的生成過程。**metalsmith**是一個外掛化的靜態網站生成器,它的一切都是通過外掛運作的,這樣可以很方便地為其擴充套件。通過generate函式的程式碼,很容易看出來生成專案的過程主要是以下幾個階段。

vue-cli是如何工作的

每個過程主要用了以下庫:

  • getOptions: 主要是讀取模板下的meta.jsonmeta.jsmeta.json是必須的檔案,為cli提供多種資訊,例如自定義的helper,自定義選項,檔案過濾規則等等。該如何寫一個自定義模板,可以參考這裡
  • 通過Metalsmith讀取模板內容,需要注意的是,此時的模板內容還是未被處理的,所以大概長這樣:
/* eslint-disable no-new */new Vue({ 
el: '#app', {{#router
}
}
router, {{/router
}
} {{#if_eq build "runtime"
}
}
render: h =>
h(App){{#if_eq lintConfig "airbnb"
}
},{{/if_eq
}
}
{{/if_eq
}
} {{#if_eq build "standalone"
}
}
template: '<
App/>
'
, components: {
App
}{{#if_eq lintConfig "airbnb"
}
},{{/if_eq
}
}
{{/if_eq
}
}
}){{#if_eq lintConfig "airbnb"
}
};
{{/if_eq
}
}
複製程式碼
  • 獲取自定義配置: 主要是通過asyncinquirer的配合完成收集使用者自定義配置。
  • filterFiles: 對檔案進行過濾,通過minimatch進行檔案匹配。
  • 渲染模板:通過consolidate.js配合handlebars渲染檔案。
  • 輸出:直接輸出

vue-init的整個工作流程大致就是這樣,vue-cli作為一個便捷的命令列工具,其程式碼寫的也簡潔易懂,而且通過分析原始碼,可以發現其中用到的很多有意思的模組。

bin/vue-list

vue-list功能很簡單,拉取vuejs-templates的模板資訊並輸出。

bin/vue-build

vue-build則是通過一份webpack配置將專案跑起來,如果是入口僅是一個.vue元件,就使用預設的default-entry.es6載入元件並渲染。

其他

在看vue-cli原始碼時,發現了user-home這個模組,這個模組的內容如下:

'use strict';
module.exports = require('os-homedir')();
複製程式碼

os-homedir這個包是一個os.homedir的polyfill,在Why not just use the os-home module?下,我看到了Modules are cheap in Node.js這個blog。事實上sindresorhus寫了很多的One-line node modules,他也很喜歡One-line node moduels,因為模組越小,就意味著靈活性和重用性更高。當然對於One-line modules,每個人的看法不一樣,畢竟也不是第一次聽到**“就這一個函式也tm能寫個包”**的話了。我認為這個要因人而異,sindresorhus何許人也,很多著名開源專案的作者,釋出的npm包1000+,大多數他用到的模組,都是他自己寫的,所以對他來說,使用各種“積木”去組建“高樓”得心應手。不過對於其他人來說,如果習慣於這種方式,可能會對這些東西依賴性變強,就像現在很多前端開發依賴框架而不重基礎一樣,所以我認為這種“拼積木”開發方式挺好,但最好還是要知其所以然。但是我感覺One-line modules的作用卻不大,就像user-home這個模組,如果沒有它,const home = require('os-homedir')();
也可以達到目的,可能處於強迫症的原因,user-home才誕生了吧,而且像negative-zero這樣的One-line modules,使用場景少是其一,而且也沒帶來什麼方便,尤其是2.0版本,這個包直接使用Object.is去判斷了:

'use strict';
module.exports = x =>
Object.is(x, -0);
複製程式碼

不知道大家對One-line modules是什麼看法?

#### 結尾招聘廣告網易考拉招聘高階前端開發工程師,座標杭州濱江網易大廈,有興趣的同學戳我瞭解詳情&
投遞簡歷

來源:https://juejin.im/post/5a7b1b86f265da4e8f049081

相關文章