前言:这一章是重点!!!从上而下,逐行解析代码
class Creator extends EventEmitter { // Creator继承EventEmitter,EventEmitter就是node的事件模块
// name就是我们传的项目名如hello-word, context是路径, promptModules是一些自定义的inquirer配置,如vue-router,vuex
constructor (name, context, promptModules) {
super()
this.name = name
this.context = process.env.VUE_CLI_CONTEXT = context
const { presetPrompt, featurePrompt } = this.resolveIntroPrompts() // 自定义的一个方法
/**
presetPrompt = { // 这个就是上面章节所说的inquirer库需要的参数,说白点就是设置用户选择项
name: 'preset',
type: 'list',
message: `Please pick a preset:`,
choices: [
...presetChoices,
{
name: 'Manually select features',
value: '__manual__'
}
]
}
**/
this.presetPrompt = presetPrompt
/**
featurePrompt = { // 同上都是选项配置
name: 'features',
when: isManualMode,
type: 'checkbox',
message: 'Check the features needed for your project:',
choices: [],
pageSize: 10
}
**/
this.featurePrompt = featurePrompt
this.outroPrompts = this.resolveOutroPrompts()
this.injectedPrompts = []
this.promptCompleteCbs = []
this.afterInvokeCbs = []
this.afterAnyInvokeCbs = []
this.run = this.run.bind(this)
const promptAPI = new PromptModuleAPI(this) //选项初始化
promptModules.forEach(m => m(promptAPI)) //选项初始化
}
}
着重讲router,即用户选择router之后的逻辑
毕竟一般创建vue项目的时候,都会依赖vue-router。
// promptModules是传递过来的参数,是一个数组里面的内容类似于[require('../promptModules/router')]
//返回的是一个函数
//m(),执行当前函数,并且传入promptAPI
// promptAPI就简单的理解为一个对象,可以方便调用featurePrompt,presetPrompt
promptModules.forEach(m => m(promptAPI))
require('../promptModules/router'),看看这里面到底是个什么东东
module.exports = cli => {
cli.injectFeature({
name: 'Router',
value: 'router',
description: 'Structure the app with dynamic pages',
link: 'https://router.vuejs.org/'
})
cli.injectPrompt({
name: 'historyMode',
when: answers => answers.features.includes('router'),
type: 'confirm',
message: `Use history mode for router? ${chalk.yellow(`(Requires proper server setup for index fallback in production)`)}`,
description: `By using the HTML5 History API, the URLs don't need the '#' character anymore.`,
link: 'https://router.vuejs.org/guide/essentials/history-mode.html'
})
cli.onPromptComplete((answers, options) => {
if (answers.features.includes('router')) {
options.plugins['@vue/cli-plugin-router'] = {
historyMode: answers.historyMode
}
}
})
}
// 显而易见,这个就是往featurePrompt里面插入参数,featurePrompt初始化的时候并没有那些vuex,router,ts的选项,是在promptModules遍历的时候插入进去的。
**小结一下: 回顾上一章节 const creator = new Creator(name, targetDir, getPromptModules()),这里其实还知识初始化选项参数,
await creator.create(options),执行inquirer.prompt,让用户进行配置。**
讲解create方法,这个方法有点长
注: 我会简化来讲
// 前面有一系列判断,但最重要是这个方法,执行inquirer.prompt,得到用户的选择参数
preset = await this.resolvePreset(cliOptions.preset, cliOptions.clone)
得到preset后就简单了,比如我选择了router,那么preset里面就会包含router,babel,如果没选就会走默认值
// 当用户选择了router之后
preset.plugins['@vue/cli-plugin-router'] = {}
// 上面设置完plugin之后,下面就是便利plugins,设置package.json依赖
const deps = Object.keys(preset.plugins)
pkg.devDependencies[dep] = (
preset.plugins[dep].version ||
((/^@vue/.test(dep)) ? `^${latestMinor}` : `latest`)
)
})
// 创建完直接写文件了
await writeFileTree(context, {
'package.json': JSON.stringify(pkg, null, 2)
})
generator.js (主要文件是在这里创建出来的)
// generator 这里是创建主要文件, 着重讲一下
// 这里真的比较绕,引入了很多自定义文件,各种跳转,我简化流程
const generator = new Generator(context, {
pkg,
plugins,
afterInvokeCbs,
afterAnyInvokeCbs
})
await generator.generate({
extractConfigFiles: preset.useConfigFiles
})
进入generator.generate
async generate ({
extractConfigFiles = false,
checkExisting = false
} = {}) {
// 删除了大部分代码
await this.resolveFiles() //这里是获取模板文件
await writeFileTree(this.context, this.files, initialFiles) // 真正的写文件file write
}
进入this.resolveFiles
// 这里也删除很多代码
async resolveFiles () {
const files = this.files
console.log(files);
// 重点在这个中间件做处理
// 感觉越讲越复杂了,不在一步步深入了。
// middleware初始化的时候就得到了template,是一个ejs模板。
// 执行middleware的时候,会根据参数初始化模板,最后会得到一个files对象,key是路径地址,value是文件内容字符串
for (const middleware of this.fileMiddlewares) {
console.log(middleware)
await middleware(files, ejs.render)
}
}
// 最后根据files对象,直接写文件
await writeFileTree(this.context, this.files, initialFiles)
下面再晒几个截图,一目了然
这个是Generator对象,里面包含了各种各样的依赖信息
这个是template,里面是ejs模板,还需要render以下,替换一些变量
这个是files对象
后记:感觉这块内容压根不用说这么复杂,看了也没啥太多收获。脚手架文件生成如果是小公司或者自己玩,直接脚手架里面clone一个git项目,或者复制一个目录就可以了。毕竟公司范围可以统一技术栈。或者更简单粗暴一点,针对不同的选择,vuex,vue-router,ts等等,不同的选择,都准备一份模板,虽然技术含量低,但是可维护性自我觉得比这种复杂代码简单。
这次的源码阅读体验很不好,没啥收获,打了大量log才走通流程。后面着重讲另外一部分,npm run start之类的,至少可以看看人家的webpack是什么配置的,为什么要这么配置。