筆者最近在準備給fle-cli
升級到webpack4
版本,覺得有必要將探索過程的經驗分享給大家,遂決定寫這篇文章。(不知道fle-cli
?看這裡)
webpack4是大趨勢,升級是必然的,那麼為什麼現在才升級?
原因有以下幾個方面:
- 剛釋出的版本還不穩定,潛在風險大,而目前版本已更新到4.8.3,基本處於穩定;
- webpack社群工具未完全跟上節奏,好多工具都得自己搞,勞心勞力(其實主要就是懶哈哈);
- webpack本身及社群工具存在或多或少的問題,未經時間沉澱,維護成本高。
然而現在,筆者認為以上這些已經成熟,是時候來一波升級了。
前言
本文不會講解webpack配置的每個細節點,因為這些官方文件都可以看到。筆者會挑一些難以理解的新概念、可能會碰到的問題,以及筆者總結下來的優化方案來分享,希望可以給大家帶來一些幫助。
配置
mode
mode是webpack4新增的引數選項,它的值有3個:development、production、none,能夠幫助我們載入一些預設配置,none即不載入預設配置。下面將對應的預設配置列出來供大家參考,以免重複配置。
development
注重提升程式碼構建速度和開發體驗
module.exports = {
cache: true,
devtools: "eval",
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") })
]
}
複製程式碼
prodution
提供程式碼優化,如壓縮、作用域提升等
var UglifyJsPlugin = require(`uglifyjs-webpack-plugin`)
module.exports = {
plugins: [
new UglifyJsPlugin(/* ... */),
new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.NoEmitOnErrorsPlugin()
]
}
複製程式碼
optimization
這個選項也是webpack4新增的,主要是用來自定義一些優化打包策略。
minimizer
在production模式,該配置會預設為我們壓縮混淆程式碼,但這顯然滿足不了我們對於優化程式碼的訴求。下面筆者分享一套自身實踐總結下來的配置及解釋:
var UglifyJsPlugin = require(`uglifyjs-webpack-plugin`)
var OptimizeCssAssetsPlugin = require(`optimize-css-assets-webpack-plugin`)
module.exports = {
optimization: {
minimizer: [
// 自定義js優化配置,將會覆蓋預設配置
new UglifyJsPlugin({
exclude: /.min.js$/, // 過濾掉以".min.js"結尾的檔案,我們認為這個字尾本身就是已經壓縮好的程式碼,沒必要進行二次壓縮
cache: true,
parallel: true, // 開啟並行壓縮,充分利用cpu
sourceMap: false,
extractComments: false, // 移除註釋
uglifyOptions: {
compress: {
unused: true,
warnings: false,
drop_debugger: true
},
output: {
comments: false
}
}
}),
// 用於優化css檔案
new OptimizeCssAssetsPlugin({
assetNameRegExp: /.css$/g,
cssProcessorOptions: {
safe: true,
autoprefixer: { disable: true }, // 這裡是個大坑,稍後會提到
mergeLonghand: false,
discardComments: {
removeAll: true // 移除註釋
}
},
canPrint: true
})
]
}
}
複製程式碼
UglifyJsPlugin
這款外掛相信大家也是經常用到,這裡不再多說,這裡的亮點是過濾掉本身已經是壓縮的js檔案,能夠提升我們的編譯效率以及避免二次混淆壓縮而造成的未知bug。
OptimizeCssAssetsPlugin
這款外掛主要用來優化css檔案的輸出,預設使用cssnano
,其優化策略主要包括:擯棄重複的樣式定義、砍掉樣式規則中多餘的引數、移除不需要的瀏覽器字首等,更多優化規則看這裡。前文我們提到這裡有個大坑,相信你已經察覺到了,沒錯,就是這貨把我們通過autoprefixer
加好了字首給移除了。筆者查閱了許多資料,依舊沒有找到滿意的答案,沒辦法,只有硬著頭皮去原始碼中找答案了,於是便有了這段配置autoprefixer: { disable: true }
,禁用掉cssnano對於瀏覽器字首的處理。
runtimeChunk
分離出webpack編譯執行時的程式碼,也就是我們先前稱為manifest
的程式碼塊,好處是方便我們做檔案的持久化快取。它可以設定多種型別的值,具體可以看這裡,其中single
即將所有chunk的執行程式碼打包到一個檔案中,multiple
就是給每一個chunk的執行程式碼打包一個檔案。
我們可以配合InlineManifestWebpackPlugin
外掛將執行程式碼直接插入html檔案中,因為這段程式碼非常少,這樣做可以避免一次請求的開銷,但是新版外掛的配置和之前有些不太一樣,接下來詳細講解一下如何配置。
var HtmlWebpackPlugin = require(`html-webpack-plugin`)
var InlineManifestWebpackPlugin = require(`inline-manifest-webpack-plugin`)
module.exports = {
entry: {
app: `src/index.js`
},
optimization: {
runtimeChunk: `single`
// 等價於
// runtimeChunk: {
// name: `runtime`
// }
},
plugins: [
new HtmlWebpackPlugin({
title: `fle-cli`,
filename: `index.html`,
template: `xxx`,
inject: true,
chunks: [`runtime`, `app`], // 將runtime插入html中
chunksSortMode: `dependency`,
minify: {/* */}
}),
new InlineManifestWebpackPlugin(`runtime`)
]
}
複製程式碼
這段配置會產生一個叫做runtime
的程式碼塊,和老版本不同的是,我們並不需要在html模版中新增<%= htmlWebpackPlugin.files.webpackManifest %>
,只需將runtime加入chunks即可。這裡有一個點要注意,InlineManifestWebpackPlugin外掛的順序一定要在HtmlWebpackPlugin之後,否則會導致編譯失敗。
splitChunks
終於要講到重頭戲了,也是筆者個人認為最難以理解的一個配置項。webpack4移除了CommonsChunkPlugin外掛,取而代之的是splitChunks。
我們先來看下預設配置:
splitChunks: {
chunks: "async",
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: `~`,
name: true,
cacheGroups: {
vendors: {
test: /[\/]node_modules[\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
複製程式碼
預設配置只會作用於非同步載入的程式碼塊,它限制了分離檔案的最小體積,即30KB(注意這個體積是壓縮之前的),這個是前提條件,然後它有兩個分組:屬於node_modules模組,或者被至少2個入口檔案引用,它才會被打包成獨立的檔案。
為什麼要限制最小體積呢?因為webpack認為小於30KB的程式碼塊分離出來,還要額外消耗一次請求去載入它,成本太高,當然這個值也不是隨便意淫出來的,而是經過大量的實踐總結得到的,筆者個人認為這是一個非常好策略。
而maxAsyncRequests
(最大的非同步請求數)和maxInitialRequests
(最大的初始請求數)這兩個引數則是為了限制程式碼塊劃分的過於細緻,導致大量的檔案請求。
但是隻分離非同步程式碼塊顯然滿足不了我們的需求,因此接下來筆者分享一套相對來說比較優雅的分離打包配置:
splitChunks: {
cacheGroups: {
vendors: {
test: /[\/]node_modules[\/]/,
name: `vendors`,
minSize: 30000,
minChunks: 1,
chunks: `initial`,
priority: 1 // 該配置項是設定處理的優先順序,數值越大越優先處理
},
commons: {
test: /[\/]src[\/]common[\/]/,
name: `commons`,
minSize: 30000,
minChunks: 3,
chunks: `initial`,
priority: -1,
reuseExistingChunk: true // 這個配置允許我們使用已經存在的程式碼塊
}
}
}
複製程式碼
首先是將node_modules的模組分離出來,這點就不再累述了。非同步載入的模組將會繼承預設配置,這裡我們就不需要二次配置了。
第二點是分離出共享模組,筆者認為一個優雅的專案結構,其公共程式碼(或者稱為可複用的程式碼)應該是放置於同一個根目錄下的,基於這點我們可以將src/common
中的公用程式碼提取出來。
當然你還可以有另外一種選擇,將字尾為.js
且使用次數超過3次的檔案提取出來,但是筆者不建議這個做,因為這不利於持久化快取,新增或刪除檔案都有可能影響到使用次數,從而導致原先的公共檔案失效。
文末
原先還想著講一下css外掛部分的配置,限於篇幅,本文就不再進行講解說明了,感興趣的小哥哥小姐姐可以在這裡翻看原始碼:webpack4-test。
順便在這裡推薦一款好用的全域性通用腳手架fle-cli:旨在幫助我們從複雜繁瑣的編譯配置中解放出來,全身心地投入業務開發中,提高開發效率;同時它也是真正意義上的全域性腳手架,區別於市面上其他的全域性腳手架,它不會在專案工程中生成各種編譯配置檔案,也不會給你安裝一系列編譯的依賴包,這意味著你的專案工程可以非常乾淨純粹。