由于最近需要做一款企业内部使用的vueCodeBase,用做公司项目初始化脚手架
常规的vue init template 脚手架不能满足需要
因此希望能够实现一款定制化的基于template模板之上的脚手架工具
这篇对vue-cli命令源码做简单分析,
目的是了解脚手架生成过程,方便自定义脚手架
vue-cli 2.9.6源码:
https://github.com/vuejs/vue-cli/tree/v2
vue官方模板源码:
https://github.com/vuejs-templates
vuejs-templates(webpack)文档:
https://vuejs-templates.github.io/webpack/
npm安装vue-cli:
npm install -g vue-cli
使用vue init命令创建工程(使用webpack模板)
vue init webpack
通过以上两个命令,可以得到一个基于webpack模板生成的项目脚手架
源码下载:
clone vue-cli branch v2:
git clone -b v2 https://github.com/vuejs/vue-cli
注意安装成功后的输出信息:
查看/usr/local/lib/node_modules/vue-cli/bin:
bin指定命令对应的可执行文件位置
在vue-cli 2.X版本中,vue-build,vue-create不支持
查看bin/vue源码:
#!/usr/bin/env node
const program = require('commander')
program
.version(require('../package').version)
.usage(' [options]' )
.command('init', 'generate a new project from a template')
.command('list', 'list available official templates')
.command('build', 'prototype a new project')
.command('create', '(for v3 warning only)')
program.parse(process.argv)
注意:交互式命令获取参数并不是在vue-cli中实现的,而是在模板项目mate.js
这篇主要说cli的各种命令源码,即vue init实现
vue-init源码:
#!/usr/bin/env node
// 从仓库下载代码-GitHub,GitLab,Bitbucket
const download = require('download-git-repo')
// 创建子命令,切割命令行参数并执行
const program = require('commander')
// 检查文件是否存在
const exists = require('fs').existsSync
// 路径模块
const path = require('path')
// loading
const ora = require('ora')
// 获取主目录路径
const home = require('user-home')
// 绝对路径转换为相对路径
const tildify = require('tildify')
// 命令行字体颜色
const chalk = require('chalk')
// 交互式命令行,可在控制台提问
const inquirer = require('inquirer')
// 包装rm -rf命令,删除文件和文件夹
const rm = require('rimraf').sync
// 日志
const logger = require('../lib/logger')
// 自动生成
const generate = require('../lib/generate')
// 检查版本
const checkVersion = require('../lib/check-version')
// 警告
const warnings = require('../lib/warnings')
const localPath = require('../lib/local-path')
// 是否本地方法
const isLocalPath = localPath.isLocalPath
// 模板路径方法
const getTemplatePath = localPath.getTemplatePath
/**
* Usage.
* 从命令中获取参数
* program.args[0] 模板类型
* program.args[1] 自定义项目名称
* program.clone clone
* program.offline 离线
*/
program
.usage(' [project-name]' )
.option('-c, --clone', 'use git clone')
.option('--offline', 'use cached template')
/**
* Help.
*/
program.on('--help', () => {
console.log(' Examples:')
console.log()
console.log(chalk.gray(' # create a new project with an official template'))
console.log(' $ vue init webpack my-project')
console.log()
console.log(chalk.gray(' # create a new project straight from a github template'))
console.log(' $ vue init username/repo my-project')
console.log()
})
/**
* Help.
*/
function help () {
program.parse(process.argv)
if (program.args.length < 1) return program.help()
}
help()
/**
* Settings.
*/
// 模板类型:获取第一个参数,如:webpack
let template = program.args[0]
// 是否有“/”符号
const hasSlash = template.indexOf('/') > -1
// 自定义项目名称,如:my-project
const rawName = program.args[1]
// rawName存在或者为“.”的时候,视为在当前目录下构建
const inPlace = !rawName || rawName === '.'
// path.relative():根据当前工作目录返回相对路径
const name = inPlace ? path.relative('../', process.cwd()) : rawName
// 合并路径
const to = path.resolve(rawName || '.')
// 检查参数是否clone
const clone = program.clone || false
// path.join():使用平台特定分隔符,将所有给定的路径连接在一起,然后对结果路径进行规范化
// 如 : /Users/admin/.vue-templates/webpack
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
}
/**
* Padding.
*/
console.log()
process.on('exit', () => {
console.log()
})
// 目录存在时询问,通过后执行run函数,否则直接执行run函数
if (inPlace || exists(to)) {
inquirer.prompt([{
type: 'confirm',
message: inPlace
// 是否在当前目录下构建项目
? 'Generate project in current directory?'
// 构建目录已存在,是否继续
: 'Target directory exists. Continue?',
name: 'ok'
}]).then(answers => {
if (answers.ok) {
run()
}
}).catch(logger.fatal)
} else {
run()
}
/**
* Check, download and generate the project.
*/
function run () {
// 本地模板
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)
}
})
}
}
/**
* Download a generate from a template repo.
* 从模板仓库下载代码
* @param {String} template
*/
function downloadAndGenerate (template) {
// loading
const spinner = ora('downloading template')
spinner.start()
// Remove if local template exists
if (exists(tmp)) rm(tmp)
// download-git-repo:从仓库下载代码-GitHub,GitLab,Bitbucket
// template:模板名 tmp:模板路径 clone:是否采用git clone模板 err:错误信息
download(template, tmp, { clone }, err => {
spinner.stop()
// error!!
if (err) logger.fatal('Failed to download repo ' + template + ': ' + err.message.trim())
generate(name, tmp, to, err => {
// error!!
if (err) logger.fatal(err)
console.log()
// success
logger.success('Generated "%s".', name)
})
})
}
1,获取参数
/**
* Usage.
* 从命令中获取参数
* program.args[0] 模板类型
* program.args[1] 自定义项目名称
* program.clone clone
* program.offline 离线
*/
program
.usage(' [project-name]' )
.option('-c, --clone', 'use git clone')
.option('--offline', 'use cached template')
2,获取模板路径:
// rawName存在或者为“.”的时候,视为在当前目录下构建
const inPlace = !rawName || rawName === '.'
// path.relative():根据当前工作目录返回相对路径
const name = inPlace ? path.relative('../', process.cwd()) : rawName
// 合并路径
const to = path.resolve(rawName || '.')
// 检查参数是否clone
const clone = program.clone || false
// path.join():使用平台特定分隔符,将所有给定的路径连接在一起,然后对结果路径进行规范化
// 如 : /Users/admin/.vue-templates/webpack
const tmp = path.join(home, '.vue-templates', template.replace(/[\/:]/g, '-'))
3,run函数:区分本地和离线,下载模板
// 本地路径存在
if (isLocalPath(template)) {
// 获取绝对路径
const templatePath = getTemplatePath(template)
// 本地下载...
} else {
// 不包含“/”,去官网下载
if (!hasSlash) {
const officialTemplate = 'vuejs-templates/' + template
// 包含“/”,去自己的仓库下载
} else {
}
}
引用了开源项目:
commander:命令行工具开发库
https://github.com/tj/commander.js/
generate命令调用lib/generate文件,使用了metalsmith:控制输出内容
https://github.com/segmentio/metalsmith
vue-list比较简单,主要是获取并显示官方git仓库中模板信息列表
vue-list源码:
#!/usr/bin/env node
const logger = require('../lib/logger')
const request = require('request')
const chalk = require('chalk')
/**
* Padding.
*/
console.log()
process.on('exit', () => {
console.log()
})
/**
* List repos.
* 仓库列表:https://api.github.com/users/vuejs-templates/repos
*/
request({
url: 'https://api.github.com/users/vuejs-templates/repos',
headers: {
'User-Agent': 'vue-cli'
}
}, (err, res, body) => {
if (err) logger.fatal(err)
const requestBody = JSON.parse(body)
if (Array.isArray(requestBody)) {
console.log(' Available official templates:')
console.log()
requestBody.forEach(repo => {
console.log(
// 黄色星星符号
' ' + chalk.yellow('★') +
// 仓库名使用蓝色字体
' ' + chalk.blue(repo.name) +
' - ' + repo.description)
})
} else {
console.error(requestBody.message)
}
})
####4,vue-build命令
vue-build源码:
#!/usr/bin/env node
const chalk = require('chalk')
console.log(chalk.yellow(
'\n' +
' We are slimming down vue-cli to optimize the initial installation by ' +
'removing the `vue build` command.\n' +
' Check out Poi (https://github.com/egoist/poi) which offers the same functionality!' +
'\n'
))
翻译一下:
我们通过删除“vue build”命令来减少vue-cli,从而优化初始安装。
签出Poi(https://github.com/egoist/poi),提供相同的功能!
Poi:
前端工程化解决方案
vue create是Vue CLI 3命令
提示卸载vue-cli,安装@vue/cli,升级到Vue CLI 3
vue-create源码:
#!/usr/bin/env node
const chalk = require('chalk')
console.log()
console.log(
` ` +
chalk.yellow(`vue create`) +
' is a Vue CLI 3 only command and you are using Vue CLI ' +
require('../package.json').version + '.'
)
console.log(` You may want to run the following to upgrade to Vue CLI 3:`)
console.log()
console.log(chalk.cyan(` npm uninstall -g vue-cli`))
console.log(chalk.cyan(` npm install -g @vue/cli`))
console.log()
通过对vue-init命令执行过程的分析,了解到命令的各种参数作用
可以通过命令来指定线上/线下,指定仓库获取Vue脚手架模板
// 下载模板
download(template, tmp, { clone }, err => {
// 渲染模板
generate(name, tmp, to, err => {
})
})
https://github.com/flipxfx/download-git-repo
https://github.com/flipxfx/download-git-repo/blob/master/index.js
/**
* Download `repo` to `dest` and callback `fn(err)`.
*
* @param {String} repo 仓库
* @param {String} dest 目标
* @param {Object} opts 参数
* @param {Function} fn 回调
*/
function download (repo, dest, opts, fn) {
// 回调
if (typeof opts === 'function') {
fn = opts
opts = null
}
// clone?
opts = opts || {}
var clone = opts.clone || false
// 规范仓库字符串(根据type转换为github.com,gitlab.com,bitbucket.com)
repo = normalize(repo)
// 构建下载模板的URL地址(区分github,gitlab,bitbucket )
var url = repo.url || getUrl(repo, clone)
// clone
if (clone) {
// 非官方库下载 var gitclone = require('git-clone')
gitclone(url, dest, { checkout: repo.checkout, shallow: repo.checkout === 'master' }, function (err) {
if (err === undefined) {
rm(dest + '/.git')
fn()
} else {
fn(err)
}
})
// 官方模板库:var downloadUrl = require('download')
} else {
downloadUrl(url, dest, { extract: true, strip: 1, mode: '666', headers: { accept: 'application/zip' } })
.then(function (data) {
fn()
})
.catch(function (err) {
fn(err)
})
}
}
vue-cli调用的这个下载方法,最终执行gitclone或downloadUrl对代码进行下载
对string类型的仓库地址repo进行处理,转为repo对象
/**
* Normalize a repo string.
* @param {String} repo 字符串类型的仓库地址
* @return {Object} 返回仓库地址对象
*/
function normalize (repo) {
// direct类型匹配
var regex = /^(?:(direct):([^#]+)(?:#(.+))?)$/
var match = regex.exec(repo)
if (match) {
var url = match[2]
var checkout = match[3] || 'master'
return {
type: 'direct',
url: url,
checkout: checkout
}
} else {
// 其他类型匹配
regex = /^(?:(github|gitlab|bitbucket):)?(?:(.+):)?([^\/]+)\/([^#]+)(?:#(.+))?$/
match = regex.exec(repo)
var type = match[1] || 'github'
var origin = match[2] || null
var owner = match[3]
var name = match[4]
var checkout = match[5] || 'master'
// 如果origin为空,尝试根据type进行补全
if (origin == null) {
if (type === 'github')
origin = 'github.com'
else if (type === 'gitlab')
origin = 'gitlab.com'
else if (type === 'bitbucket')
origin = 'bitbucket.com'
}
// 返回repo仓库对象
return {
type: type, // 仓库类型
origin: origin, // 仓库host
owner: owner, // 仓库所有者
name: name, // 工程名
checkout: checkout // 分支
}
}
}
文档:https://www.npmjs.com/package/download-git-repo
js正则测试工具:http://tools.jb51.net/regex/javascript
下载范例:
download('gitlab:mygitlab.com:flipxfx/download-git-repo-fixture#my-branch', 'test/tmp', function (err) {
console.log(err ? 'Error' : 'Success')
})
^(?:(github|gitlab|bitbucket):)?(?:(.+):)?([^\/]+)\/([^#]+)(?:#(.+))?$
flipxfx/download-git-repo-fixture
bitbucket:flipxfx/download-git-repo-fixture#my-branch
gitlab:mygitlab.com:flipxfx/download-git-repo-fixture#my-branch
^(?:(direct):([^#]+)(?:#(.+))?)$
direct:https://gitlab.com/flipxfx/download-git-repo-fixture/repository/archive.zip
direct:https://gitlab.com/flipxfx/download-git-repo-fixture.git
direct:https://gitlab.com/flipxfx/download-git-repo-fixture.git#my-branch
使用上一步转换出来的repo仓库对象,进一步转换得到url
/**
* Return a zip or git url for a given `repo`.
* 得到下载模板的最终地址
* @param {Object} repo 仓库对象
* @return {String} url
*/
function getUrl (repo, clone) {
var url
// 使用协议获取源代码并添加尾随斜杠或冒号(用于SSH)
// 附加协议(附加git@或https://协议):
var origin = addProtocol(repo.origin, clone)
if (/^git\@/i.test(origin))
origin = origin + ':'
else
origin = origin + '/'
// 构建URL
// clone
if (clone) {
url = origin + repo.owner + '/' + repo.name + '.git'
// 非clone(区分:github,gitlab,bitbucket)
} else {
// github
if (repo.type === 'github')
url = origin + repo.owner + '/' + repo.name + '/archive/' + repo.checkout + '.zip'
// gitlab
else if (repo.type === 'gitlab')
url = origin + repo.owner + '/' + repo.name + '/repository/archive.zip?ref=' + repo.checkout
// bitbucket
else if (repo.type === 'bitbucket')
url = origin + repo.owner + '/' + repo.name + '/get/' + repo.checkout + '.zip'
}
return url
}
构造URL前,为git仓库添加附加协议(附加git@或https://协议):
/**
* Adds protocol to url in none specified
* 为URL添加协议
* @param {String} url
* @return {String}
*/
function addProtocol (origin, clone) {
if (!/^(f|ht)tps?:\/\//i.test(origin)) {
if (clone)
origin = 'git@' + origin
else
origin = 'https://' + origin
}
return origin
}
模板下载好了,开始渲染
// 下载模板
download(template, tmp, { clone }, err => {
// 渲染模板
generate(name, tmp, to, err => {
})
})
根据设置的参数,对模板进行配置,引用了…/lib/generate
generate.js文件:
https://github.com/vuejs/vue-cli/blob/v2/lib/generate.js
声明:
// 高亮打印信息
const chalk = require('chalk')
// 静态网站生成器
const Metalsmith = require('metalsmith')
// Handlebars模板引擎
const Handlebars = require('handlebars')
// 异步处理工具
const async = require('async')
// 模板引擎渲染
const render = require('consolidate').handlebars.render
// node路径模块
const path = require('path')
// 多条件匹配
const multimatch = require('multimatch')
// 获取模板配置
const getOptions = require('./options')
// 询问开发者
const ask = require('./ask')
// 文件过滤
const filter = require('./filter')
// 日志
const logger = require('./logger')
主要逻辑:
/**
* Generate a template given a `src` and `dest`.
* 生成一个模板,给定一个“Src”和“Dest`”
* @param {String} name
* @param {String} src
* @param {String} dest
* @param {Function} done
*/
module.exports = function generate (name, src, dest, done) {
// 获取配置-src是模板下载成功之后的临时路径
const opts = getOptions(name, src)
// 初始化Metalsmith对象-读取的内容是模板的tempalte目录
// metalsmith返回文件路径和内容的映射对象, 方便metalsmith中间件对文件进行处理
const metalsmith = Metalsmith(path.join(src, 'template'))
// 添加变量至metalsmith
const data = Object.assign(metalsmith.metadata(), {
destDirName: name,
inPlace: dest === process.cwd(),
noEscape: true
})
// 注册配置对象中的helper
opts.helpers && Object.keys(opts.helpers).map(key => {
Handlebars.registerHelper(key, opts.helpers[key])
})
const helpers = { chalk, logger }
// 配置对象是否含有before函数,如果有before函数就执行
if (opts.metalsmith && typeof opts.metalsmith.before === 'function') {
opts.metalsmith.before(metalsmith, opts, helpers)
}
// vue cli使用了三个中间件来处理模板
metalsmith
// 询问mate.js中prompts配置的问题
.use(askQuestions(opts.prompts))
// 根据配置对文件进行过滤
.use(filterFiles(opts.filters))
// 渲染模板文件
.use(renderTemplateFiles(opts.skipInterpolation))
// 配置对象是否含有after函数,如果有after函数就执行
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('.') // 从模板根开始而不是“./Src”,这是MalalSmith'缺省的“源”
.destination(dest)
.build((err, files) => {
done(err)
// //配置对象有complete函数则执行
if (typeof opts.complete === 'function') {
const helpers = { chalk, logger, files }
opts.complete(data, helpers)
} else {
// 配置对象有completeMessage,执行logMessage函数
logMessage(opts.completeMessage, data)
}
})
return data
}
const metalsmith = Metalsmith(path.join(src, ‘template’))
注册handlebars模板Helper-if_eq和unless_eq
Handlebars.registerHelper('if_eq', function (a, b, opts) {
return a === b
? opts.fn(this)
: opts.inverse(this)
})
Handlebars.registerHelper('unless_eq', function (a, b, opts) {
return a === b
? opts.inverse(this)
: opts.fn(this)
})
中间件 askQuestions 用于读取用户输入
/**
* Create a middleware for asking questions.
* 询问mate.js中prompts配置的问题
* @param {Object} prompts
* @return {Function}
*/
function askQuestions (prompts) {
return (files, metalsmith, done) => {
ask(prompts, metalsmith.metadata(), done)
}
}
meta.{js,json}样例:
{
"prompts": {
"name": {
"type": "string",
"required": true,
"message" : "Project name"
},
"version": {
"type": "input",
"message": "project's version",
"default": "1.0.0"
}
}
}
在 ask 中, 对 meta 信息中的 prompt 会有条件的咨询用户
// vue-cli/lib/ask.js#prompt
inquirer.prompt([{
type: prompt.type,
message: prompt.message,
default: prompt.default
//...
}], function(answers) {
// 保存用户的输入
})
经过 askQuestions 中间件处理之后, global metadata 是一个以 prompt 中的 key 为 key, 用户的输入为 value 的对象:
{
name: 'my-project',
version: '1.0.0'...
}
中间件 filterFiles-根据meta信息中的filters文件进行过滤:
/**
* Create a middleware for filtering files.
* 创建用于过滤文件的中间件
* @param {Object} filters
* @return {Function}
*/
function filterFiles (filters) {
return (files, metalsmith, done) => {
filter(files, filters, metalsmith.metadata(), done)
}
}
filter源码:
// vue-cli/lib/filter.js
module.exports = function (files, filters, data, done) {
// 没filters,直接调用done()返回
if (!filters) {
return done()
}
// 得到全部文件名
var fileNames = Object.keys(files)
// 遍历filters,进行匹配,删除不需要的文件
Object.keys(filters).forEach(function (glob) {
fileNames.forEach(function (file) {
if (match(file, glob, { dot: true })) {
// 获取到匹配的值
var condition = filters[glob]
// evaluate用于执行js表达式,在vue-cli/lib/eval.js
// var fn = new Function('data', 'with (data) { return ' + exp + '}')
if (!evaluate(condition, data)) {
// 删除文件-根据用户输入过滤掉不需要的文件
delete files[file]
}
}
})
})
done()
}
使用renderTemplateFiles中间件渲染模板:
/**
* Template in place plugin.
* 渲染模板文件
* @param {Object} files 全部文件对象
* @param {Metalsmith} metalsmith metalsmith对象
* @param {Function} done
*/
function renderTemplateFiles (skipInterpolation) {
// skipInterpolation如果不是数组,就转成数组,确保为数组类型
skipInterpolation = typeof skipInterpolation === 'string'
? [skipInterpolation]
: skipInterpolation
return (files, metalsmith, done) => {
// 获取files对象所有的key
const keys = Object.keys(files)
// 获取metalsmith对象的metadata
const metalsmithMetadata = metalsmith.metadata()
// 异步处理所有key对应的files
async.each(keys, (file, next) => {
// 跳过符合skipInterpolation的配置的file
if (skipInterpolation && multimatch([file], skipInterpolation, { dot: true }).length) {
return next()
}
// 获取文件内容
const str = files[file].contents.toString()
// 跳过不符合handlebars语法的file(不渲染不含mustaches表达式的文件)
if (!/{{([^{}]+)}}/g.test(str)) {
return next()
}
// 调用handlebars完成文件渲染
render(str, metalsmithMetadata, (err, res) => {
if (err) {
err.message = `[${file}] ${err.message}`
return next(err)
}
files[file].contents = new Buffer(res)
next()
})
}, done)
}
}
显示模板完成信息:
模板文件渲染完成后, metalsmith 会将最终结果build到dest目录
如果build失败, 会将err信息传给回调输出;
build成功后,如果meta信息有complete函数则调用,有completeMessage则输出:
/**
* Display template complete message.
*
* @param {String} message 消息
* @param {Object} data 数据
*/
function logMessage (message, data) {
// 如果没有message,直接return
if (!message) return
// 渲染信息
render(message, data, (err, res) => {
if (err) {
console.error('\n Error when rendering template complete message: ' + err.message.trim())
} else {
console.log('\n' + res.split(/\r?\n/g).map(line => ' ' + line).join('\n'))
}
})
}
options.js中读取配置信息(来自meta(.json/.js)和getGitUser)
const getGitUser = require('./git-user')
module.exports = function options (name, dir) {
// 读取模板的meta(.json/.js)信息
// dir 是模板下载成功之后的临时路径
const opts = getMetadata(dir)
// 向配置对象添加字段默认值
setDefault(opts, 'name', name)
// 检测配置对象中name字段是否合法
setValidateName(opts)
// 读取用户的git昵称和邮箱,用于设置meta信息默认属性
const author = getGitUser()
if (author) {
setDefault(opts, 'author', author)
}
return opts
}
总结一下模板渲染流程:
1,获取模板配置,
2,初始化Metalsmith,添加变量至Metalsmith
3,handlebars模板注册helper
4,执行before函数(如果有)
5,询问问题,过滤文件,渲染模板文件
6,执行after函数(如果有)
7,构建项目,
8,构建完成后,有complete函数则执行,
没有则打印配置对象中的completeMessage信息,
有错误就执行回调函数done(err)
其中还用到了lib文件夹中的其他文件,简要解释:
options.js
获取模板配置文件
设置name字段并检测name是否合法
设置author
getMetadata:获取meta.js或meta.json中的配置信息
setDefault: 向配置对象添加默认字段值
setValidateName: 检测name是否合法
git-user.js
用于获取本地的git配置的用户名和邮件,并返回格式 姓名<邮箱> 的字符串。
eval.js
在data的作用域执行exp表达式并返回其执行得到的值
ask.js
将meta.js或meta.json中prompts字段解析成问题并询问
filter.js
根据metalsmith.metadata()删除不需要模板文件
local-path.js
isLocalPath:
UNIX (以“.”或者"/"开头) WINDOWS(以形如:“C:”的方式开头)
getTemplatePath:
templatePath是绝对路径返回templatePath,否则转为绝对路径并规范化
check-version.js
检查本地node版本,是否达到package.json中对node版本的要求
获取vue-cli最新版本号,和package.json中version字段比较,提示升级
warnings.js
v2SuffixTemplatesDeprecated:
提示“-2.0”模板已弃用,官方默认2.0。不需要用“-2.0”区分1.0和2.0
v2BranchIsNowDefault:
vue-init中已被注释,默认使用2.0
logger.js
通过以上代码,了解从vue init到下载模板,生成脚手架的整个过程和原理
这样就可以使用vue init命令从指定仓库获取自定义Vue脚手架模板了
例如:我们可以fork一份官方的webpack脚手架模板到自己的github仓库
官方webpack模板地址:https://github.com/vuejs-templates/webpack
然后,通过vue init username/repo my-project生成Vue脚手架
如:vue init BraveWangDev/webpack my-project
通过分析Vue cli的命令源码,了解了vue所提供命令的运行机制
对于自定义Vue脚手架模板来讲,
vue-init、generate.js、options.js、ask.js、filter.js,
这五个文件构成了vue-cli构建项目的主流程
通过vue-cli获取的模板工程一定要具备以下两点:
1,工程根目录一定要有meta.js文件
2,工程下一定要有template文件夹,这里边才是真正的脚手架模板
好了,接下来就要开始自定义Vue脚手架模板了