您好,登錄后才能下訂單哦!
webpack
作為前端最火的構建工具,是前端自動化工具鏈最重要的部分,使用門檻較高。本系列是筆者自己的學習記錄,比較基礎,希望通過問題 + 解決方式的模式,以前端構建中遇到的具體需求為出發點,學習webpack
工具中相應的處理辦法。(本篇中的參數配置及使用方式均基于webpack4.0版本
)本篇摘要:
本篇主要介紹基于
webpack4.0
的splitChunks
分包技術。
[TOC]
javascript
之所以需要打包合并,是因為模塊化開發的存在。開發階段我們需要將js
文件分開寫在很多零碎的文件中,方便調試和修改,但如果就這樣上線,那首頁的http
請求數量將直接爆炸。同一個項目,別人2-3個請求就拿到了需要的文件,而你的可能需要20-30個,結果就不用多說了。
但是合并腳本可不是“把所有的碎片文件都拷貝到一個js
文件里”這樣就能解決的,不僅要解決命名空間沖突的問題,還需要兼容不同的模塊化方案,更別提根據模塊之間復雜的依賴關系來手動確定模塊的加載順序了,所以利用自動化工具來將開發階段的js
腳本碎片進行合并和優化是非常有必要的。
TS
或ES6
代碼的編譯)babel
是ES6
語法的轉換工具,對babel
不了解的讀者可以先閱讀《大前端的自動化工廠(3)——Babel》一文進行了解,babel
與webpack
結合使用的方法也在其中做了介紹,此處僅提供基本配置:
webpack.config.js
:
...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader'
}
]
}
]
},
...
.babelrc
:
{
"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篇》詳細分析。
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 the?vendors
?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
配置項里。
通過Bundle Buddy
分析工具或webpack-bundle-analyser
插件就可以看到分包前后對于公共代碼的抽取帶來的影響(圖片來自參考文獻的博文):
【1】附加中文件說明:
webpack.spa.config.js
——單頁面應用代碼分割配置實例main.js
——單頁面應用入口文件webpack.multi.config.js
——多頁面應用代碼分割配置實例entryA.js
,entryB.js
,entryC.js
——多頁面應用的3個入口【2】參考文獻: 《Reduce JavaScript Payloads with Code Splitting》
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。