vue-cli脚手架源码解析(三)

前言:这一章是重点!!!从上而下,逐行解析代码


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)

vue-cli脚手架源码解析(三)
vue-cli脚手架源码解析(三)

得到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对象,里面包含了各种各样的依赖信息
vue-cli脚手架源码解析(三)

这个是template,里面是ejs模板,还需要render以下,替换一些变量
vue-cli脚手架源码解析(三)

这个是files对象
vue-cli脚手架源码解析(三)

后记:感觉这块内容压根不用说这么复杂,看了也没啥太多收获。脚手架文件生成如果是小公司或者自己玩,直接脚手架里面clone一个git项目,或者复制一个目录就可以了。毕竟公司范围可以统一技术栈。或者更简单粗暴一点,针对不同的选择,vuex,vue-router,ts等等,不同的选择,都准备一份模板,虽然技术含量低,但是可维护性自我觉得比这种复杂代码简单。


这次的源码阅读体验很不好,没啥收获,打了大量log才走通流程。后面着重讲另外一部分,npm run start之类的,至少可以看看人家的webpack是什么配置的,为什么要这么配置。

相关推荐