前言
最近組里讓我寫個(gè)腳手架赋铝,用腳手架來生成模板項(xiàng)目。比如每個(gè)公司都會(huì)有很多后臺(tái)管理系統(tǒng)泳挥,用于給產(chǎn)品然痊、客服等內(nèi)部人員使用。對(duì)于一個(gè)新的管理后臺(tái)屉符,如果每次都從頭開始寫剧浸,必然浪費(fèi)太多時(shí)間∷粝悖或者copy以前的項(xiàng)目進(jìn)行改造又不太優(yōu)雅。如果寫一個(gè)像vue-cli這樣的腳手架進(jìn)行命令行創(chuàng)建那再好不過了真仲。
一開始我感覺會(huì)有點(diǎn)難初澎,像vue-cli這樣的項(xiàng)目,經(jīng)歷了很多次的提交碑宴,代碼量不少。我之前的想象是延柠,vue-cli中進(jìn)行許多的目錄、文件的讀寫與創(chuàng)建贞间,用戶的交互,各種npm包的下載增热,腳本的運(yùn)行,最終才生成了項(xiàng)目峻仇。想想就有點(diǎn)亂。
但看過后發(fā)現(xiàn)變簡(jiǎn)單多了》惭粒總的來說,vue-cli分兩部分朝蜘,一部分為vue-cli,vue-cli只是一個(gè)拉取倉庫的作用芹务,一個(gè)下載的作用蝉绷。另一部分就是真正的模板了。這個(gè)模板也是github上的項(xiàng)目枣抱,按照一定的規(guī)范進(jìn)行編寫熔吗。
正文
這次看的是 vue-cli2,vue-cli3進(jìn)行了很多封裝佳晶,既然只需要其拉取倉庫的作用桅狠,那么vue-cli2就夠了。其目錄結(jié)構(gòu)如圖:
網(wǎng)上也有許多vue-cli2的源碼解析轿秧,這里只是簡(jiǎn)單說明中跌。
首先從package.json開始,查看一些命令菇篡。也許用了vue-cli3漩符,很多人已經(jīng)忘記vue-cli2的命令了。在package.json中有:
"bin": {
"vue": "bin/vue",
"vue-init": "bin/vue-init",
"vue-list": "bin/vue-list"
}
"bin" 字段的作用是能讓我們?cè)诿畲翱谌州斎朊顖?zhí)行驱还。
下面將以 vue init webpack my-project
為例嗜暴。說明輸入命令后發(fā)生了什么。
輸入命令后议蟆,此時(shí)將運(yùn)行bin文件夾下的vue-init腳本:
vue-init主要看這幾個(gè)部分:
第一部分
首先第一句是必須的闷沥,這樣才能在全局下運(yùn)行命令。
#!/usr/bin/env node
第二部分
首先要知道的是咐容,模板可以從各種地方下載舆逃,而不僅僅是官方提供的那幾個(gè)(雖然許多時(shí)候用不到,但其的確提供了)戳粒。這一部分主要是獲取命令中的參數(shù)路狮,從而得出模板、項(xiàng)目名等信息蔚约。進(jìn)而確定如何去獲取模板奄妨。
let template = program.args[0] // 獲取模板名稱: webpack
const hasSlash = template.indexOf('/') > -1 // 模板名稱是否有斜杠 (這里為false)
const rawName = program.args[1] // 項(xiàng)目名稱: my-project
const inPlace = !rawName || rawName === '.'
const name = inPlace ? path.relative('../', process.cwd()) : rawName
const to = path.resolve(rawName || '.')
const clone = program.clone || false // 是否采用 clone 模式,默認(rèn) http 方式下載模版
const tmp = path.join(home, '.vue-templates', template.replace(/[\/:]/g, '-')) // 將模板下載到該路徑炊琉,并非直接下載到創(chuàng)建項(xiàng)目的路徑(緩存)
if (program.offline) { // 是否使用離線模版(使用上一行代碼中緩存下的)
console.log(`> Use cached template at ${chalk.yellow(tildify(tmp))}`)
template = tmp
}
第三部分
function run () {
// check if template is local
if (isLocalPath(template)) { // 是否使用本地模版 - 判斷模版路徑是否是本地
const templatePath = getTemplatePath(template)
if (exists(templatePath)) {
generate(name, templatePath, to, err => {
if (err) logger.fatal(err)
console.log()
logger.success('Generated "%s".', name)
})
} else {
logger.fatal('Local template "%s" not found.', template)
}
} else {
checkVersion(() => {
if (!hasSlash) {
// use official templates
const 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)
}
})
}
}
確定好命令中各種參數(shù)后展蒂,然后就根據(jù)參數(shù)進(jìn)行下載了又活。首先isLocalPath(template)
判斷參數(shù)是否是一個(gè)路徑,如 ../../webpack
锰悼,而我們輸入是參數(shù)為 webpack
柳骄。則走到 else
邏輯。else
中首先進(jìn)行版本檢查: checkVersion
箕般,這就是如果有新版本的vue-cli了,將在命令行中詢問我們是否下載曲初。
由于沒有斜杠(hasSlash),且不帶版本號(hào)臼婆,最終走到了 downloadAndGenerate(officialTemplate)
去下載官方模板颁褂,其中經(jīng)過拼接后officialTemplate
的值為: vuejs-templates/webpack
傀广。至于 downloadAndGenerate(template)
是自定義模板伪冰,后面再講。
第四部分
function downloadAndGenerate (template) {
const spinner = ora('downloading template')
spinner.start()
// Remove if local template exists
if (exists(tmp)) rm(tmp)
download(template, tmp, { clone }, err => {
spinner.stop()
if (err) logger.fatal('Failed to download repo ' + template + ': ' + err.message.trim())
generate(name, tmp, to, err => {
if (err) logger.fatal(err)
console.log()
logger.success('Generated "%s".', name)
})
})
}
downloadAndGenerate
方法沒什么內(nèi)容靠柑,主要看其中的 download
方法病往。download
是通過一個(gè)npm包 download-git-repo
引入的 :const download = require('download-git-repo')
骄瓣。download 方法也沒有什么內(nèi)容榕栏,主要是根據(jù)url去下載(http)或者 clone項(xiàng)目蕾各。
那么如何根據(jù)傳入的 officialTemplate
: vuejs-templates/webpack
就能得出URL呢,主要在于 normalize
:
最終得出的URL為:https://github.com/vuejs-templates/webpack妨托。就這樣兰伤,我們終于找到官方模板的位置了,總的來說vue-cli只起了一個(gè)下載的作用均澳,而真正的模板隱藏于此找前!這也就是命令 vue init webpack my-project
中 webpack
參數(shù)的作用判族。在vuejs-templates 這個(gè)用戶下,我們還可以看到其他幾個(gè)模板颗品,如當(dāng)運(yùn)行 vue init pwa my-project
時(shí)將下載pwa模板躯枢。
還記得第三部分中說到的 downloadAndGenerate(template)
邏輯嗎锄蹂?那是有斜桿/
時(shí)將去下載自定義模板得糜。
也就是說晰洒,如果運(yùn)行命令 vue init my-templates/webpack my-project
,它將到 my-templates 這個(gè)用戶下尋找自定義的模板進(jìn)行下載治宣!
這個(gè)自定義模板編寫是具有一定規(guī)范侮邀,具體可以看 https://github.com/vuejs/vue-cli/tree/v2#custom-templates
第五部分
在執(zhí)行完 download
后贝润,這是將模板下載完成而已打掘。這個(gè)模板不是直接就用的鹏秋,還記得我們創(chuàng)建項(xiàng)目時(shí)在命令窗口進(jìn)行的詢問與選擇嗎:
項(xiàng)目的作者拼岳?項(xiàng)目的描述惜纸?是否選擇eslint绝骚?eslint的版本?等等粪牲。那么如何根據(jù)模板來生成自己最終的項(xiàng)目呢腺阳?
可以看到后面再調(diào)用 generate
進(jìn)行項(xiàng)目的生成:
generate(name, tmp, to, err => {
if (err) logger.fatal(err)
console.log()
logger.success('Generated "%s".', name)
})
在generate
中主要使用了 Metalsmith
這個(gè)靜態(tài)網(wǎng)站生成器的插件亭引,這個(gè)插件配合https://github.com/vuejs/vue-cli/tree/v2#custom-templates 規(guī)范進(jìn)行生成項(xiàng)目皮获。具體怎么使用看其文檔即可了。
限于篇幅购公,vue-cli的源碼閱讀就到這了宏浩,網(wǎng)上也有很多分析靠瞎。下面說明如何進(jìn)行簡(jiǎn)單的改造就可以使vue-cli為自己所用较坛。也就是如何從公司gitlab進(jìn)行拉取模板扒最。
vue-cli已經(jīng)提供了不同平臺(tái)下載模板的方式:github、gitlab法竞、bitbucket。默認(rèn)是從github下載模板薛躬,如從gitlab下載則運(yùn)行:
vue init gitlab:username/repo my-project
而我們是要從公司的gitlab上拉取型宝,公司gitlab的域名是這樣組成的gitlab.xxxx.com
絮爷。需要我們進(jìn)行改造。在第三部分的run
方法中岖寞,將 officialTemplate
的拼接改為:
const officialTemplate = `gitlab:gitlab.xxxx.com:username/${template}`
那么仗谆,當(dāng)你運(yùn)行vue init webpack my-project
時(shí)淑履,將默認(rèn)從你公司的gitlab上拉取模板秘噪。那接下來就是如何編寫模板的事了,按照https://github.com/vuejs/vue-cli/tree/v2#custom-templates 去做吧捷绒。
結(jié)束語
9月30日:文章是之前寫的暖侨,簡(jiǎn)書之前不給發(fā)崇渗。祝大家國慶快樂吧!