mirahs 2019-11-04
前言:这一章是重点!!!从上而下,逐行解析代码
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)) //选项初始化 } }
毕竟一般创建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,让用户进行配置。**
注: 我会简化来讲
// 前面有一系列判断,但最重要是这个方法,执行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 这里是创建主要文件, 着重讲一下 // 这里真的比较绕,引入了很多自定义文件,各种跳转,我简化流程 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是什么配置的,为什么要这么配置。