現狀
隨着項目不斷發展壯大,組件數量開始變得越來越多,項目也開始變得龐大,webpack
編譯的時間也會越來越久,我們現在的項目編譯一次在 40s ——70s
之間,這是一個效率非常低下的操作。優化的手段有很多,之前項目原本已經做了很多,本文從緩存的角度進行優化講解
以下僅介紹幾種緩存相關的優化手段,包括
babel-loader
的cacheDirectory
cache-loader
dll
動態鏈接庫HardSourceWebpackPlugin
先說結論,第一個是項目中已有的,第二第三個效果不大,第四個達到了預期的效果
我們的 webpack 版本:4.41.2,系統:mac os
瓶頸分析
優化的第一步,應該是分析目前的性能,這里我們使用 speed-measure-webpack-plugin
進行速度分析
// 安裝
npm install --save-dev speed-measure-webpack-plugin
// 使用方式
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const webpackConfig = smp.wrap({
plugins: [
new MyPlugin(),
new MyOtherPlugin()
]
});
結果類似如下,可以看到每一個 Loader
以及 Plugin
的耗時,有了這個,我們就可以“對症下葯”
但需要注意的是:HardSourceWebpackPlugin 和 speed-measure-webpack-plugin 不能一起使用,這一點讓我郁悶了很久
babel-loader 的 cacheDirectory
babel-loader
允許使用 Babel
和 webpack
轉譯 JavaScript
文件,有時候如果我們運行 babel-loader
很慢的話,可以考慮確保轉譯盡可能少的文件。你可能使用 /\.m?js$/
來匹配,這樣有可能去轉譯 node_modules
目錄或者其他不需要的源代碼,導致性能下降
可以通過 exclude
排除掉一些不需要編譯的文件。比如下面就不會去轉義 node_modules
和 bower_components
文件夾下面的內容
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-proposal-object-rest-spread']
}
}
}
]
}
你也可以通過使用 cacheDirectory
選項,將 babel-loader
提速至少兩倍。這會將轉譯的結果緩存到文件系統中。cacheDirectory
默認值為 false
。當有設置時,指定的目錄將用來緩存 loader
的執行結果。之后的 webpack
構建,將會嘗試讀取緩存,來避免在每次執行時,可能產生的、高性能消耗的 Babel
重新編譯過程(recompilation process
)。如果設置了一個空值 (loader: 'babel-loader?cacheDirectory')
或者 true (loader: 'babel-loader?cacheDirectory=true')
,loader 將使用默認的緩存目錄node_modules/.cache/babel-loader
,如果在任何根目錄下都沒有找到 node_modules
目錄,將會降級回退到操作系統默認的臨時文件目錄。
{
test: /\.js$/,
use: 'babel-loader?cacheDirectory',
include: [resolve('src'), resolve('test') ,resolve('node_modules/webpack-dev-server/client')]
}
cache-loader
除了 babel-loader
,如果我們想讓其他的 loader
的處理結果也緩存,該怎么做呢?
答案是可以使用 cache-loader
。在一些性能開銷較大的 loader
之前添加 cache-loader
,以便將結果緩存到磁盤里
安裝
npm install --save-dev cache-loader
配置
module.exports = {
module: {
rules: [
{
test: /\.ext$/,
use: ['cache-loader', ...loaders],
include: path.resolve('src'),
},
],
},
};
⚠️ 請注意,保存和讀取這些緩存文件會有一些時間開銷,所以請只對性能開銷較大的 loader 使用此 loader
除了默認的配置,cache-loader
提供了其他一些選項,詳見 cache-loader[1]
dll 的緩存方案
什么是 DLL?
DLL 文件為動態鏈接庫,在一個動態鏈接庫中可以包含給其他模塊調用的函數和數據
為什么要用 DLL?
原因在於包含大量復用模塊的動態鏈接庫只需要編譯一次,在之后的構建過程中被動態鏈接庫包含的模塊將不會在重新編譯,而是直接使用動態鏈接庫中的代碼。由於動態鏈接庫中大多數包含的是常用的第三方模塊,例如 Vue react、react-dom
,只要不升級這些模塊的版本,動態鏈接庫就不用重新編譯
如何使用?
要完成下面三步:
- 抽離。把網頁依賴的基礎模塊抽離出來,打包到一個個單獨的動態鏈接庫中去。一個動態鏈接庫中可以包含多個模塊
- 獲取。當需要導入的模塊存在於某個動態鏈接庫中時,這個模塊不能被再次被打包,而是去動態鏈接庫中獲取
- 加載。頁面依賴的所有動態鏈接庫需要被加載
之前使用 DllPlugin
和 DllReferencePlugin
完成,但是其配置非常復雜,而且假如更新了文件,還需要手動重新生成 dll。這里選擇了 AutoDllPlugin[2],它會自動完成以上兩個插件的功能,這是 Vue-cli
曾經用過的一個插件
安裝:
webpack 4
npm install --save-dev autodll-webpack-plugin
webpack 2 / 3
npm install --save-dev autodll-webpack-plugin@0.3
基礎使用:
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: './src/index.html',
}),
new AutoDllPlugin({
inject: true, // will inject the DLL bundles to index.html
filename: '[name].js',
entry: {
vendor: [
'react',
'react-dom'
]
}
})
]
優化前
優化后
第一次編譯:
第二次編譯:
優化了幾s,成效不大
之所以成效不大,是因為 webpack4
的性能是足夠優秀的了,Vue-cli
也廢除了這個功能
HardSourceWebpackPlugin
安裝:
npm install --save-dev hard-source-webpack-plugin
# or
yarn add --dev hard-source-webpack-plugin
配置:
// webpack.config.js
var HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
context: // ...
entry: // ...
output: // ...
plugins: [
new HardSourceWebpackPlugin()
]
}
優化前
可以看到,需要 50s
優化后
第一次啟動
第二次啟動
只需要 7 s,減少了 43 s
,速度提升百分之八十左右。優化的目的達成!
熱更新速度
看到 issue
中提到了關於熱更新相關的,說是會慢一點,我利用我們的項目做了一些測試,下面是測試數據
優化前
js: 2443ms 1634ms 1844ms 2532ms 1443ms 1248ms
html: 1094ms 1232ms 1119ms 1490ms 1264ms
css: 1422ms 1186ms 1341ms 1562ms 1183ms
優化后
js: 2429ms 2436ms 2860ms 2528ms 1917ms 1487ms 1450ms 1450ms 1557ms 2198ms
html: 2855ms 1569ms 1400ms 1298ms 1204ms 1299ms 1578ms 1485ms 2028ms
css: 2035ms 1406ms 1415ms 1600ms 1773ms 1604ms
相比而言,有時候會慢了一些,但總體而言能夠接受。但也有了一些影響,所以項目中提高了兩個 npm script
命令,如果不希望開啟的話,可以直接 npm run dev:noCache
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --cache=true",
"dev:noCache": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --cache=false"
}
在 build/webpack.dev.conf.js
中
if (args.cache) {
devConfig = merge(devConfig, {
plugins: [new HardSourceWebpackPlugin()]
})
}
再次強調:
HardSourceWebpackPlugin 和 speed-measure-webpack-plugin 不能一起使用
展望未來
webpack 5
已經發布,其中有一個很吸引人的功能——持久緩存(據說思想跟 HardSourceWebpackPlugin
是一致的)
通過 cache
緩存生成的 webpack
模塊和chunk
,來改善構建速度。cache
會在開發模式被設置成 type: 'memory'
而且在生產模式中被禁用
module.exports = {
cache: {
// 1. 將緩存類型設置為文件系統
type: 'filesystem',
buildDependencies: {
// 2. 將你的 config 添加為 buildDependency,以便在改變 config 時獲得緩存無效
config: [__filename],
// 3. 如果你有其他的東西被構建依賴,你可以在這里添加它們
// 注意,webpack、加載器和所有從你的配置中引用的模塊都會被自動添加
},
},
};
總結
以上的探索,花費了筆者挺多的時間的,菜是原罪,還是要多積累。另外一個感慨就是前端的發展如此迅速,很多東西可能已經過時,唯有保持持續學習以及穩固基礎知識才是王道