從前的日色變得慢,車,馬,郵件都慢,一輩子只夠愛一我的 -- 《從前慢》css
近期在團隊項目裏把Webpack升級到4.4.1,過程當中發現現存的升級文檔十分有限,踩了很多坑,好在升級以後提高還算顯著,production場景下第三方依賴打包速度提高76%,development場景下本地服務首次啓動提高效果約46%,再次啓動提高效果上升至63%。這裏將此次升級過程當中的點滴分享出來,但願對你們有所幫助。html
Webpack 4 發佈以後,議論最多的兩大特性,其一是零配置,其二是速度快(號稱提速上限98%)。聽起來十分美妙,在實地測試以前,首先從理論上分析一下可能性。react
一言以蔽之,約定優於配置。經過mode屬性將開發/生產(development/production)環境中經常使用的功能設置好默認值,用戶即來即用。webpack
Webpack 4取消了四個經常使用的用於性能優化的plugin(UglifyjsWebpackPlugin,CommonsChunkPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin),轉而提供了一個名爲optimization的配置項,用於接手以上四位的工做。 git
廢棄插件:UglifyjsWebpackPlugingithub
新增屬性:sideEffects,minimize等web
Tree shaking一直是一個美麗而高不可攀的話題。影響tree shaking的根本緣由在於side effects(反作用),其中最廣爲人知的一條side effect就是動態引入依賴的問題。得益於ES6的模塊化實現思路,全部的依賴必須位於文件頂部,靜態引入(然而import()的出現打破了這個規則),Webpack能夠在繪製依賴圖的時候進行靜態分析,從而將真正被引用的exports添加到bundle文件中,減小打包體積。然而不少熱度較高的第三方庫爲了考慮兼容性每每採用UMD實現,而其所支持的動態引入依賴的功能則致使真實的依賴圖可能要到運行時才能肯定,使得靜態分析難以發揮真正威力,tree shaking採用了保守策略,致使咱們發現沒有被用到的方法依然出如今了bundle文件中。typescript
一個好消息是許多第三方庫相繼推出了es版,配合tree-shaking食用,口感更佳,這也是官方號稱提速98%的重要前提之一(冷漠臉)。壞消息是ES6其實也提供import()方法支持動態引入依賴,因此如下寫法其實也是徹底行的通的。。。還記得那些年咱們追過的沈佳宜說過的話麼,「人生原本就有不少事情是徒勞無功的啊」。瀏覽器
if(Math.random() > 0.5) {
import('./a.js').then(() => {
...
})
} else {
import('./b.js').then(() => {
...
})
}
複製代碼
除此之外,爲了防止用戶不當心修改輸出元素的屬性,有些庫會將最終的輸出元素用Object.freeze方法包裹起來,這也屬於side effects之一,一樣也會對tree shaking產生影響。緩存
回到Webpack 4,官方提供了sideEffects屬性,經過將其設置爲false,能夠主動標識該類庫中的文件只執行簡單輸出,並無執行其餘操做,能夠放心shaking。除了能夠減少bundle文件的體積,同時也可以提高打包速度。爲了檢查side effects,Webpack須要在打包的時候將全部的文件執行一遍。而在設置sideEffects以後,則能夠跳過執行那些未被引用的文件,畢竟已經明確標識了「我是平民」。所以對於一些咱們本身開發的庫,設置sideEffects爲false大有裨益。
Minimize屬性就沒啥可多說的了,混淆壓縮文件。
廢棄插件:ModuleConcatenationPlugin
新增屬性:concatenateModules
//開啓前
[
/* 0 */
function(module, exports, require) {
var module_a = require(1)
console.log(module_a['default'])
}
/* 1 */
function(module, exports, require) {
exports['default'] = 'module A'
}
]
//開啓後
[
function(module, exports, require) {
var module_a_defaultExport = 'module A'
console.log(module_a_defaultExport)
}
]
複製代碼
concatenateModules開啓以後,能夠看出bundle文件中的函數聲明變少了,於是能夠帶來的好處,其一,文件的體積比以前更小了,其二,運行代碼時建立的函數做用域變少了,開銷也隨之變少了。不過scope hoisting的效果一樣也依賴於靜態分析,無奈命不禁我。
廢棄插件:CommonsChunkPlugin
新增屬性:splitChunks,runtimeChunk, occurrenceOrder等
splitChunks在Webpack 4裏被用於取代咱們熟悉CommonsChunkPlugin。讀到這裏不知道你有沒有發現其中的端倪,這是否意味着DllPlugin和CommonsChunkPlugin(splitChunks)能夠共存了呢?
在Webpack 4以前,二者並不能一塊兒使用,緣由有二
這塊功能實際上經過CommonsChunkPlugin設置兩個entry point也能夠實現,一個做爲業務代碼的入口,一個做爲vendors的入口。不過存在兩個問題,第一個問題是,儘管vendors被單獨設置了entry point,可是在每次啓動本地服務的時候,儘管打包的結果不變,hash值不變,瀏覽器的緩存文件也被充分利用了,它的打包過程依然會執行,因此啓動時間並不會縮短,第二個問題是,許多人在使用CommonsChunkPlugin的時候並無注意到Webpack會將runtime一塊兒打包進vendors文件,因此每次啓動的時候,儘管你並無修改任何第三方依賴,可是vendors文件的hash值卻變了,致使瀏覽器緩存實際上並無被利用起來。要解決這個問題,須要配置CommonsChunkPlugin將runtime單獨打包成一個文件。
然而到了Webpack 4,在CommonsChunkPlugin變成splitChunks以後,出於某些未知的緣由,二者兼容性的問題被解決了。。。Happy coding。
runtimeChunk之因此被單獨設置爲一個配置項,應該就是爲了主動幫助用戶避免上文所述的問題吧。
occurrenceOrder應用的場景是若是不手動設置chunk的名字,而採用默認值的話,Webpack將會用更短的名字去命名引用頻度更高的chunk。
廢棄插件:NoEmitOnErrorsPlugin
新增屬性:noEmitOnErrors
noEmitOnErrors在編譯出現錯誤時,用來跳過輸出階段。
Webpack 4同時實現了一套新的plugin機制,與性能相關的改進點是消除了對arguments的濫用。如同咱們推崇開發時定義類型,從而能夠避免JIT過程當中產生過多的重載函數,以及下降從新編譯的機率。
講了這麼多,最後分享一下個人實操經歷。Webpack 4爲用戶描繪的場景當然美好,然而帶來便利的同時也給開發者留下了很多麻煩。首當其衝的就是兼容性的問題,不少咱們經常使用的loader,plugin還沒有對此次升級作好準備,找到合適的替代工具以及積極改造自研的工具將成爲升級過程當中一場重要戰役。接下來我會針對在此次項目升級中我所遇到的兼容性問題以及最終採用的解決方案作一個總結,常規的Webpack 4配置能夠在官方demo 中找到答案。
Nothing special,主要仍是一個分類問題,如何識別存在公共依賴的第三方依賴,並將其分配到不一樣的entry中。例如antd和react都依賴了react,則應該將二者分配到不一樣的entry中。以及如何均勻的分配依賴到不一樣的entry中,使得打包以後的每一個entry大小相近。能夠說十分考驗一名配置工程師的功力和對源碼庫的瞭解程度。
module: {
rule: {
test: /\.tsx?$/,
use: [
'cache-loader',
{
loader: 'thread-loader',
options: {
workers: require('os').cpus().length - 1,
}
},
{
loader: 'ts-loader',
options: {
happyPackMode: true,
transpileOnly: true
}
}
]
}
}
plugins: [
new ForkTsCheckerWebpackPlugin()
]
複製代碼
最後秀一下數據吧
在展現最終結果以前須要聲明的一點是,因爲升級Webpack的同時,還解決了諸多兼容性問題,因此最終結果的表現不管優劣,都不只僅是Webpack的功過,loader以及plugin替換帶來的性能影響一樣不可忽略。至於如何到達提速98%,若是全部依賴所有更新成爲es版本的話。。。
DllPlugin + CommonsChunkPlugin對第三方依賴打包場景(production場景) Webpack 3.8.1的打包時長爲57411ms,Webpack 4的打包時長爲13959ms,提高效果約76%,詳情以下圖所示。
本地啓動(development場景) Webpack 3.8.1的啓動時長(僅包含業務代碼打包過程)爲42890ms,Webpack 4的首次啓動(cache文件還沒有產生)時長爲23017ms,Webpack 4的再次啓動(cache文件已經存在,並不是watch模式下的rebuild場景)時長爲15827ms,首次啓動提高效果約46%,再次啓動提高效果上升至63%,詳情以下圖所示。
在不糾結到底是Webpack仍是替換loader&plugin的功勞,以及升級過程當中遭遇的懵逼,躁鬱,崩潰的狀況下,此次升級仍是爲項目帶來了正反饋。若是你也是一名追求極致開發體驗的配置工程師的話,此次Webpack升級仍是值得嘗試的。最後但願文章中的內容可以有所幫助。