Webpack 效能優化
減少 Webpack 打包時間
優化 Loader
對於 Loader 來說,影響打包效率首當其衝必屬 Babel 了。因為 Babel 會將程式碼轉為字串生成 AST,然後對 AST 繼續進行轉變最後再生成新的程式碼,專案越大,轉換程式碼越多,效率就越低。當然了,我們是有辦法優化的。
首先我們可以優化 Loader 的檔案搜尋範圍
module.exports = {
module: {
rules: [
{
// js 檔案才使用 babel
test: /\.js$/,
loader: 'babel-loader',
// 只在 src 資料夾下查詢
include: [resolve('src')],
// 不會去查詢的路徑
exclude: /node_modules/
}
]
}
}
對於 Babel 來說,我們肯定是希望只作用在 JS 程式碼上的,然後node_modules
中使用的程式碼都是編譯過的,所以我們也完全沒有必要再去處理一遍。
當然這樣做還不夠,我們還可以將 Babel 編譯過的檔案快取起來,下次只需要編譯更改過的程式碼檔案即可,這樣可以大幅度加快打包時間
loader: 'babel-loader?cacheDirectory=true'
HappyPack
受限於 Node 是單執行緒執行的,所以 Webpack 在打包的過程中也是單執行緒的,特別是在執行 Loader 的時候,長時間編譯的任務很多,這樣就會導致等待的情況。
HappyPack 可以將 Loader 的同步執行轉換為並行的,這樣就能充分利用系統資源來加快打包效率了
module: {
loaders: [
{
test: /\.js$/,
include: [resolve('src')],
exclude: /node_modules/,
// id 後面的內容對應下面
loader: 'happypack/loader?id=happybabel'
}
]
},
plugins: [
new HappyPack({
id: 'happybabel',
loaders: ['babel-loader?cacheDirectory'],
// 開啟 4 個執行緒
threads: 4
})
]
DllPlugin
DllPlugin 可以將特定的類庫提前打包然後引入。這種方式可以極大的減少打包類庫的次數,只有當類庫更新版本才有需要重新打包,並且也實現了將公共程式碼抽離成單獨檔案的優化方案。
接下來我們就來學習如何使用 DllPlugin
// 單獨配置在一個檔案中
// webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
// 想統一打包的類庫
vendor: ['react']
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].dll.js',
library: '[name]-[hash]'
},
plugins: [
new webpack.DllPlugin({
// name 必須和 output.library 一致
name: '[name]-[hash]',
// 該屬性需要與 DllReferencePlugin 中一致
context: __dirname,
path: path.join(__dirname, 'dist', '[name]-manifest.json')
})
]
}
然後我們需要執行這個配置檔案生成依賴檔案,接下來我們需要使用DllReferencePlugin
將依賴檔案引入專案中
// webpack.conf.js
module.exports = {
// ...省略其他配置
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
// manifest 就是之前打包出來的 json 檔案
manifest: require('./dist/vendor-manifest.json'),
})
]
}
程式碼壓縮
在 Webpack3 中,我們一般使用UglifyJS
來壓縮程式碼,但是這個是單執行緒執行的,為了加快效率,我們可以使用webpack-parallel-uglify-plugin
來並行執行UglifyJS
,從而提高效率。
在 Webpack4 中,我們就不需要以上這些操作了,只需要將mode
設定為production
就可以預設開啟以上功能。程式碼壓縮也是我們必做的效能優化方案,當然我們不止可以壓縮 JS 程式碼,還可以壓縮 HTML、CSS 程式碼,並且在壓縮 JS 程式碼的過程中,我們還可以通過配置實現比如刪除console.log
這類程式碼的功能。
一些小的優化點
我們還可以通過一些小的優化點來加快打包速度
resolve.extensions
:用來表明檔案字尾列表,預設查詢順序是['.js', '.json']
,如果你的匯入檔案沒有新增字尾就會按照這個順序查詢檔案。我們應該儘可能減少字尾列表長度,然後將出現頻率高的字尾排在前面resolve.alias
:可以通過別名的方式來對映一個路徑,能讓 Webpack 更快找到路徑module.noParse
:如果你確定一個檔案下沒有其他依賴,就可以使用該屬性讓 Webpack 不掃描該檔案,這種方式對於大型的類庫很有幫助
減少 Webpack 打包後的檔案體積
注意:該內容也屬於效能優化領域。
按需載入
想必大家在開發 SPA 專案的時候,專案中都會存在十幾甚至更多的路由頁面。如果我們將這些頁面全部打包進一個 JS 檔案的話,雖然將多個請求合併了,但是同樣也載入了很多並不需要的程式碼,耗費了更長的時間。那麼為了首頁能更快地呈現給使用者,我們肯定是希望首頁能載入的檔案體積越小越好,這時候我們就可以使用按需載入,將每個路由頁面單獨打包為一個檔案。當然不僅僅路由可以按需載入,對於loadash
這種大型類庫同樣可以使用這個功能。
按需載入的程式碼實現這裡就不詳細展開了,因為鑑於用的框架不同,實現起來都是不一樣的。當然了,雖然他們的用法可能不同,但是底層的機制都是一樣的。都是當使用的時候再去下載對應檔案,返回一個Promise
,當Promise
成功以後去執行回撥。
Scope Hoisting
Scope Hoisting 會分析出模組之間的依賴關係,儘可能的把打包出來的模組合併到一個函式中去。
比如我們希望打包兩個檔案
// test.js
export const a = 1
// index.js
import { a } from './test.js'
對於這種情況,我們打包出來的程式碼會類似這樣
[
/* 0 */
function (module, exports, require) {
//...
},
/* 1 */
function (module, exports, require) {
//...
}
]
但是如果我們使用 Scope Hoisting 的話,程式碼就會盡可能的合併到一個函式中去,也就變成了這樣的類似程式碼
[
/* 0 */
function (module, exports, require) {
//...
}
]
這樣的打包方式生成的程式碼明顯比之前的少多了。如果在 Webpack4 中你希望開啟這個功能,只需要啟用optimization.concatenateModules
就可以了。
module.exports = {
optimization: {
concatenateModules: true
}
}
Tree Shaking
Tree Shaking 可以實現刪除專案中未被引用的程式碼,比如
// test.js
export const a = 1
export const b = 2
// index.js
import { a } from './test.js'
對於以上情況,test
檔案中的變數b
如果沒有在專案中使用到的話,就不會被打包到檔案中。
如果你使用 Webpack 4 的話,開啟生產環境就會自動啟動這個優化功能