淺談webpack4.0 效能優化
原文連結:https://blog.csdn.net/yuanyang08/article/details/84324331
前言:在現實專案中,我們可能很少需要從頭開始去配置一個webpack 專案,特別是webpack4.0釋出以後,零配置啟動一個專案成為一種標配。正因為零配置的webpack對專案本身提供的“打包”和“壓縮”功能已經做了優化,所以實際應用中,我們可以把精力更多專注在業務層面上,而無需分心於專案構建上的優化。然而從學習者的角度,我們需要了解webpack在專案的構建和打包壓縮過程中做了哪些的優化,以及在原有預設配置上,還可以做哪些效能方面上的改進。
最近在完成vue的單頁面應用後萌生了一個想法,拋棄掉vue-cli的構建配置,從零開始進行webpack優化,並將過程中的思路和體會分享在這篇文章中。webpack的初始配置在我之前寫的另一篇手把手教你從零認識webpack4.0文章中,以下內容也不對基本的webpack配置做過多闡述。
一,優化的方向
1.1 專案開發
對開發者而言,我們希望webpack這個工具可以給我們帶來流暢的開發體驗。比如,當不斷修改程式碼時,我們希望程式碼的變更能及時的通知瀏覽器重新整理頁面,而不是手動去重新整理頁面。更進一步的我們希望,程式碼的修改只會區域性更換某個模組,而不是整個頁面的重新整理。這樣可以使我們不需要在等待重新整理中浪費很多時間,大大提高了頁面的開發效率。
1.2 專案部署
專案部署上線時,效能優化是我們考慮的重點,有兩個方向可以作為核心考慮的點,一個是減少HTTP請求,我們知道在網速相同的條件下,下載一個100KB的圖片比下載兩個50KB的圖片要快,因此,我們要求webpack將多個檔案打包成一個或者少量個檔案;另一個優化的重點是減少單次請求的時間,也就是儘可能減少請求檔案的體積大小。
webpack中在效能優化所做的努力,也大抵圍繞著這兩個大方向展開。另外在構建專案中,我們也希望能持續的提高構建效率。
二, 提升開發效率
2.1 減少體積
開發環境下,我們依然對程式碼的體積有一定的要求,更小的體積可以讓載入速度更快,開發效率更高,當然配置也相對簡單。
// webpack.dev.js 開發環境webpack配置
module.exports = {
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 9000,
compress: true, // 程式碼壓縮
},
}
2.2 模組熱更新(HMR)
開發過程中,我們希望修改程式碼的過程中,頁面能實時且不需要手動的重新整理。因此使用HRM, HMR 既避免了頻繁手動重新整理頁面,也減少了頁面重新整理時的等待,大幅度提高了開發效率。
// webpack.dev.js
module.exports = {
devServer: {
compress: true,
hot: true // 開啟配置
},
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
],
}
三,構建體積優化
3.1 生產中的sourcemap 模式
webpack 在構建中提供了不少於7種的sourcemap模式,其中eval模式雖然可以提高構建效率,但是構建後的指令碼較大,因此生產上並不適用。而source-map 模式可以通過生成的 .map 檔案來追蹤指令碼檔案的 具體位置,進而縮小指令碼檔案的體積,這是生產模式的首選,並且在生產中,我們需要隱藏具體的指令碼資訊,因此可以使用 cheap 和module 模式來達到目的。
綜上,在生產的webpack devtool選項中,我們使用 cheap-module-source-map
的配置
// webpack.pro.js 生產webpack配置指令碼
module.exports = {
mode: 'production',
devtool: 'cheap-module-source-map',
}
3.2 獨立css 檔案
以單入口檔案而論,通常我們會將頁面的所有靜態資源都打包成一個JS 檔案,這已經實現了1.2 中的優化部分,將程式碼合併成一個靜態資源,減少了HTTP 請求。
分離前
但是接下來,我們需要將css程式碼獨立開來,為什麼呢?最主要的一點是我們希望更好的利用瀏覽器的快取,當單獨修改了樣式時,獨立的css檔案可以不需要應用去載入整個的指令碼檔案,提高效率。並且,當遇到多頁面的應用時,可以單獨將一些公共部分的樣式抽離開來,載入一個頁面後,接下來的頁面同樣可以利用快取來減少請求。
webpack4.0 中提供了抽離css檔案的外掛,mini-css-extract-plugin
,只需要簡單的配置便可以將css檔案分離開來
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
···
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css",
chunkFilename: "[name].[contenthash].css"
})
],
module: {
rules: {
test: /\.(css|scss)$/,
use: [process.env.NODE_ENV == 'production' ? MiniCssExtractPlugin.loader : 'style-loader', {
loader: 'css-loader',
options: {
sourceMap: true
},
}, "sass-loader"]
}
}
···
}
分離後
3.3 壓縮js, html, css 檔案
要想優化構建後的體積,不斷減少靜態資原始檔的大小,我們希望webpack幫助我們儘可能壓縮檔案的體積。對於js 指令碼檔案而言,webpack4.0 在mode 為‘production’時,預設會啟動程式碼的壓縮。除此之外,我們需要手動對html和css 進行壓縮。
針對html 的壓縮,只需要對html-webpack-plugin進行相關配置。
// webpack.base.js
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: 'minHTML',
filename: 'index.html',
template: path.resolve(__dirname, '../index.html'),
minify: { // 壓縮 HTML 的配置
collapseWhitespace: true,
removeComments: true,
useShortDoctype: true
}
}),
]
}
針對css 的壓縮, webpack4.0 使用optimize-css-assets-webpack-plugin
來壓縮單獨的css 檔案。
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
plugins: [
new OptimizeCSSAssetsPlugin()
],
}
對比之下,我們可以看到明顯的效果,關於壓縮css 更多的配置可以參考optimize-css-assets-webpack-plugin
3.4. 合併壓縮圖片
處理完前端的三大塊js,html,css後, 接下來優化能想到的是處理圖片。前面提到,提升效能的一個重要的條件是降低http請求數,而應用中經常會有大大小小的圖片需要處理,對應用中的小圖示來說,css sprite 是首選,將各種圖示集合成一張大的圖片可以很好的減少網路請求數。而對於需要獨立開的圖片,且大小在合理範圍內時,我們可以將圖片轉換成 base64位編碼,內嵌到css 中,同樣可以減少請求。
3.4.1 base64 轉換
處理圖片資源時,webpack 提供了 file-loader 和url-loader 兩個loaders供選擇,file-loader 和url-loader 的作用,可以用來解析專案中圖片檔案的url引入問題。兩者的區別在於,url-loader 可以將小於指定位元組的檔案轉為DataURL, 大於指定位元組 的依舊會使用file-loader 進行解析
// webpack.base.js
module.exports = {
module: {
rules: [{
test: /\.(png|jpe?g|gif|svg|ttf|woff2|woff)(\?.*)?$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000, // 限制大小
}
},
]
},
}
3.4.2 壓縮圖片
處理完雪碧圖和小圖片的base64轉換後,對於大圖片來說,webpack還可以做到對圖片進行壓縮,推薦使用image-webpack-loader
,外掛提供了多種形式的壓縮,詳細可以參考官網文件
// webpack.base.js
module.exports = {
module: {
rules: [
{
loader: 'image-webpack-loader',
options: {
optipng: { // 使用 imagemin-optipng 壓縮 png,enable: false 為關閉
enabled: true,
},
pngquant: { // 使用 imagemin-pngquant 壓縮 png
quality: '65-90',
speed: 4
},
}
}
]
}
}
效果對比如下:
3.5 依賴庫分離
一箇中大型應用中,第三方的依賴,龐大得可怕,佔據了打包後文件的一半以上。然而,這些依賴模組又是很少變更的資源,和css 程式碼分離的邏輯相似,分離第三方依賴庫,可以更好的利用瀏覽器快取,提升應用效能。因此,將依賴模組從業務程式碼中分離是效能優化重要的一環。
webpack4.0 中,依賴庫的分離只需要通過 optimization.splitChunks 進行配置即可。
// webpack.pro.js
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: "initial",
test: path.resolve(__dirname, "../node_modules"),
name: "vendor", // 使用 vendor 入口作為公共部分
enforce: true,
},
},
},
},
}
公共庫分離後的結果
3.6 依賴分析
正如前面所講,在優化分析中,實際影響體積最大的是 node_modules 的第三方庫,這一部分的優化可以大大的減少打包後的體積。這裡我們使用最新的webpack-bundle-analyzer
外掛來分析打包好後的模組,它可以將打包後的內容束展示位方便互動的直觀樹狀圖,通過它,可以知道專案大致有哪些模組組成,哪個模組佔據的體積較大,是否是可替代的。
我們大致可以從幾個方向考慮
- 1.判斷依賴是否不可或缺,依賴在專案中使用率是否過低。在專案中,可能針對某個運算,某個功能,我們使用了一個龐大的庫,這個庫在體積上的佔比較大,而功能使用卻較少。這個時候我們可以尋找體積更小,且功能滿足的替換庫,或者手動實現某些依賴的功能來替換他。
- 2.大型庫是否可以通過定製功能的方式減少體積。明顯的一個例子是 echart, echart完全版的依賴壓縮後也有幾百k 之多,這顯示是難以接受的。現實專案中,我們可能只需要少量或者部分的echart 功能,這時我們可以通過制定圖表的形式,下載圖表用到的功能,達到體積最優化。
- 3.某些不可優化的大型庫是否可以通過外部引用的方式減少檔案體積。例如像bootstrap,vue這類無法優化的第三方庫,通過免費開源的cdn服務不但可以減少檔案體積,還可以提高網站的載入速度,也是個優化效能的方法
3.7 按需載入
前面提到依賴分析的方向中,如果大型庫不可或缺,而且使用率也不算低的時候,我們可以通過按需載入的形式。這種方式實際上是先把你的程式碼在一些邏輯斷點處分離開,然後在一些程式碼塊中完成某些操作後,立即引用或即將引用另外一些新的程式碼塊。這樣加快了應用的初始載入速度,減輕了它的總體體積,因為某些程式碼塊可能永遠不會被載入。
webpack中利用require.ensure()實現按需載入,在不使用按需載入的情況下,首屏載入時會把所有的指令碼同時加載出來,這往往會拖累首屏顯示時間,帶來不好的使用者體驗。例子來說。當專案需要使用大型的圖表類庫,而首頁並不需要時,按需載入往往比同時載入在使用者體驗上好好得多。
當不需要按需載入的時候,我們的程式碼可能是這樣的:
import test from './components/test.vue'
import test2 from './components/test2.vue'
開啟按需載入時,我們的程式碼修改為:
const test = r => require.ensure([], () => r(require('./components/test.vue')), 'chunk1')
const test2 = r => require.ensure([], () => r(require('./components/test2.vue')), 'chunk2')
webpack 配置修改為
output: {
···
chunkFilename: '[name].[hash].js'
}
這時編譯出來的檔案會從原來的一個,變成了多個小檔案。每個路由載入時會去載入不同的資源。特別在首屏的資源載入上進一步優化了應用的體驗。
儘管如此,實際中我們需要根據專案的需求來衡量按需載入的可用性,儘管在首屏優化上取得較大的提升,但按需載入畢竟會將大的檔案拆分成多個小檔案,增加了http 的請求數。這又違背了效能優化的基礎。所以實際中需要取捨,更需要權衡。
3.8 刪除冗餘程式碼
程式碼體積優化到這一步,基本可以優化的地方已經優化完畢了。接下來可以抓住一些細節做更細的優化。比如可以刪除專案中上下文都未被引用的程式碼。這就是所謂的 Tree shaking 優化。webpack 4.0中,mode 為production 預設啟動這一優化。但是,如果在專案中使用到babel的 話,需要把babel解析語法的功能關掉。只需要
// .babelrc
{
"presets": [["env", { "modules": false }]]
}
四,構建速度優化
說完如何減少專案構建後的大小後,接下來簡單的談一下如何提高構建的速度。實際上webpack的 構建速度,只需要簡單的修改配置便能大幅提高速度。常見的設定如下。
4.1 babel-loader構建時間過長
4.1.1 限定載入器作用範圍
由於babel-loader需要將語法進行轉換,所耗費的時間較長,所以第一步需要限定babel-loader 作用的範圍,讓babel-loader 的搜尋和轉換準確的定位到指定模組。大幅提高構建速度。
例如:
// webpack.base.js
module.exports = {
module:{
rules: [
{
test: /\.js$/,
include: [resolve('src')],// 限定範圍
use: {
loader: 'babel-loader',
},
},]
}
}
4.1.2 快取載入器執行結果
正因為babel-loader在解析轉換上耗時太長,所以我們希望能快取每次執行的結果。webpack的loader中剛好有 cacheDirectory 的選項,預設為false 開啟後將使用快取的執行結果,打包速度明顯提升。
// webpack.base.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
include: [resolve('src')],
use: {
loader: 'babel-loader?cacheDirectory',
},
},]
}
}
4.2 resolve 解析優化
webpack 的resolve 做相關的配置後,也可以讓專案的構建速度加快。具體看下文的配置:
- 當專案中出現 import 'react' 既不是絕對路徑也不是相對路徑時,指定好搜尋的路徑,可以不用過多的查詢
- 儘可能少的使用 resolve.alias 來設定路徑別名,因為會影響到tree shaking 的優化
- 字尾自動補全儘可能的少。減少路徑查詢的工作
- 當使用的第三方庫過大,並且不包含import require define 的呼叫。可以使用noParse讓庫不被loaders 解析
// webpack.base.js
module.exports = {
resolve: {
modules: [
path.resolve(__dirname, 'node_modules'),
],
extensions: [".js"],
// 避免新增預設檔案,編碼時使用詳細的檔案路徑,程式碼會更容易解讀,也有益於提高構建速度
mainFiles: ['index'],
},
module: {
noParse: function(content){
return /jquery/.test(content)
}
}
}
五,結語
webpack效能優化的瓶頸還是集中在構建時間和構建體積上,作為構建工具業界霸主,webpack一直不停的優化構建打包流程。通過對舊有專案的優化也可以對webpack這個工具以及效能優化的知識有更深的瞭解。
轉載請註明出處