Webpack基本架構淺析

文章webpack版本爲3.6.0css

前言

隨着掌握的前端基礎知識愈來愈多,對技術的要求逐漸不知足於實現便可,技術到了瓶頸期,本身也曾嘗試寫過一些開源庫,不過不多有滿意的做品,一般沒迭代幾個版本就沒有耐心繼續維護了。一般是面臨的情形是前期設計思路太過簡單致使後期擴展的時候須要重構大量的代碼(GG吧~),就比如一坨屎,再怎麼裝點,都很難把它當成蛋糕吃下去。前端

我認爲,突破這個瓶頸的關鍵就是學會深刻理解優秀開源庫背後的思路。有人可能會說,我用xxx已經好久了,可以熟練使用它解決各類棘手問題,對於它,我已經充分理解了。我想說的是,即使你對於它的使用已經達到了爐火純青的程度,可是站在使用者角度理解再「深」能有多深呢,不過是坐井觀天罷了。webpack

爲何Webpack

目前爲止,Webpack已經擁有39.9k的star,在前端代碼打包器領域內應該算是無敵的存在了吧。Webpack強大的生態圈和豐富的解決方案使得咱們在平常開發中很難逃脫它的魔爪。若是能學習到它背後的思路,對於技能樹的完善和水平層次的提升應該是很是有好處的。web

概要

若是要全面總結webpack的實現,估計寫10篇文章都不必定夠。爲了更加清晰地get到webpack的設計思路,會隱去webpack的大部分功能實現。編程

以實現簡單的js模塊打包功能爲背景,文章分爲3部分:架構

  1. BundleBuilder基本架構
  2. Webpack基本架構
  3. 學到了些什麼

相信你在閱讀完本文後會對Webpack的架構有個大概的瞭解,這應該會對你繼續深刻理解webpack其它功能的實現以及編寫插件會有所幫助。框架

BundleBuilder基本架構

簡單到不能再簡單的js模塊打包器函數

示意圖

BundleBuilder.JPG-26.6kB

BundleBuilder對象

  1. BundleBuilder對象接收並處理外部配置
  2. 根據配置選擇不一樣的ModuleResolver
  3. 使用ModuleResolver接收配置獲得最終文件內容
  4. 生成打包後的文件

ModuleResolver對象

  1. 接收從BundleBuilder傳進的配置
  2. 解析入口文件內容
  3. 提取子模塊路徑,並遞歸地解析子模塊
  4. 將引用的模塊路徑替換爲模塊id最終生成模塊文件

webpack基本架構

這個接下來依次講解webpack中幾個重要對象之間的關係,會以各自的視角描述幾個重要的過程。固然,就單單這幾個對象還不能徹底地描述流程上的全部內容。學習

Tapable插件功能

webpack 4.0的插件系統已經徹底重作並將Tapable更新到了1.0.0ui

在正式介紹幾個核心對象以前,你須要瞭解一下Tapable類。

簡單來講,Tapable爲一個對象提供了插件功能。若是你用過Vue.js或者React.js之類的框架,Tapable就是爲某個對象提供了至關於組件的生命週期功能,在外部你能夠經過調用這些生命週期鉤子監聽該對象。

固然,你還能夠在外部手動觸發對象的某個生命週期。

若是你想詳細瞭解Tapable的API能夠參考這裏(文中版本爲0.2.8)

Webpack主函數視角

最宏觀的視角

1. 合併外部與默認配置

1-1.PNG-220.1kB

2. 配置並建立compiler

1-1.PNG-125kB

3. 在compiler啓動前觸發compiler上的若干生命週期

其中生命週期包括:environment,after-environment,entry-option,after-plugins,after-resolvers

1-3.PNG-122.2kB

4. 啓動compiler

1-4.PNG-107.2kB

5. 將compiler運行後獲得的狀態信息打印出來

1-5.PNG-201.8kB

Compiler視角

1. 正式運行前依次觸發before-run和run生命週期

2-1.JPG-17.1kB

2. 建立params對象並觸發before-compile生命週期

2-2.JPG-16.2kB

3. 觸發compile生命週期並建立compilation對象

2-3.JPG-17.6kB

4. 觸發this-compilation和compilation生命週期

2-4.JPG-17.9kB

5. 觸發make生命週期並調用compilation.finish()

在make階段調用了compilation.addEntry(),開始構建模塊樹,構建完畢後調用compilation.finish(),記錄報錯信息

2-5.JPG-20.6kB

6. 調用compilation.seal()並觸發after-compile生命週期

compilation在seal過程當中作了不少工做,在compilation視角部分會講到,如今只需知道seal事後compilation生成了assets對象以供compiler生成文件

2-6.JPG-17.1kB

7. 拿到assets並在生成每一個assets對應的文件

2-7.JPG-22.2kB

8. 將警告信息和文件大小信息合成爲stats狀態信息

2-8.JPG-20kB

9. 觸發done生命週期並將stats狀態信息交給webpack主函數

2-9.JPG-17.4kB

Compilation構建模塊樹視角

當compiler命令compilation構建模塊樹以後compilation都作了些什麼

1. 使用moduleFactory建立空module

3-1.JPG-16.6kB

2. 命令module自行構建自身屬性,好比依賴的子模塊信息(dependency)

調用module.build()進行構建模塊自身屬性

3-2.JPG-24.4kB

3. 遞歸地重複1和2的操做,生成模塊樹

3-3.JPG-18.8kB

4. 將模塊樹記錄到chunk中

3-4.JPG-19.9kB

Compilation的seal視角

1. 配置chunk

4-1.JPG-25.4kB

2. 將所處模塊樹深度和引用順序等信息記錄在每一個模塊上

4-2.JPG-37.1kB

3. 將全部模塊按照引用順序排序

4-3.JPG-27.5kB

4. 觸發optimize-module-order生命週期並按照排序後的模塊順序爲每一個模塊編號

4-4.JPG-28.8kB

5. 使用template對象渲染出chunk的內容source

4-5.JPG-29.2kB

6. 拿到source後生成asset,添加到assets中

4-6.JPG-34.4kB

學到了什麼

引入插件系統

1. 存在的問題

能夠看到,BundleBuilder的架構中徹底沒有爲第三方提供接口,後期固然也能夠作成根據不一樣的外部配置項來實現一些有限的定製化需求。

可是,這樣爲了保證功能的多樣性,會頻繁修改打包器的內部實現。這種作法會使得整個打包器的穩定性不足,最終很是臃腫,維護困難。

2. webpack的作法

反觀webpack,它使用了一種很是聰明的方式。在保證基本架構的前提下,爲主流程上的大部分對象都引入插件系統,使用者能夠獲取到這些對象,而且在一些特定的時候運行使用者提供的代碼。這樣一來,社區的逐漸壯大保證了功能的多樣性,還把穩定性不足的風險留給用戶去處理,提升了整個打包器的可維護性。

過程粒度細化

1. 存在的問題

能夠看到,BundleBuilder最終生成文件內容只有一個過程,就是調用ModuleResolver獲取字符串。當這個過程當中的某一階段須要獨立進行的時候,不免會要重構代碼。若是內部實現是比較鬆耦合的,那麼重構的工做會比較輕鬆,可是像如今BundleBuilder這種實現,顯然要作的工做並很多。

2. webpack的作法

從接收配置到生成文件內容,從比較宏觀的角度,分爲構建,封裝,生成文件內容,三部分。

  1. 保證了內部修改的靈活性。若是要對過程再細分或者添加過程,實現起來會比較方便。
  2. 豐富了對外擴展的接口。很顯然,因爲webpack引入了插件系統,細化過程粒度應該是必然選擇,這樣會有效地增長用戶對整個打包過程的自定義能力。
  3. 提高了代碼的可維護性。當打包器在運行時出現了bug,粒度越小,越加方便定位問題。

更多類的抽象

1. 存在的問題

在BundleBuilder中,對於每一個模塊僅僅是經過路徑讀取它的文件內容,而後分析其子模塊的信息,最後生成處理後的模塊內容。這些都是過程。若是後面迭代時須要在打包後輸出一些log,如模塊警告,模塊路徑等與模塊相關的信息。以面向過程的編程方式固然也能夠實現,但這樣不免會增長實現難度,下降代碼可讀性。

2. webpack的作法

稍微搜索一下,不包括自帶插件,webpack總共有200多個用Class聲明的類。

  1. 結構化的數據。建立一個類就意味着咱們能統一不少有相同抽象含義的對象建立一樣的屬性,好比Module類,它能夠記錄不少與模塊相關的信息。
  2. 方便擴展不一樣種類的對象。好比模塊類,能夠經過繼承的方式衍生出,普通js文件模塊,css文件模塊等等。
  3. 多類意味着有承擔不一樣職責的對象。明確的職責分工,好比compiler僅僅負責compilation的建立,文件的生成和信息狀態的合成。ModuleFactory負責建立Module。一旦出了問題方便定位到責任人,下降了各個工做的耦合度。
  4. 對象間的解耦。好比compilation和Module兩個類,webpack其實也能夠直接使用compilation來直接建立Module,可是一旦Module的種類增長,不可避免地須要在compilation中寫一些條件語句,這樣,建立Module這部分的代碼會讓原本就有不少事情要作的compilation變得更加龐大。因此webpack引入了ModuleFactory,compilation只需調用ModuleFactory來建立Module就好,建立部分的邏輯則被分佈在了ModuleFactory中,將compilation與Module解耦,二者中一方發生變化,只需在ModuleFactory中增長邏輯便可。

感覺

因爲webpack過於龐大,看源碼的過程感受是在修行。寫這篇文章之初準備深刻到一些技術細節,後來感受意義不大。也嘗試過列舉在簡單js模塊打包流程上涉及到的默認插件,寫出來像API手冊,若是徹底寫完,體量可能都接近半本書了。最後,決定拿小學3年級畫畫水平,將最基本的架構關係畫出來。

最大的感覺就是:當你真的準備設計一個庫的時候,應該在實現以前充分列舉可能的應用場景,將充分抽象出穩定的基本架構,而後將難辦的部分,複雜度很高的部分,或者說定製化需求比較多的部分,採用開放插件的方式扔給使用者去解決。

相關文章
相關標籤/搜索