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)

clipboard.png
clipboard.png

得到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對象,裏面包含了各種各樣的依賴信息
clipboard.png

這個是template,裏面是ejs模板,還需要render以下,替換一些變量
clipboard.png

這個是files對象
clipboard.png

後記:感覺這塊內容壓根不用說這麼複雜,看了也沒啥太多收穫。腳手架文件生成如果是小公司或者自己玩,直接腳手架裏面clone一個git項目,或者複製一個目錄就可以了。畢竟公司範圍可以統一技術棧。或者更簡單粗暴一點,針對不同的選擇,vuex,vue-router,ts等等,不同的選擇,都準備一份模板,雖然技術含量低,但是可維護性自我覺得比這種複雜代碼簡單。


這次的源碼閱讀體驗很不好,沒啥收穫,打了大量log才走通流程。後面着重講另外一部分,npm run start之類的,至少可以看看人家的webpack是什麼配置的,爲什麼要這麼配置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章