前言
最近组里让我写个脚手架,用脚手架来生成模板项目。比如每个公司都会有很多后台管理系统,用于给产品、客服等内部人员使用。对于一个新的管理后台,如果每次都从头开始写,必然浪费太多时间。或者copy以前的项目进行改造又不太优雅。如果写一个像vue-cli这样的脚手架进行命令行创建那再好不过了。
一开始我感觉会有点难,像vue-cli这样的项目,经历了很多次的提交,代码量不少。我之前的想象是,vue-cli中进行许多的目录、文件的读写与创建,用户的交互,各种npm包的下载,脚本的运行,最终才生成了项目。想想就有点乱。
但看过后发现变简单多了。总的来说,vue-cli分两部分,一部分为vue-cli,vue-cli只是一个拉取仓库的作用,一个下载的作用。另一部分就是真正的模板了。这个模板也是github上的项目,按照一定的规范进行编写。
正文
这次看的是 vue-cli2,vue-cli3进行了很多封装,既然只需要其拉取仓库的作用,那么vue-cli2就够了。其目录结构如图:
网上也有许多vue-cli2的源码解析,这里只是简单说明。
首先从package.json开始,查看一些命令。也许用了vue-cli3,很多人已经忘记vue-cli2的命令了。在package.json中有:
"bin": {
"vue": "bin/vue",
"vue-init": "bin/vue-init",
"vue-list": "bin/vue-list"
}
"bin" 字段的作用是能让我们在命令窗口全局输入命令执行。
下面将以 vue init webpack my-project
为例。说明输入命令后发生了什么。
输入命令后,此时将运行bin文件夹下的vue-init脚本:
vue-init主要看这几个部分:
第一部分
首先第一句是必须的,这样才能在全局下运行命令。
#!/usr/bin/env node
第二部分
首先要知道的是,模板可以从各种地方下载,而不仅仅是官方提供的那几个(虽然许多时候用不到,但其的确提供了)。这一部分主要是获取命令中的参数,从而得出模板、项目名等信息。进而确定如何去获取模板。
let template = program.args[0] // 获取模板名称: webpack
const hasSlash = template.indexOf('/') > -1 // 模板名称是否有斜杠 (这里为false)
const rawName = program.args[1] // 项目名称: 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 模式,默认 http 方式下载模版
const tmp = path.join(home, '.vue-templates', template.replace(/[\/:]/g, '-')) // 将模板下载到该路径,并非直接下载到创建项目的路径(缓存)
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)
}
})
}
}
确定好命令中各种参数后,然后就根据参数进行下载了。首先isLocalPath(template)
判断参数是否是一个路径,如 ../../webpack
,而我们输入是参数为 webpack
。则走到 else
逻辑。else
中首先进行版本检查: checkVersion
,这就是如果有新版本的vue-cli了,将在命令行中询问我们是否下载。
由于没有斜杠(hasSlash),且不带版本号,最终走到了 downloadAndGenerate(officialTemplate)
去下载官方模板,其中经过拼接后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
方法没什么内容,主要看其中的 download
方法。download
是通过一个npm包 download-git-repo
引入的 :const download = require('download-git-repo')
。download 方法也没有什么内容,主要是根据url去下载(http)或者 clone项目。
那么如何根据传入的 officialTemplate
: vuejs-templates/webpack
就能得出URL呢,主要在于 normalize
:
最终得出的URL为:https://github.com/vuejs-templates/webpack。就这样,我们终于找到官方模板的位置了,总的来说vue-cli只起了一个下载的作用,而真正的模板隐藏于此!这也就是命令 vue init webpack my-project
中 webpack
参数的作用。在vuejs-templates 这个用户下,我们还可以看到其他几个模板,如当运行 vue init pwa my-project
时将下载pwa模板。
还记得第三部分中说到的 downloadAndGenerate(template)
逻辑吗?那是有斜杆/
时将去下载自定义模板。
也就是说,如果运行命令 vue init my-templates/webpack my-project
,它将到 my-templates 这个用户下寻找自定义的模板进行下载!
这个自定义模板编写是具有一定规范,具体可以看 https://github.com/vuejs/vue-cli/tree/v2#custom-templates
第五部分
在执行完 download
后,这是将模板下载完成而已。这个模板不是直接就用的,还记得我们创建项目时在命令窗口进行的询问与选择吗:
项目的作者?项目的描述?是否选择eslint?eslint的版本?等等。那么如何根据模板来生成自己最终的项目呢?
可以看到后面再调用 generate
进行项目的生成:
generate(name, tmp, to, err => {
if (err) logger.fatal(err)
console.log()
logger.success('Generated "%s".', name)
})
在generate
中主要使用了 Metalsmith
这个静态网站生成器的插件,这个插件配合https://github.com/vuejs/vue-cli/tree/v2#custom-templates 规范进行生成项目。具体怎么使用看其文档即可了。
限于篇幅,vue-cli的源码阅读就到这了,网上也有很多分析。下面说明如何进行简单的改造就可以使vue-cli为自己所用。也就是如何从公司gitlab进行拉取模板。
vue-cli已经提供了不同平台下载模板的方式:github、gitlab、bitbucket。默认是从github下载模板,如从gitlab下载则运行:
vue init gitlab:username/repo my-project
而我们是要从公司的gitlab上拉取,公司gitlab的域名是这样组成的gitlab.xxxx.com
。需要我们进行改造。在第三部分的run
方法中,将 officialTemplate
的拼接改为:
const officialTemplate = `gitlab:gitlab.xxxx.com:username/${template}`
那么,当你运行vue init webpack my-project
时,将默认从你公司的gitlab上拉取模板。那接下来就是如何编写模板的事了,按照https://github.com/vuejs/vue-cli/tree/v2#custom-templates 去做吧。
结束语
9月30日:文章是之前写的,之前不给发。祝大家国庆快乐吧!