最近在研究前端工程化的一些東西, 那就得有本身的一套腳手架, 因而瞄上了vue-cli
, 也看了下create-react-app
, 感受vue-cli
更符合個人預期, 因而擼了遍源碼, 寫了個小demo腳手架。前端
獻上源碼地址: 源碼vue
體驗方法:react
$ npm i masoneast-cli -g
$ masoneast init my-project
複製代碼
如今, 咱們一塊兒來了解下vue-cli
到底幫咱們作了什麼,讓咱們能夠一行命令就能夠生成一個工程吧!webpack
咱們先了解下如何使用vue-cli
, 再詳細講解每一步的實現。git
vue-cli
提供了多種模板, 咱們這裏以webpack
模板爲例。github
安裝: npm install vue-cli -g
web
使用:vue-cli
vue init webpack my-project
vue init webpack my-projiect --offline
vue init webpack my-projiect --clone
這樣, 咱們就能在當前目錄下獲得一個vue的初始工程了。npm
當咱們使用vue-cli
時, 其實依賴了兩個東西: 一個是vue-cli
命令行, 一個是vue-template
模板, 用於生成工程。json
vue-cli
後, 會註冊環境變量,生成軟鏈接, 這樣咱們在命令行中任意路徑就可使用該命令了。vue init webpack my-project
時, vue-cli
會提示你正在下載模板。此時, vue-cli
就是從github託管的代碼中download
對應的webpack
模板。 對應的webpack模板的git地址在這裏: webpack模板
拼接url代碼是這段:
function getUrl (repo, clone) {
var url
// Get origin with protocol and add trailing slash or colon (for ssh)
var origin = addProtocol(repo.origin, clone)
if (/^git\@/i.test(origin))
origin = origin + ':'
else
origin = origin + '/'
// Build url
if (clone) {
url = origin + repo.owner + '/' + repo.name + '.git'
} else {
if (repo.type === 'github')
url = origin + repo.owner + '/' + repo.name + '/archive/' + repo.checkout + '.zip'
else if (repo.type === 'gitlab')
url = origin + repo.owner + '/' + repo.name + '/repository/archive.zip?ref=' + repo.checkout
else if (repo.type === 'bitbucket')
url = origin + repo.owner + '/' + repo.name + '/get/' + repo.checkout + '.zip'
}
return url
}
複製代碼
vue-cli
會將它放在你的本地,方便你之後離線使用它生成項目, 路徑是/Users/xxx/.vue-templates
, 若是你以前有使用vue-cli
生成過項目, 應該在你的管理員路徑下能找到對應的.vue-templates
文件夾。裏面的webpack文件就和上面git地址裏的代碼如出一轍。注意: .開頭的文件夾默認是隱藏的, 你須要讓它展現出來才能看到。
接下, vue-cli
會問你一堆問題, 你回答的這些問題它會將它們的答案存起來, 在接下來的生成中, 會根據你的答案來渲染生成對應的文件。
在你回答完問題後, vue-cli
就會根據你的需求從webpack模板中篩選出無用的文件, 並刪除, 它不是從你本地刪除, 只是在給你生成的項目中刪除這些文件。
在模板中, 你的src/App.vue
長這樣:
<template>
<div id="app">
<img src="./assets/logo.png">
{{#router}}
<router-view/>
{{else}}
<HelloWorld/>
{{/router}}
</div>
</template>
<script>
{{#unless router}}
import HelloWorld from './components/HelloWorld'
{{/unless}}
export default {
name: 'App'{{#router}}{{else}},
components: {
HelloWorld
}{{/router}}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
複製代碼
若是在選擇是否須要路由, 你選是,那最後生成在你的項目的App.vue
長這樣:
<template>
<div id="app">
<img src="./assets/logo.png">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
複製代碼
它會根據你的要求,渲染出不一樣的文件給你。
在完成渲染後, 接下來就會在你當前目錄下生成對應的文件了, 至此, vue-cli
的工做就完成了。
搞明白了vue-cli
的工做原理, 咱們徹底能夠本身作一個簡單點的cli出來了。
經過npm init
生成你的package.json
文件, 在裏面加入bin
"bin": {
"xxx": "bin/index.js"
},
複製代碼
這樣, 當你全局裝包的時候纔會把你xxx
命令註冊到環境變量中。
接下來就是bin/index.js
的事了。
commander
完成命令行中的命令program
.command('init [project-name]')
.description('create a project')
.option("-c, --clone", `it will clone from ${tmpUrl}`)
.option('--offline', 'use cached template')
.action(function (name, options) {
console.log('we are try to create "%s"....', name);
downloadAndGenerate(name, options)
}).on('--help', function () {
console.log('');
console.log('Examples:');
console.log('');
console.log(' $ masoneast init my-project');
console.log(` $ path: ${home}`);
});
program.parse(process.argv)
複製代碼
經過上面代碼, 你就有了init
命令, 和clone
, offline
參數了, 此時你就有了:
$ masoneast init my-project
$ masoneast init my-project --clone
$ masoneast init my-project --offline
複製代碼
關於commander
包的具體使用, 能夠看這裏: commander
這裏你須要有有個模板的地址供你下載和clone, 若是你只是玩玩的話也能夠直接使用vue
提供的模板地址, 或者個人模板地址: 模板
下載實現代碼:
這裏依賴了兩個庫: git-clone
和download
。
function download (name, clone, fn) {
if (clone) {
gitclone(tmpUrl, tmpPath, err => {
if (err) fn(err)
rm(tmpPath + '/.git')
fn()
})
} else {
const url = tmpUrl.replace(/\.git*/, '') + '/archive/master.zip'
console.log(url)
downloadUrl(url, tmpPath, { extract: true, strip: 1, mode: '666', headers: { accept: 'application/zip' } })
.then(function (data) {
fn()
})
.catch(function (err) {
fn(err)
})
}
}
複製代碼
交互的實現, 主要依賴了inquirer
庫。
function askQuestion (prompts) { //詢問交互
return (files, metalsmith, done) => {
async.eachSeries(Object.keys(prompts), (key, next) => {
prompt(metalsmith.metadata(), key, prompts[key], next)
}, done)
}
}
複製代碼
將詢問獲得的答案存貯起來, 留給後面渲染使用
function prompt (data, key, prompt, done) { //將用戶操做存儲到metaData中
inquirer.prompt([{
type: prompt.type,
name: key,
message: prompt.message || prompt.label || key,
default: prompt.default,
choices: prompt.choices || [],
validate: prompt.validate || (() => true)
}]).then(answers => {
if (Array.isArray(answers[key])) {
data[key] = {}
answers[key].forEach(multiChoiceAnswer => {
data[key][multiChoiceAnswer] = true
})
} else if (typeof answers[key] === 'string') {
data[key] = answers[key].replace(/"/g, '\\"')
} else {
data[key] = answers[key]
}
done()
}).catch(done)
}
複製代碼
模板渲染, 依賴了前端模板引擎handlebar
和解析模板引擎的consolidate
庫。 上面看到的vue-template
模板裏的{{#router}}
其實就是handlebar
的語法。
function renderTemplateFiles () {
return (files, metalsmith, done) => {
const keys = Object.keys(files)
const metalsmithMetadata = metalsmith.metadata() //以前用戶操做後的數據存在這裏面
async.each(keys, (file, next) => { //對模板進行遍歷, 找到須要渲染內容的文件
const str = files[file].contents.toString()
if (!/{{([^{}]+)}}/g.test(str)) { //正則匹配文件內容, 若是沒有就不須要修改文件, 直接去往下一個
return next()
}
render(str, metalsmithMetadata, (err, res) => {
if (err) {
err.message = `[${file}] ${err.message}`
return next(err)
}
files[file].contents = new Buffer(res)
next()
})
}, done)
}
}
複製代碼
這裏用到了一個核心庫: metalsmith
。它主要功能就是讀取你的文件, 並經過一系列的中間件對你的文件進行處理, 而後寫到你想要的路徑中去。就是經過這個庫, 將咱們的各個流程串聯起來, 實現對模板的改造, 寫出你想要的項目。
metalsmith.use(askQuestion(options.prompts)) //這一段是generator的精華, 經過各類中間件對用戶選擇的模板進行處理
.use(filterFiles(options.filters)) //文件篩選過濾
.use(renderTemplateFiles()) //模板內部變量渲染
.source('.')
.destination(projectPath) //項目建立的路徑
.build((err, files) => {
if (err) console.log(err)
})
複製代碼
我這裏實現的demo就是vue-cli
的精簡版, 主要功能有:
vue-cli
還有有不少的容錯判斷, 以及其餘模板, 下載源等的切換我這裏都沒有作處理了。
這個masoneast-cli
就是我閱讀vue-cli
源碼的學習成果, 這裏作一個總結。 若是 對你們有幫助, 隨手給個star
✨唄。