文章webpack版本爲3.6.0css
隨着掌握的前端基礎知識愈來愈多,對技術的要求逐漸不知足於實現便可,技術到了瓶頸期,本身也曾嘗試寫過一些開源庫,不過不多有滿意的做品,一般沒迭代幾個版本就沒有耐心繼續維護了。一般是面臨的情形是前期設計思路太過簡單致使後期擴展的時候須要重構大量的代碼(GG吧~),就比如一坨屎,再怎麼裝點,都很難把它當成蛋糕吃下去。前端
我認爲,突破這個瓶頸的關鍵就是學會深刻理解優秀開源庫背後的思路。有人可能會說,我用xxx已經好久了,可以熟練使用它解決各類棘手問題,對於它,我已經充分理解了。我想說的是,即使你對於它的使用已經達到了爐火純青的程度,可是站在使用者角度理解再「深」能有多深呢,不過是坐井觀天罷了。webpack
目前爲止,Webpack已經擁有39.9k的star,在前端代碼打包器領域內應該算是無敵的存在了吧。Webpack強大的生態圈和豐富的解決方案使得咱們在平常開發中很難逃脫它的魔爪。若是能學習到它背後的思路,對於技能樹的完善和水平層次的提升應該是很是有好處的。web
若是要全面總結webpack的實現,估計寫10篇文章都不必定夠。爲了更加清晰地get到webpack的設計思路,會隱去webpack的大部分功能實現。編程
以實現簡單的js模塊打包功能爲背景,文章分爲3部分:架構
相信你在閱讀完本文後會對Webpack的架構有個大概的瞭解,這應該會對你繼續深刻理解webpack其它功能的實現以及編寫插件會有所幫助。框架
簡單到不能再簡單的js模塊打包器函數
這個接下來依次講解webpack中幾個重要對象之間的關係,會以各自的視角描述幾個重要的過程。固然,就單單這幾個對象還不能徹底地描述流程上的全部內容。學習
webpack 4.0的插件系統已經徹底重作並將Tapable更新到了1.0.0ui
在正式介紹幾個核心對象以前,你須要瞭解一下Tapable類。
簡單來講,Tapable爲一個對象提供了插件功能。若是你用過Vue.js
或者React.js
之類的框架,Tapable就是爲某個對象提供了至關於組件的生命週期功能,在外部你能夠經過調用這些生命週期鉤子監聽該對象。
固然,你還能夠在外部手動觸發對象的某個生命週期。
若是你想詳細瞭解Tapable的API能夠參考這裏(文中版本爲0.2.8)
最宏觀的視角
1. 合併外部與默認配置
2. 配置並建立compiler
3. 在compiler啓動前觸發compiler上的若干生命週期
其中生命週期包括:environment,after-environment,entry-option,after-plugins,after-resolvers
4. 啓動compiler
5. 將compiler運行後獲得的狀態信息打印出來
1. 正式運行前依次觸發before-run和run生命週期
2. 建立params對象並觸發before-compile生命週期
3. 觸發compile生命週期並建立compilation對象
4. 觸發this-compilation和compilation生命週期
5. 觸發make生命週期並調用compilation.finish()
在make階段調用了compilation.addEntry(),開始構建模塊樹,構建完畢後調用compilation.finish(),記錄報錯信息
6. 調用compilation.seal()並觸發after-compile生命週期
compilation在seal過程當中作了不少工做,在compilation視角部分會講到,如今只需知道seal事後compilation生成了assets對象以供compiler生成文件
7. 拿到assets並在生成每一個assets對應的文件
8. 將警告信息和文件大小信息合成爲stats狀態信息
9. 觸發done生命週期並將stats狀態信息交給webpack主函數
當compiler命令compilation構建模塊樹以後compilation都作了些什麼
1. 使用moduleFactory建立空module
2. 命令module自行構建自身屬性,好比依賴的子模塊信息(dependency)
調用module.build()進行構建模塊自身屬性
3. 遞歸地重複1和2的操做,生成模塊樹
4. 將模塊樹記錄到chunk中
1. 配置chunk
2. 將所處模塊樹深度和引用順序等信息記錄在每一個模塊上
3. 將全部模塊按照引用順序排序
4. 觸發optimize-module-order生命週期並按照排序後的模塊順序爲每一個模塊編號
5. 使用template對象渲染出chunk的內容source
6. 拿到source後生成asset,添加到assets中
1. 存在的問題
能夠看到,BundleBuilder的架構中徹底沒有爲第三方提供接口,後期固然也能夠作成根據不一樣的外部配置項來實現一些有限的定製化需求。
可是,這樣爲了保證功能的多樣性,會頻繁修改打包器的內部實現。這種作法會使得整個打包器的穩定性不足,最終很是臃腫,維護困難。
2. webpack的作法
反觀webpack,它使用了一種很是聰明的方式。在保證基本架構的前提下,爲主流程上的大部分對象都引入插件系統,使用者能夠獲取到這些對象,而且在一些特定的時候運行使用者提供的代碼。這樣一來,社區的逐漸壯大保證了功能的多樣性,還把穩定性不足的風險留給用戶去處理,提升了整個打包器的可維護性。
1. 存在的問題
能夠看到,BundleBuilder最終生成文件內容只有一個過程,就是調用ModuleResolver獲取字符串。當這個過程當中的某一階段須要獨立進行的時候,不免會要重構代碼。若是內部實現是比較鬆耦合的,那麼重構的工做會比較輕鬆,可是像如今BundleBuilder這種實現,顯然要作的工做並很多。
2. webpack的作法
從接收配置到生成文件內容,從比較宏觀的角度,分爲構建,封裝,生成文件內容,三部分。
1. 存在的問題
在BundleBuilder中,對於每一個模塊僅僅是經過路徑讀取它的文件內容,而後分析其子模塊的信息,最後生成處理後的模塊內容。這些都是過程。若是後面迭代時須要在打包後輸出一些log,如模塊警告,模塊路徑等與模塊相關的信息。以面向過程的編程方式固然也能夠實現,但這樣不免會增長實現難度,下降代碼可讀性。
2. webpack的作法
稍微搜索一下,不包括自帶插件,webpack總共有200多個用Class
聲明的類。
因爲webpack過於龐大,看源碼的過程感受是在修行。寫這篇文章之初準備深刻到一些技術細節,後來感受意義不大。也嘗試過列舉在簡單js模塊打包流程上涉及到的默認插件,寫出來像API手冊,若是徹底寫完,體量可能都接近半本書了。最後,決定拿小學3年級畫畫水平,將最基本的架構關係畫出來。
最大的感覺就是:當你真的準備設計一個庫的時候,應該在實現以前充分列舉可能的應用場景,將充分抽象出穩定的基本架構,而後將難辦的部分,複雜度很高的部分,或者說定製化需求比較多的部分,採用開放插件的方式扔給使用者去解決。