目前,,幾乎所有業(yè)務(wù)的開(kāi)發(fā)構(gòu)建都會(huì)用到 webpack ,。的確,,作為模塊加載和打包神器,只需配置幾個(gè)文件,,加載各種 loader 就可以享受無(wú)痛流程化開(kāi)發(fā),。但對(duì)于 webpack 這樣一個(gè)復(fù)雜度較高的插件集合,它的整體流程及思想對(duì)我們來(lái)說(shuō)還是很透明的,。那么接下來(lái)我會(huì)帶你了解 webpack 這樣一個(gè)構(gòu)建黑盒,,首先來(lái)談?wù)勊牧鞒獭?/span> 首先是準(zhǔn)備工作 1. webstorm 中配置 webpack-webstorm-debugger-script 在開(kāi)始了解之前,必須要能對(duì) webpack 整個(gè)流程進(jìn)行 debug ,,配置過(guò)程比較簡(jiǎn)單,。 先將 webpack-webstorm-debugger-script 中的 webstorm-debugger.js 置于webpack.config.js 的同一目錄下,搭建好你的腳手架后就可以直接 Debug 這個(gè) webstorm-debugger.js 文件了,。 2. webpack.config.js 配置 估計(jì)大家對(duì) webpack.config.js 的配置也嘗試過(guò)不少次了,,這里就大致對(duì)這個(gè)配置文件進(jìn)行個(gè)分析。
除此之外再大致介紹下 webpack 的一些核心概念: loader:能轉(zhuǎn)換各類資源,,并處理成對(duì)應(yīng)模塊的加載器,。loader 間可以串行使用。 chunk:code splitting 后的產(chǎn)物,,也就是按需加載的分塊,,裝載了不同的 module。 對(duì)于 module 和 chunk 的關(guān)系可以參照 webpack 官方的這張圖: plugin:webpack 的插件實(shí)體,,這里以 UglifyJsPlugin 為例,。
在 webpack 中你經(jīng)常可以看到 compilation.plugin(‘xxx’, callback) ,,你可以把它當(dāng)作是一個(gè)事件的綁定,,這些事件在打包時(shí)由 webpack 來(lái)觸發(fā)。 3. 流程總覽 在具體流程學(xué)習(xí)前,,可以先通過(guò)這幅 webpack 整體流程圖 了解一下大致流程(建議保存下來(lái)查看),。 shell 與 config 解析 每次在命令行輸入 webpack 后,操作系統(tǒng)都會(huì)去調(diào)用 ./node_modules/.bin/webpack 這個(gè) shell 腳本,。這個(gè)腳本會(huì)去調(diào)用 ./node_modules/webpack/bin/webpack.js 并追加輸入的參數(shù),,如 -p , -w 。(圖中 webpack.js 是 webpack 的啟動(dòng)文件,,而 $@ 是后綴參數(shù)) 在 webpack.js 這個(gè)文件中 webpack 通過(guò) optimist 將用戶配置的 webpack.config.js 和 shell 腳本傳過(guò)來(lái)的參數(shù)整合成 options 對(duì)象傳到了下一個(gè)流程的控制對(duì)象中,。 1. optimist 和 commander 一樣,optimist 實(shí)現(xiàn)了 node 命令行的解析,,其 API 調(diào)用非常方便,。
獲取到后綴參數(shù)后,optimist 分析參數(shù)并以鍵值對(duì)的形式把參數(shù)對(duì)象保存在 optimist.argv 中,,來(lái)看看 argv 究竟有什么,?
2. config 合并與插件加載 在加載插件之前,webpack 將 webpack.config.js 中的各個(gè)配置項(xiàng)拷貝到 options 對(duì)象中,并加載用戶配置在 webpack.config.js 的 plugins ,。接著 optimist.argv 會(huì)被傳入到./node_modules/webpack/bin/convert-argv.js 中,,通過(guò)判斷 argv 中參數(shù)的值決定是否去加載對(duì)應(yīng)插件。(至于 webpack 插件運(yùn)行機(jī)制,,在之后的運(yùn)行機(jī)制篇會(huì)提到)
options 作為最后返回結(jié)果,,包含了之后構(gòu)建階段所需的重要信息。
這和 webpack.config.js 的配置非常相似,,只是多了一些經(jīng) shell 傳入的插件對(duì)象,。插件對(duì)象一初始化完畢, options 也就傳入到了下個(gè)流程中,。
編譯與構(gòu)建流程 在加載配置文件和 shell 后綴參數(shù)申明的插件,,并傳入構(gòu)建信息 options 對(duì)象后,,開(kāi)始整個(gè) webpack 打包最漫長(zhǎng)的一步,。而這個(gè)時(shí)候,真正的 webpack 對(duì)象才剛被初始化,,具體的初始化邏輯在 lib/webpack.js 中,,如下:
webpack 的實(shí)際入口是 Compiler 中的 run 方法,run 一旦執(zhí)行后,,就開(kāi)始了編譯和構(gòu)建流程 ,,其中有幾個(gè)比較關(guān)鍵的 webpack 事件節(jié)點(diǎn)。
1. 核心對(duì)象 Compilation compiler.run 后首先會(huì)觸發(fā) compile ,,這一步會(huì)構(gòu)建出 Compilation 對(duì)象: 這個(gè)對(duì)象有兩個(gè)作用,一是負(fù)責(zé)組織整個(gè)打包過(guò)程,,包含了每個(gè)構(gòu)建環(huán)節(jié)及輸出環(huán)節(jié)所對(duì)應(yīng)的方法,,可以從圖中看到比較關(guān)鍵的步驟,如 addEntry() , _addModuleChain() ,buildModule() , seal() , createChunkAssets() (在每一個(gè)節(jié)點(diǎn)都會(huì)觸發(fā) webpack 事件去調(diào)用各插件),。二是該對(duì)象內(nèi)部存放著所有 module ,,chunk,生成的 asset 以及用來(lái)生成最后打包文件的 template 的信息,。 2. 編譯與構(gòu)建主流程 在創(chuàng)建 module 之前,,Compiler 會(huì)觸發(fā) make,并調(diào)用 Compilation.addEntry 方法,,通過(guò) options 對(duì)象的 entry 字段找到我們的入口js文件,。之后,在 addEntry 中調(diào)用私有方法_addModuleChain ,,這個(gè)方法主要做了兩件事情,。一是根據(jù)模塊的類型獲取對(duì)應(yīng)的模塊工廠并創(chuàng)建模塊,二是構(gòu)建模塊。 而構(gòu)建模塊作為最耗時(shí)的一步,,又可細(xì)化為三步: 調(diào)用各 loader 處理模塊之間的依賴webpack 提供的一個(gè)很大的便利就是能將所有資源都整合成模塊,,不僅僅是 js 文件。所以需要一些 loader ,,比如 url-loader ,, jsx-loader , css-loader 等等來(lái)讓我們可以直接在源文件中引用各類資源,。webpack 調(diào)用 doBuild() ,,對(duì)每一個(gè) require() 用對(duì)應(yīng)的 loader 進(jìn)行加工,最后生成一個(gè) js module,。
調(diào)用 acorn 解析經(jīng) loader 處理后的源文件生成抽象語(yǔ)法樹(shù) AST
遍歷 AST,構(gòu)建該模塊所依賴的模塊對(duì)于當(dāng)前模塊,,或許存在著多個(gè)依賴模塊,。當(dāng)前模塊會(huì)開(kāi)辟一個(gè)依賴模塊的數(shù)組,在遍歷 AST 時(shí),,將 require() 中的模塊通過(guò) addDependency() 添加到數(shù)組中,。當(dāng)前模塊構(gòu)建完成后,webpack 調(diào)用 processModuleDependencies 開(kāi)始遞歸處理依賴的 module,,接著就會(huì)重復(fù)之前的構(gòu)建步驟,。
3. 構(gòu)建細(xì)節(jié) module 是 webpack 構(gòu)建的核心實(shí)體,也是所有 module 的 父類,,它有幾種不同子類:NormalModule , MultiModule , ContextModule , DelegatedModule 等,。但這些核心實(shí)體都是在構(gòu)建中都會(huì)去調(diào)用對(duì)應(yīng)方法,也就是 build() ,。來(lái)看看其中具體做了什么:
對(duì)于每一個(gè) module ,,它都會(huì)有這樣一個(gè)構(gòu)建方法。當(dāng)然,,它還包括了從構(gòu)建到輸出的一系列的有關(guān) module 生命周期的函數(shù),,我們通過(guò) module 父類類圖其子類類圖(這里以 NormalModule 為例)來(lái)觀察其真實(shí)形態(tài): 可以看到無(wú)論是構(gòu)建流程,處理依賴流程,,包括后面的封裝流程都是與 module 密切相關(guān)的,。 打包輸出 在所有模塊及其依賴模塊 build 完成后,webpack 會(huì)監(jiān)聽(tīng) seal 事件調(diào)用各插件對(duì)構(gòu)建后的結(jié)果進(jìn)行封裝,,要逐次對(duì)每個(gè) module 和 chunk 進(jìn)行整理,,生成編譯后的源碼,,合并,拆分,,生成 hash ,。 同時(shí)這是我們?cè)陂_(kāi)發(fā)時(shí)進(jìn)行代碼優(yōu)化和功能添加的關(guān)鍵環(huán)節(jié)。
1. 生成最終 assets 在封裝過(guò)程中,webpack 會(huì)調(diào)用 Compilation 中的 createChunkAssets 方法進(jìn)行打包后代碼的生成,。 createChunkAssets 流程如下: 不同的 Template從上圖可以看出通過(guò)判斷是入口 js 還是需要異步加載的 js 來(lái)選擇不同的模板對(duì)象進(jìn)行封裝,,入口 js 會(huì)采用 webpack 事件流的 render 事件來(lái)觸發(fā) Template類 中的renderChunkModules() (異步加載的 js 會(huì)調(diào)用 chunkTemplate 中的 render 方法)。
在 webpack 中有四個(gè) Template 的子類,,分別是 MainTemplate.js ,, ChunkTemplate.js,ModuleTemplate.js ,, HotUpdateChunkTemplate.js ,,前兩者先前已大致有介紹,而 ModuleTemplate 是對(duì)所有模塊進(jìn)行一個(gè)代碼生成,,HotUpdateChunkTemplate 是對(duì)熱替換模塊的一個(gè)處理,。 模塊封裝模塊在封裝的時(shí)候和它在構(gòu)建時(shí)一樣,,都是調(diào)用各模塊類中的方法,。封裝通過(guò)調(diào)用module.source() 來(lái)進(jìn)行各操作,比如說(shuō) require() 的替換,。
生成 assets各模塊進(jìn)行 doBlock 后,把 module 的最終代碼循環(huán)添加到 source 中,。一個(gè) source 對(duì)應(yīng)著一個(gè) asset 對(duì)象,,該對(duì)象保存了單個(gè)文件的文件名( name )和最終代碼( value )。 2. 輸出 最后一步,,webpack 調(diào)用 Compiler 中的 emitAssets() ,,按照 output 中的配置項(xiàng)將文件輸出到了對(duì)應(yīng)的 path 中,從而 webpack 整個(gè)打包過(guò)程結(jié)束,。要注意的是,,若想對(duì)結(jié)果進(jìn)行處理,則需要在 emit 觸發(fā)后對(duì)自定義插件進(jìn)行擴(kuò)展,。 webpack 的整體流程主要還是依賴于 compilation 和 module 這兩個(gè)對(duì)象,,但其思想遠(yuǎn)不止這么簡(jiǎn)單。最開(kāi)始也說(shuō)過(guò),,webpack 本質(zhì)是個(gè)插件集合,,并且由 tapable 控制各插件在 webpack 事件流上運(yùn)行,,至于具體的思想和細(xì)節(jié),將會(huì)在后一篇文章中提到,。同時(shí),,在業(yè)務(wù)開(kāi)發(fā)中,無(wú)論是為了提升構(gòu)建效率,,或是減小打包文件大小,,我們都可以通過(guò)編寫(xiě) webpack 插件來(lái)進(jìn)行流程上的控制,這個(gè)也會(huì)在之后提到,。 -文章結(jié)束- |
|
來(lái)自: 西北望msm66g9f > 《文件夾1》