目錄javascript
webpack
做爲前端最火的構建工具,是前端自動化工具鏈最重要的部分,使用門檻較高。本系列是筆者本身的學習記錄,比較基礎,但願經過問題 + 解決方式的模式,之前端構建中遇到的具體需求爲出發點,學習webpack
工具中相應的處理辦法。(本篇中的參數配置及使用方式均基於webpack4.0版本
)html本篇摘要:前端
本篇主要介紹基於
webpack4.0
的splitChunks
分包技術。vue
javascript
之因此須要打包合併,是由於模塊化開發的存在。開發階段咱們須要將js
文件分開寫在不少零碎的文件中,方便調試和修改,但若是就這樣上線,那首頁的http
請求數量將直接爆炸。同一個項目,別人2-3個請求就拿到了須要的文件,而你的可能須要20-30個,結果就不用多說了。java
可是合併腳本可不是「把全部的碎片文件都拷貝到一個js
文件裏」這樣就能解決的,不只要解決命名空間衝突的問題,還須要兼容不一樣的模塊化方案,更別提根據模塊之間複雜的依賴關係來手動肯定模塊的加載順序了,因此利用自動化工具來將開發階段的js
腳本碎片進行合併和優化是很是有必要的。node
TS
或ES6
代碼的編譯)babel
是ES6
語法的轉換工具,對babel
不瞭解的讀者能夠先閱讀《大前端的自動化工廠(3)——Babel》一文進行了解,babel
與webpack
結合使用的方法也在其中作了介紹,此處僅提供基本配置:webpack
webpack.config.js
:ios
... module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: [ { loader: 'babel-loader' } ] } ] }, ...
.babelrc
:es6
{ "presets":[ ["env",{ "targets":{ "browsers":"last 2 versions" } } ]], "plugins": [ "babel-plugin-transform-runtime" ] }
使用webpack
對腳本進行合併是很是方便的,畢竟模塊管理和文件合併這兩個功能是webpack
最初設計的主要用途,直到涉及到分包和懶加載的話題時纔會變得複雜。webpack
使用起來很方便,是由於實現了對各類不一樣模塊規範的兼容處理,對前端開發者來講,理解這種兼容性實現的方式比學習如何配置webpack
更爲重要。webpack
默認支持的是CommonJs
規範,但同時爲了擴展其使用場景,webpack
在後續的版本迭代中也加入了對ES harmony
等其餘規範定義模塊的兼容處理,具體的處理方式將在下一章《webpack4.0各個擊破(5)—— Module篇》詳細分析。web
webpack
的輸出的文件中能夠看到以下的部分:
/******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ }
上面的__webpack_require__( )
方法就是webpack
的模塊加載器,很容易看出其中對於已加載的模塊是有統一的installedModules
對象來管理的,這樣就避免了模塊重複加載的問題。而公共模塊通常也須要從bundle.js
文件中提取出來,這涉及到下一節的「代碼分割」的內容。
1. 爲何要進行代碼分割?
代碼分割最基本的任務是分離出第三方依賴庫,由於第三方庫的內容可能好久都不會變更,因此用來標記變化的摘要哈希contentHash
也好久不變,這也就意味着咱們能夠利用本地緩存來避免沒有必要的重複打包,並利用瀏覽器緩存避免冗餘的客戶端加載。另外當項目發佈新版本時,若是第三方依賴的contentHash
沒有變化,就可使用客戶端原來的緩存文件(通用的作法通常是給靜態資源請求設置一個很大的max-age
),提高訪問速度。另一些場景中,代碼分割也能夠提供對腳本在整個加載週期內的加載時機的控制能力。
2. 代碼分割的使用場景
舉個很常見的例子,好比你在作一個數據可視化類型的網站,引用到了百度的Echarts
做爲第三方庫來渲染圖表,若是你將本身的代碼和Echarts
打包在一塊兒生成一個main.bundle.js
文件,這樣的結果就是在一個網速欠佳的環境下打開你的網站時,用戶可能須要面對很長時間的白屏,你很快就會想到將Echarts
從主文件中剝離出來,讓體積較小的主文件先在界面上渲染出一些動畫或是提示信息,而後再去加載Echarts
,而分離出的Echarts
也能夠從速度更快的CDN
節點獲取,若是加載某個體積龐大的庫,你也能夠選擇使用懶加載的方案,將腳本的下載時機延遲到用戶真正使用對應的功能以前。這就是一種人工的代碼分割。
從上面的例子整個的生命週期來看,咱們將本來一次就能夠加載完的腳本拆分爲了兩次,這無疑會加劇服務端的性能開銷,畢竟創建TCP鏈接是一種開銷很大的操做,但這樣作卻能夠換來對渲染節奏的控制和用戶體驗的提高,異步模塊和懶加載模塊從宏觀上來說實際上都屬於代碼分割的範疇。code splitting
最極端的情況其實就是拆分紅打包前的原貌,也就是源碼直接上線。
3. 代碼分割的本質
代碼分割的本質,就是在「源碼直接上線」和「打包爲惟一的腳本main.bundle.js」這兩種極端方案之間尋找一種更符合實際場景的中間狀態,用可接受的服務器性能壓力增長來換取更好的用戶體驗。
4. 配置代碼分割
code-splitting
技術的配置和使用方法將在下一小節詳細描述。
5. 更細緻的代碼分割
感興趣的讀者能夠參考來自google開發者社區的文章《Reduce JavaScript Payloads with Code Splitting》自行研究。
webpack4
中已經內置了UglifyJs
插件,當打包模式參數mode
設置爲production
時就會自動開啓,固然這不是惟一的選擇,babel
的插件中也能提供代碼壓縮的處理,具體的效果和原理筆者還沒有深究,感興趣的讀者能夠自行研究。
webpack4
廢棄了CommonsChunkPlugin
插件,使用optimization.splitChunks
和optimization.runtimeChunk
來代替,緣由能夠參考《webpack4:連奏中的進化》一文。關於runtimeChunk
參數,有的文章說是提取出入口chunk中的runtime部分,造成一個單獨的文件,因爲這部分不常變化,能夠利用緩存。google開發者社區的博文是這樣描述的:
The
runtimeChunk
option is also specified to move webpack's runtime into thevendors
chunk to avoid duplication of it in our app code.
splitChunks
中默認的代碼自動分割要求是下面這樣的:
node_modules中的模塊或其餘被重複引用的模塊
就是說若是引用的模塊來自node_modules
,那麼只要它被引用,那麼知足其餘條件時就能夠進行自動分割。不然該模塊須要被重複引用才繼續判斷其餘條件。(對應的就是下文配置選項中的minChunks
爲1或2的場景)
分離前模塊最小體積下限(默認30k,可修改)
30k是官方給出的默認數值,它是能夠修改的,上一節中已經講過,每一次分包對應的都是服務端的性能開銷的增長,因此必需要考慮分包的性價比。
對於異步模塊,生成的公共模塊文件不能超出5個(可修改)
觸發了懶加載模塊的下載時,併發請求不能超過5個,對於稍微瞭解過服務端技術的開發者來講,【高併發】和【壓力測試】這樣的關鍵詞應該不會陌生。
對於入口模塊,抽離出的公共模塊文件不能超出3個(可修改)
也就是說一個入口文件的最大並行請求默認不得超過3個,緣由同上。
splitChunks
的在webpack
4.0以上版本中的用法是下面這樣的:
module.exports = { //... optimization: { splitChunks: { chunks: 'async',//默認只做用於異步模塊,爲`all`時對全部模塊生效,`initial`對同步模塊有效 minSize: 30000,//合併前模塊文件的體積 minChunks: 1,//最少被引用次數 maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~',//自動命名鏈接符 cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, minChunks:1,//敲黑板 priority: -10//優先級更高 }, default: { test: /[\\/]src[\\/]js[\\/]/ minChunks: 2,//通常爲非第三方公共模塊 priority: -20, reuseExistingChunk: true } }, runtimeChunk:{ name:'manifest' } } }
注:實例中使用的demo及配置文件已放在附件中。
單頁面應用
單頁面應用只有一個入口文件,splitChunks
的主要做用是將引用的第三方庫拆分出來。從下面的分包結果就能夠看出,node_modules
中的第三方引用被分離了出來,放在了vendors-main.[hash].js
中。
多頁面應用
多頁面應用的情形稍顯複雜,以《webpack4:連奏中的進化》一文中的例子進行代碼分割處理,源碼的依賴關係爲:
entryA.js: vue vuex component10k entryB.js: vue axios component10k entryC.js: vue vuex axios component10k
通過代碼分割後獲得的包以下圖所示:
splitChunks
提供了更精確的分割策略,可是彷佛沒法直接經過html-webpack-plugin
配置參數來動態解決分割後代碼的注入問題,由於分包名稱是不肯定的。這個場景在使用chunks:'async'
默認配置時是不存在的,由於異步模塊的引用代碼是不須要以<script>
標籤的形式注入html
文件的。
當chunks
配置項設置爲all
或initial
時,就會有問題,例如上面示例中,經過在html-webpack-plugin
中配置excludeChunks
能夠去除page和about這兩個chunk,可是卻沒法提早排除vendors-about-page這個chunk,由於打包前沒法知道是否會生成這樣一個chunk。這個場景筆者並無找到現成的解決方案,對此場景有需求的讀者也許能夠經過使用html-webpack-plugin
的事件擴展來處理此類場景,也可使用折中方案,就是第一次打包後記錄下新生成的chunk名稱,按需填寫至html-webpack-plugin
的chunks
配置項裏。
### 4.4 結果分析
經過Bundle Buddy
分析工具或webpack-bundle-analyser
插件就能夠看到分包先後對於公共代碼的抽取帶來的影響(圖片來自參考文獻的博文):
【1】附加中文件說明:
webpack.spa.config.js
——單頁面應用代碼分割配置實例main.js
——單頁面應用入口文件webpack.multi.config.js
——多頁面應用代碼分割配置實例entryA.js
,entryB.js
,entryC.js
——多頁面應用的3個入口