webpack構建流程及梳理

摘要

webpack的核心功能是經過抽離出不少插件來實現的,所以系統內功能的劃分粒度很細,這樣作到了完美解偶同時又分工明確,代碼容易維護。能夠說插件就是webpack的基石,這些基石又影響着流程的走向。這些鉤子是經過Tapable串起來的,能夠類比Vue框架的生命週期,webpack也有本身的生命週期,在週期裏邊會順序地觸發一些鉤子,掛載在這些鉤子上的插件得以執行,從而進行一些特定的邏輯處理。在插件裏邊,構建的實體或構建出來的數據結果都是可觸達的,這樣作實現了webpack的高度可擴展。瞭解了這些以後,咱們就再也不懷疑webpack是如何擁有如此豐富的生態體系及社區、如何達到了今天的高度。webpack

關於Compiler

Compiler對象就是webpack的實體(是Tapable的實例),掌控者整個webpack的生命週期,他不執行具體的任務,只是進行一些調度工做(調兵遣將)。他建立了Compilation對象,Compilation任務執行完畢後會將最終的處理結果返回給Compiler。官網列出了該對象暴露出的全部鉤子。git

關於Compilation

Compilation是編譯階段的主要執行者,(是Tapable的實例),執行模塊建立、依賴收集、分塊、打包等主要任務的對象。官網列出了該對象暴露出的全部鉤子。github

在第二章節,咱們瞭解到獲取到配置數據以後,啓動了compiler.run方法。其實在compiler啓動run以前,在webpack.js咱們會發現還作了不少初始化操做,好比增長默認操做,好比針對不一樣的配置項(如target、devtool)初始化相應的插件等。web

webpack支持傳入多個配置對象,好比一個library有多個構建目標,就須要傳入多個配置對象,每一個配置對象都會執行。若是傳入一個數組,初始化的就不是Compiler,而是MultiCompiler,最後會運做MultiCompiler上的run方法,在裏邊遍歷compilers對象,存放着Compiler數組,而後會依次調用Conmpiler的run方法。api

Compiler

compiler的啓動是run方法,run方法裏邊主要關注兩個動做:調用了compile方法;聲明瞭調用compile傳入的回調函數onCompiled。
複製代碼
  1. compile()數組

    涉及webpack構建生命週期的幾個重要鉤子:promise

    1. compile上的鉤子:beforeCompile、compile、make(關鍵鉤子)、afterCompile、thisCompilation、compilation
    2. compilation上的鉤子:finish、seal
      在這個方法裏邊,主要是構建建立Compilation所須要的參數並建立了Compilation,這個參數就是後邊解析module須要用的工廠函數。
      make鉤子是一個關鍵的鉤子,調用make鉤子時傳入的是新建的compilation對象,在這個鉤子上掛載了一些入口插件的處理邏輯,這些入口插件裏邊調用compilation.addEntry(),此後,控制權就由Compiler轉移到了Compilation。在make鉤子的回調函數裏邊調用了compilation的finish、seal鉤子。
  2. onCompiled()markdown

    涉及webpack構建生命週期的最後幾個重要鉤子:emit、done。該方法至關於將Compilation的權限又收取回來。此時拿到的compilation對象是聚集了通過module解析、loader處理、template編譯後的全部資源文件。
    該方法裏邊主要調用了emitAssets方法,該方法調用了emit鉤子(這一步咱們能夠獲取完整的構建數據),獲取compilation構建出來的全部的assets資源數據,裏邊遞歸的調用writeOut寫入最終的chunk文件,並調用done鉤子。框架

Compilation

compilation開始於addEntry方法並結束於addEntry。
複製代碼
  1. addEntry(): 根據入口的配置模式,分爲單入口和多入口。該方法裏邊主要調用了_addModuleChain()。
  2. _addModuleChain(): 建立module。根據入口的不一樣,使用不一樣的模塊工廠(從建立Compilation傳入的參數中獲取,包括ContextModuleFactory或NormalModuleFactory)的create方法建立模塊,獲取模塊相關的parser、loader、hash等數據信息。
  3. buildModule(): 調用module.build()進行module構建。
  4. addModuleDependencies(): 構建成功以後,遞歸地獲取依賴模塊並構建(邏輯同_addModuleChain)。
  5. successEntry: 執行完上述的操做以後,在_addModuleChain的回調函數裏邊調用succeedEntry鉤子,在這個鉤子裏邊能夠獲取剛建立的module。而後將控制權返回給Compiler。

上述階段完成後,cimpiler調用了compilation的finish()、seal(),這裏咱們重點關注seal方法。async

  1. seal(): 該方法主要完成了chunk的構建。主要是收集modules、chunks,使用template(在Compilation的構建函裏初始化了幾種Template:MainTemplate、ChunkTemplate等)對chunk進行編譯。entry屬性配置的入口模塊使用的是MainTemplate,裏邊會加入啓動webpack多須要的代碼,建立並更新hash信息等,並調用emitAsset()會將最終的資源文件所有收集在assets對象裏邊。
  2. emitAsset(): 收集assets資源數據,多個插件都有調用該方法。

Tapable改造

爲方便源碼的學習,想獲取webpack執行過程當中鉤子的掛載及觸發狀況,改造了Tapable。主要是修改Hook.js文件。頂部須要引入fs庫。

const fs = require('fs')
複製代碼
  1. 在call方法裏插入邏輯
_fnCp(fn, name, type) {
      const _fn = (...arg) => {
            // console.log('hahaha:', arg)
            fs.writeFileSync('/Users/eleme/Documents/my/test-webpack/calls.js', `${type}: ${name} \n`, { 'flag': 'a' },  () => {})
                  return fn(...arg)
            }
      return _fn
}
// 改造tap、tapAsync、tapPromise方法
tap(options, fn) {
      // ...
      // options = Object.assign({ type: "sync", fn: fn }, options);
      options = Object.assign({ type: "sync", fn: this._fnCp(fn, options.name, "sync") }, options);
      // ...
}
tapAsync(options, fn) {
      // ...
      // options = Object.assign({ type: "async", fn: fn }, options);
      options = Object.assign({ type: "async", fn: this._fnCp(fn, options.name, "async") }, options);
      // ...
}
tapPromise(options, fn) {
      // ...
      // options = Object.assign({ type: "promise", fn: fn }, options);
      options = Object.assign({ type: "promise", fn: this._fnCp(fn, options.name, "promise") }, options);
      // ...
}
複製代碼
  1. 在tap方法插入邏輯
// 改造insert方法,在方法最後插入一條語句
_insert(item){
      fs.writeFileSync('/Users/eleme/Documents/my/test-webpack/taps.js', `${item.type}: ${item.name} \n`, { 'flag': 'a' },  () => {})
}
複製代碼

總結

至此,咱們大體理了一下webpack構建的脈絡。webpack體系很是龐大,內部封裝了不少webpack本身的庫。學習webpack源碼的目的一個是學習好的構建思想,一個是方便本身在業務中開發插件,這裏有源碼註釋版及其餘資料可供拓展。

相關文章
相關標籤/搜索