webpack的核心功能是經過抽離出不少插件來實現的,所以系統內功能的劃分粒度很細,這樣作到了完美解偶同時又分工明確,代碼容易維護。能夠說插件就是webpack的基石,這些基石又影響着流程的走向。這些鉤子是經過Tapable串起來的,能夠類比Vue框架的生命週期,webpack也有本身的生命週期,在週期裏邊會順序地觸發一些鉤子,掛載在這些鉤子上的插件得以執行,從而進行一些特定的邏輯處理。在插件裏邊,構建的實體或構建出來的數據結果都是可觸達的,這樣作實現了webpack的高度可擴展。瞭解了這些以後,咱們就再也不懷疑webpack是如何擁有如此豐富的生態體系及社區、如何達到了今天的高度。webpack
Compiler對象就是webpack的實體(是Tapable的實例),掌控者整個webpack的生命週期,他不執行具體的任務,只是進行一些調度工做(調兵遣將)。他建立了Compilation對象,Compilation任務執行完畢後會將最終的處理結果返回給Compiler。官網列出了該對象暴露出的全部鉤子。git
Compilation是編譯階段的主要執行者,(是Tapable的實例),執行模塊建立、依賴收集、分塊、打包等主要任務的對象。官網列出了該對象暴露出的全部鉤子。github
在第二章節,咱們瞭解到獲取到配置數據以後,啓動了compiler.run方法。其實在compiler啓動run以前,在webpack.js咱們會發現還作了不少初始化操做,好比增長默認操做,好比針對不一樣的配置項(如target、devtool)初始化相應的插件等。web
webpack支持傳入多個配置對象,好比一個library有多個構建目標,就須要傳入多個配置對象,每一個配置對象都會執行。若是傳入一個數組,初始化的就不是Compiler,而是MultiCompiler,最後會運做MultiCompiler上的run方法,在裏邊遍歷compilers對象,存放着Compiler數組,而後會依次調用Conmpiler的run方法。api
compiler的啓動是run方法,run方法裏邊主要關注兩個動做:調用了compile方法;聲明瞭調用compile傳入的回調函數onCompiled。
複製代碼
compile()數組
涉及webpack構建生命週期的幾個重要鉤子:promise
onCompiled()markdown
涉及webpack構建生命週期的最後幾個重要鉤子:emit、done。該方法至關於將Compilation的權限又收取回來。此時拿到的compilation對象是聚集了通過module解析、loader處理、template編譯後的全部資源文件。
該方法裏邊主要調用了emitAssets方法,該方法調用了emit鉤子(這一步咱們能夠獲取完整的構建數據),獲取compilation構建出來的全部的assets資源數據,裏邊遞歸的調用writeOut寫入最終的chunk文件,並調用done鉤子。框架
compilation開始於addEntry方法並結束於addEntry。
複製代碼
上述階段完成後,cimpiler調用了compilation的finish()、seal(),這裏咱們重點關注seal方法。async
爲方便源碼的學習,想獲取webpack執行過程當中鉤子的掛載及觸發狀況,改造了Tapable。主要是修改Hook.js文件。頂部須要引入fs庫。
const fs = require('fs') 複製代碼
_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); // ... } 複製代碼
// 改造insert方法,在方法最後插入一條語句 _insert(item){ fs.writeFileSync('/Users/eleme/Documents/my/test-webpack/taps.js', `${item.type}: ${item.name} \n`, { 'flag': 'a' }, () => {}) } 複製代碼
至此,咱們大體理了一下webpack構建的脈絡。webpack體系很是龐大,內部封裝了不少webpack本身的庫。學習webpack源碼的目的一個是學習好的構建思想,一個是方便本身在業務中開發插件,這裏有源碼註釋版及其餘資料可供拓展。