最近給項(xiàng)目進(jìn)行webpack優(yōu)化,嘗試過幾乎所有方法,一共26條,列舉在此。
優(yōu)化webpack,首先明確優(yōu)化目標(biāo):
- 構(gòu)建速度: 開發(fā)環(huán)境項(xiàng)目的啟動(dòng)速度,以及生產(chǎn)環(huán)境項(xiàng)目的打包速度。構(gòu)建時(shí)間越短,速度越快越好。
- 增量構(gòu)建速度:開發(fā)項(xiàng)目時(shí),每修改一次代碼,webpack會(huì)對修改的部分進(jìn)重新構(gòu)建。增量構(gòu)建時(shí)間越短,速度越快越好。
- 打包體積:開發(fā)環(huán)境webpack打包后,生成的文件體積。打包體積越小越好
- 加載體積: 除關(guān)注打包體積外,瀏覽器打開頁面,加載資源的體積也很重要。按需加載、緩存等技術(shù)可以減少加載體積。加載體積越小越好。
減少依賴包,縮小打包體積,構(gòu)建速度會(huì)更快。使用壓縮,打包體積會(huì)減小,但構(gòu)建速度則變慢。討論webpack的優(yōu)化,速度和體積需同時(shí)考慮,二者有時(shí)正相關(guān),有時(shí)負(fù)相關(guān),需要均衡速度和體積。
下文中每個(gè)優(yōu)化建議,都會(huì)列出優(yōu)化目標(biāo),影響明顯的會(huì)加粗。除速度和體積,還有: 分析, 即幫助分析webpack打包性能,呈現(xiàn)打包時(shí)間等信息;錯(cuò)誤追蹤,webpack打包后的文件與源文件不同,需要特殊的處理以方便錯(cuò)誤定位和調(diào)試。
webpack 在生產(chǎn)環(huán)境更關(guān)注打包體積,而開發(fā)環(huán)境更關(guān)注打包時(shí)間,生產(chǎn)環(huán)境和開發(fā)環(huán)境使用不同的策略,下文每一個(gè)優(yōu)化建議都列出使用環(huán)境。
webpack及相關(guān)打包工具在不斷跟新優(yōu)化中,不同版本可能會(huì)有很大不同,請以官方文檔為準(zhǔn)。本文以webpack4.42為準(zhǔn),與webpack3和webpack5區(qū)別較大的地方會(huì)標(biāo)注版本變動(dòng)。
1. 量化打包速度
環(huán)境:開發(fā),生產(chǎn)
目標(biāo):分析,構(gòu)建速度,增量構(gòu)建速度
對webpack進(jìn)行優(yōu)化,首先要測量webpack打包耗時(shí),發(fā)現(xiàn)問題所在。
- 使用
speed-measure-webpack-plugin,快速測量各插件和loader的耗時(shí)。

speed-measure-webpack-plugin使用非常簡單,直接包裹webpack配置即可
// npm i -D speed-measure-webpack-plugin
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin")
const smp = new SpeedMeasurePlugin()
const webpackConfig = smp.wrap({
// ... webpack config
})
speed-measure-webpack-plugin對webpack 4的兼容性不好,一些插件可能會(huì)發(fā)生錯(cuò)誤,需暫停發(fā)生錯(cuò)誤的插件。
-
使用
profilingPlugin, 創(chuàng)建構(gòu)建性能報(bào)告。profilingPlugin可以生成一份JSON文件,上傳到chrome devtool的performence面板, 可以看到詳細(xì)的編譯過程,尋找性能瓶頸:const webpack = require('webpack') module.exports = { //... plugins: [ new webpack.debug.ProfilingPlugin( { // 產(chǎn)生出的文件位置,相對于根目錄 outputPath: 'stats/profileEvents.json' }) ] }上傳到chrome,出現(xiàn)漂亮的時(shí)間線,可供分析:
perfomrance.png
webpack多進(jìn)程運(yùn)行時(shí),插件和loader的運(yùn)行時(shí)間較難觀察,比如各插件的運(yùn)行時(shí)間相加會(huì)大于總時(shí)間,loader的先后順序可能不對。使用單進(jìn)程可以保證有效的測試數(shù)據(jù)。測速時(shí)請將 parallelism 配置項(xiàng)設(shè)為 false (默認(rèn)為 true ),確保webpack單線程運(yùn)行:
module.exports = {
//...
parallelism: false
}
除調(diào)試webpack外,其他情況請開啟多進(jìn)程。
2. 量化打包體積
環(huán)境:開發(fā),生產(chǎn)
目標(biāo):分析,打包體積,加載體積
可以通過 webpack-bundle-analyzer 查看打包后的體積。探究某個(gè)依賴是否過大,是否存在重復(fù)的依賴,是否有功能類似的依賴,是否進(jìn)行有效的代碼分離。
// npm i -D webpack-bundle-analyzer
const BundleAnalyzerPlugin =
require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
//...
plugins: [
new BundleAnalyzerPlugin({
defaultSizes: 'gzip' // 設(shè)置默認(rèn)尺寸為'gzip'
})
)
]
}

webpack-bundle-analyzer 顯示的尺寸有三種:
-
stat: 未經(jīng)Uglify、Terser 等插件最小化的體積。最小化js就是我們常說的壓縮js,最小化(minify)是指去除注釋、簡化變量名等操作壓縮體積,而壓縮是通過gzip等算法壓縮體積。 -
parsed: 經(jīng)Uglify、Terser 等插件最小化后的體積。 -
gzip: 經(jīng)過gzip壓縮后的尺寸。
線上一般按照 gzip 進(jìn)行數(shù)據(jù)傳輸,所以選擇 gzip 作為衡量體積的標(biāo)準(zhǔn)。
3. 查看打包詳情
環(huán)境:開發(fā),生產(chǎn)
目標(biāo):分析,構(gòu)建速度,增量構(gòu)建速度,打包體積,加載體積
除查看打包體積和時(shí)間,有時(shí)需要更多打包信息,比如模塊的依賴關(guān)系,可以用如下方式:
-
配置
profile選項(xiàng),生成詳細(xì)打包報(bào)告。配置profile選項(xiàng)后,每次打包會(huì)生成一個(gè)json文件,里面有打包的詳細(xì)信息,也可通過stats-webpack-plugin進(jìn)行更多設(shè)置:// npm i -D stats-webpack-plugin const StatsPlugin = require('stats-webpack-plugin') module.exports = { //... profile: true // 簡單的配置,將在打包文件輸出報(bào)告 pulgins: [ // 更靈活的配置 new StatsPlugin( '../../stats/stats.json', { exclude: [/node_modules/], }) ] }將產(chǎn)生的json文件上傳至https://webpack.github.io/analyse/ 官方分析工具,查看可讀的報(bào)告:

-
打包后,在terminal終端中顯示打包詳情。可以通過
stats選項(xiàng)配置terminal中的輸出結(jié)果,如:module.exports = { //... stats: 'verbose' // 將在terminal中呈現(xiàn)全部輸出 };每次打包后,在終端會(huì)顯示:

`stats` 的可選址值有:
* `none`: 沒有輸出
* `errors-only` :只顯示錯(cuò)誤信息,**使用此選項(xiàng),可提高webpack編譯速度**
* `minimal`: 顯示錯(cuò)誤信息,或者新的輸出
* `normal` : 標(biāo)準(zhǔn)輸出
* `verbose` : 全部輸出,建議用 `profile` 選項(xiàng)輸出成文檔。
可以個(gè)性化配置輸出信息。使用Node.js API 時(shí)此配置無效,使用 `webpack-dev-server` 時(shí),請將此選項(xiàng)需放入 `devServer` 中。。
Jarvis、webpack-dashboard、webpack visualizer等非官方工具也可提供打包信息,幫你分析webpack編譯。
4. 使用最新版本
環(huán)境:開發(fā),生產(chǎn)
目標(biāo): 構(gòu)建速度,增量構(gòu)建速度,打包體積,加載體積
工欲善其事,必先利其器。使用最新的版本,保證最快的速度。請檢查node、 webpack、插件、 loader等的版本號(hào),使用最新版本。推薦一款工具 npm-check, 只要在項(xiàng)目根目錄運(yùn)行:
npm-check -u
即查看哪些包有更新:

5. 刪除不必要的插件、loader等工具
環(huán)境:開發(fā),生產(chǎn)
目標(biāo):構(gòu)建速度,增量構(gòu)建速度
任何插件和loader的啟用都占用時(shí)間,使用最少的插件和loader可以減少webpack的打包時(shí)間。
- 一些工具,只對生產(chǎn)階段有效,比如壓縮、CSS分離、hash,請?jiān)陂_發(fā)環(huán)境刪除。
- 一些工具,比如
progress-bar-webpack-plugin進(jìn)度提示條,請衡量對你是否真的有用,每增加一個(gè)插件都會(huì)讓打包更慢。 - 對于
speed-measure-webpack-plugin,webpack-bundle-analyzer這類量化插件,只在調(diào)試webpack時(shí)使用,在其他時(shí)候請刪除。 - 在webpack打包時(shí),會(huì)在terminal中輸出moudle、chunk等打包信息,生成這些信息會(huì)消耗一定的時(shí)間。建議設(shè)置為
stats: 'errors-only'或者stats: 'minimal'。
6. 設(shè)置正確的模式mode
環(huán)境:開發(fā),生產(chǎn)
目標(biāo): 構(gòu)建速度,增量構(gòu)建速度,構(gòu)建體積,加載體積
版本變動(dòng):webpack3
webpack提供開箱即用的配置環(huán)境,通過 mode 屬性告知webpack你的使用環(huán)境,webpack將自動(dòng)開啟一系列的優(yōu)化。
- 生產(chǎn)環(huán)境,請?jiān)O(shè)置
mode:'production' - 開發(fā)環(huán)境,請?jiān)O(shè)置
mode: 'development'
module.exports = {
//...
mode: 'production' // 生產(chǎn)環(huán)境,也是默認(rèn)值
mode: 'development' // 開發(fā)環(huán)境
}
設(shè)置 mode 后,webpack自動(dòng)將 process.env.NODE_ENV 設(shè)為相應(yīng)的值(生產(chǎn)為 production , 開發(fā)為 develpment ),react等包根據(jù)此值進(jìn)行優(yōu)化,所以不要將process.env.NODE_ENV 改為其他值。
webpack3沒有 mode 選項(xiàng),需要用 DefinePlugin手動(dòng)設(shè)置process.env.NODE_ENV,明確生產(chǎn)或者開發(fā)環(huán)境 。
webpack自動(dòng)啟用的優(yōu)化,如無需配置,本文不再介紹。
7. 啟用多進(jìn)程,并行編譯
環(huán)境:開發(fā),生產(chǎn)
目標(biāo): 構(gòu)建速度,增量構(gòu)建速度
webpack默認(rèn)是單進(jìn)程編譯,不利于發(fā)揮多核CPU的威力??梢圆扇⌒┐胧?,使用多進(jìn)程,這將大幅提高打包速度:
對于
TerserWebpackPlugin等插件,本身支持多進(jìn)程,默認(rèn)開啟,也可通過parallel選項(xiàng)進(jìn)行個(gè)性化配置。-
對于其他loader,可以使用
happyPack或者thread-loader進(jìn)行并行編譯。這兩個(gè)功能類似,對打包速度的提升也類似。對于babel-loader尤其需要并行編譯,提高打包速度。happyPack使用的人較多,使用限制較少, 但其配置較復(fù)雜,例如:// npm i -D happypack const HappyPack = require('happypack') module.exports = { // ... module: { loaders: [{ test: /\\.js$/, loader: 'babel-loader', happy: { id: 'js' } }] }, plugins: [ new HappyPack({ id: 'js' }) ] }thread-loader有些使用限制,但配置非常簡單:// npm i -D threa-loader module.exports = { // ... module: { rules: [ { test: /\\.jsx?$/, use: ['thread-loader', 'babel-loader'] } ] } }thread-loader將其后的loader 放在單獨(dú)的池中運(yùn)行,要求loader不能產(chǎn)生新的文件、不能使用插件、無法獲取webpack選項(xiàng)。
開啟多進(jìn)程會(huì)消耗大量時(shí)間,各進(jìn)程之間的通信也非常耗時(shí)。請僅對長耗時(shí)的loader啟用。進(jìn)程數(shù)過多會(huì)導(dǎo)致打包變慢,進(jìn)程的默認(rèn)數(shù)通常是CPU數(shù)-1,即 os.cpus().length - 1 。
8. 使用持久緩存
環(huán)境:開發(fā),生產(chǎn)
目標(biāo):構(gòu)建速度,增量構(gòu)建速度
版本變動(dòng):webpack5
webpack構(gòu)建一次耗費(fèi)很長時(shí)間,如果將構(gòu)建的結(jié)果進(jìn)行緩存,第一次構(gòu)建的時(shí)間可能略有增加,但之后的構(gòu)建時(shí)間將大幅度縮短,這種技術(shù)叫持久緩存。
-
建議使用
hardSourcePlugin進(jìn)行全局緩存,開箱即用,無需配置,使用效果非常好,能減少50%以上的構(gòu)建時(shí)間。hardSourcePlugin在使用中可能存在一些問題,可能導(dǎo)致CSS分離失效,如有問題請查閱官方文檔。使用hardSourcePlugin后,不需再使用cache-loader或者DLL plugin等緩存工具。hardSourcePlugin配置示例:// npm i -D hard-source-webpack-plugin const HardSourceWebpackPlugin = require('hard-source-webpack-plugin') module.exports = { // ... plugins: [ new HardSourceWebpackPlugin () ] } 對于
TerserWebpackPlugin,請確保緩存開啟,即cache不為false。hardSourcePlugin對TaresePlugin似乎沒有效果,需單獨(dú)處理。對于webpack5,請直接配置
cache: { type: "filesystem” },不再需要hardSourcePlugin等緩存工具,TarserPlugin自帶的緩存特性也會(huì)失效,無需開啟。
另外一種緩存技術(shù)叫做”長期緩存" (long-term caching), 是指瀏覽器緩存一些資源,可以更快的打開頁面,需與“持久緩存”相區(qū)別,后文有更多介紹。
9. 選擇合適的devTool
環(huán)境:開發(fā),生產(chǎn)
目標(biāo):構(gòu)建速度、增量構(gòu)建速度
webpack打包后的代碼是經(jīng)過壓縮處理,缺少可讀性。devTool可以控制代碼的輸出品質(zhì),添加 source-map ,增加代碼可讀性,方便錯(cuò)誤定位。注意代碼輸出品質(zhì)的提高,會(huì)使打包速度變慢,需均衡選擇。
module.exports = {
//...
devTool: 'source-map' // 產(chǎn)生源碼品質(zhì)的source-map,有暴露源碼風(fēng)險(xiǎn),不建議使用
}
生產(chǎn)環(huán)境常用的值:
-
none:不使用devtool,速度最快,無法定位錯(cuò)誤,是生成環(huán)境默認(rèn)值。 -
hidden-source-map:產(chǎn)生source-map, 需要手動(dòng)加載到瀏覽器devtool,或使用其他工具加載,才可查看源碼和追蹤錯(cuò)誤,推薦使用。 -
nosources-source-map:無法看到源碼,但能在瀏覽器devtoo看到錯(cuò)誤棧,錯(cuò)誤發(fā)生的準(zhǔn)確位置、行號(hào)等信息。這是線上環(huán)境最簡單的方案,但暴露源碼結(jié)構(gòu)。
開發(fā)環(huán)境常用的值:
-
eval: 源碼被放入eval函數(shù)中,缺少可讀性,只知道錯(cuò)誤的大概位置,沒有錯(cuò)誤行號(hào)、列號(hào)提示,構(gòu)建速度最快,這是開發(fā)環(huán)境默認(rèn)值。 -
eval-cheap-source-map: 看到近似源碼的代碼,有錯(cuò)誤行號(hào)提示,沒列號(hào)提示。較快的構(gòu)建速度和增量構(gòu)建速度。 -
eval-cheap-module-source-map: 能看到源碼,有行號(hào)錯(cuò)誤提示,沒列號(hào)提示。較慢的構(gòu)建速度和較快的增量構(gòu)建速度,推薦此選項(xiàng)。 -
eval-source-map: 能看到源碼,并有行號(hào)和列號(hào)的錯(cuò)誤提示。很慢的構(gòu)建速度和一般的增量構(gòu)建速度。
devTool 還有其他配置值,需均衡考慮構(gòu)建速度和錯(cuò)誤定位,并防止源碼在生產(chǎn)環(huán)境中暴露。你可以用 SourceMapDevToolPlugin 代替 devTool 進(jìn)行個(gè)性化配置。
如未能生成 source-map,檢查一些插件或者loader是否配置 sourceMap: true ,這包括postcss-loader、resolve-url-loader、TeserWebpackPlugin、OptimizeCSSAssetsPlugin 等。
10. 合理配置loader的解析范圍
環(huán)境:開發(fā),生產(chǎn)
目標(biāo):構(gòu)建速度,增量構(gòu)建速度
一些loader,比如 babel-loader 轉(zhuǎn)換JS的過程是非常耗時(shí)的,如果能減少loader處理的文件數(shù)量,會(huì)大大減少編譯時(shí)間。
可以通過 test 正則匹配文件, include 指定要包含的文件, exclude 指定要排除的文件。優(yōu)先級(jí)為: exclude > include > test ,例如:
module.exports = {
//...
module: {
rules: [
{
test: /\\.styl$/,
include: [
path.resolve(__dirname, 'app/styles'),
path.resolve(__dirname, 'vendor/styles')
],
// 在app/styles 中排除掉 app/styles/common
exclude: [path.resolve(__dirname, 'app/styles/commen')],
use: ['style-loader', 'css-loader', 'stylus-loader']
}
]
}
}
順便提下loader的執(zhí)行順序,列表中較后的選項(xiàng)先執(zhí)行,比如在上例中,先執(zhí)行 stylus-loader
解析.style,再執(zhí)行 css-loader解析CSS,最后執(zhí)行 style-loader 將CSS內(nèi)聯(lián)處理。
11. 忽略jquery等模塊的解析
環(huán)境:開發(fā),生產(chǎn)
目標(biāo):構(gòu)建速度、增量構(gòu)建速度
jquery, lodash等是獨(dú)立的、不需要外部依賴即可運(yùn)行的模塊??梢允褂?module.noParse 避免對這些庫進(jìn)行解析,以提高構(gòu)建性能。配置方法如下:
module.exports = {
//...
module: {
noParse: /jquery|lodash/,
}
};
module.noParse 配置后,將忽略相關(guān)模塊中 import 、 reuqire 、 define 的調(diào)用,請確保配置的模塊沒有其他依賴。
module.noParse 只是不對jquery等模塊進(jìn)行解析(如通過babel-loader轉(zhuǎn)換js文件),打包后的文件中仍包含jquery的代碼。 module.noParse 的優(yōu)先級(jí)高于上文中對loader的include ,test配置。
12. 使用 externals 引入公共CDN
環(huán)境:開發(fā),生產(chǎn)
目標(biāo):構(gòu)建速度、增量構(gòu)建速度
可以利用公共CDN服務(wù)加載包,而不用webpack打包。如下配置,將 jquery 變量掛載在 windows 變量上:
module.exports = {
//...
externals: {
'jquery': 'jQuery'
}
};
當(dāng)然,要在HTML代碼中引入jquery:
<script src="<http://libs.baidu.com/jquery/2.0.0/jquery.min.js>"></script>
webpack打包后的文件中將不包含jquery的代碼。
如果你的多個(gè)應(yīng)用使用相同的依賴,可以將公共依賴分離,再用此種方式單獨(dú)部署使用。
13. 合理配置 reslove 字段,縮小webpack尋找模塊的范圍
環(huán)境: 開發(fā)、生產(chǎn)
目標(biāo):構(gòu)建速度、增量構(gòu)建速度
resolve 配置webpack如何查找模塊,例如 import 'vue' ,resolve 告訴webpack在哪里找到vue。更精準(zhǔn)的配置將減小webpack搜索模塊的時(shí)間。
module.exports = {
//...
resolve: {
modules: ['node_modules'], // 僅在node_modules目錄尋找模塊
extensions: ['.js', '.json'], // import 'utils' 類似于 import 'utils.js'
mainFields: ['loader', 'main'], // 通過package.json 的loader、main 選項(xiàng)找到入口文件名
mainFiles: ['index'], // 設(shè)index為入口文件名
symlinks: false, // 忽略npm link
alias: {
// import 'Pages' 將等同于 import 'src/pages'
Pages: 'src/pages',
// webpack直接確定react模塊位置,不需要層層查找
react: patch.resolve(__dirname, './node_modules/react/dist/react.min.js')
}
}
};
-
resolve.modules:告訴webpack搜索模塊的范圍。 -
resolve.mainFields和resolve.mainFiles:告訴webpack模塊的入口文件。 -
resolve.extensions:如果引入文件時(shí)不指定文件類型后綴,將通過此字段查找相應(yīng)的文件類型。 -
resolve.symlinks:如果不使用NPM link,可將此設(shè)為false。 -
resolve.alias:設(shè)置別名,這將提高代碼的可讀性,并加快模塊的尋找速度。
如果選項(xiàng)是列表的,請盡量減少列表項(xiàng),并將高頻值寫在前面, 如 extensions: ['.js', '.json'] 中 js 在前。
未掌握 reslove 各選項(xiàng)含義前,請使用默認(rèn)值, reslove 對webpack編譯的提速不明顯,但錯(cuò)誤的配置可能導(dǎo)致一些bug,比如明明存在某個(gè)模塊但webpack無法找到該模塊。
14. 使用ES module,保證tree-shaking刪除無用代碼
環(huán)境:開發(fā),生產(chǎn)
目標(biāo):構(gòu)建體積
版本變動(dòng):webpack3
webpack4支持tree-shaking,可以刪除未使用的代碼,請使用ES module的導(dǎo)入方式,確保tree-shaking的有效,例如:
// common.js
const run = () => {console.log('run')}
const fly = () => {console.log('fly')}
export {run, fly}
// index.js
import {run, fly} from './common'
run()
如此,fly將會(huì)從代碼中刪除。按下述導(dǎo)入方式,tree-shaking不會(huì)生效:
// common.js 同上
// index.js
import common from './common'
common.run()
// common 依賴將被整個(gè)打包,無法實(shí)現(xiàn)tree-shaking
由于JS動(dòng)態(tài)編譯的特性,tree-shaking的效果有限。對于很多依賴,webpack無法很好的執(zhí)行tree-shaking。另外webpack3不支持tree-shaking。
在開發(fā)其他項(xiàng)目的依賴包時(shí),可考慮在 package.js 中添加 sideEffects:false,其他項(xiàng)目在導(dǎo)入你的包時(shí),會(huì)嘗試進(jìn)行tree-shaking。
15. 使用TerserWebpackPlugin 最小化JS
環(huán)境:生產(chǎn)
目標(biāo):構(gòu)建速度,打包體積
TerserWebpackPlugin 可以最小化JS,即壓縮js,并且支持最小化ES6,webpack4生產(chǎn)模式下自動(dòng)啟用,你也可以進(jìn)行一些配置:
- 請確保
parallel多進(jìn)程開啟,提高打包速度。 - 像loader一樣,通過
test,include,exclude縮小壓縮文件范圍。也可以用chunkFilter排除一些chunk。 - 非webpack5版本,請確保
cache緩存開啟,webpack5中此選項(xiàng)失效。 - 如果需要source-map,請配置
sourceMap為true,這將生成source-map。注意cheap-source-map等包含cheap的source-map 不會(huì)產(chǎn)生source-map。請使用hidden-source-map或者nosources-source-map。
配置示例如下:
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
exclude: /\\/excludes/,
chunkFilter: (chunk) => {
return chunk.name !== 'vendor' // 排除名為 `vendor` 的chunk
},
parallel: true, // 多進(jìn)程,默認(rèn)開啟
cache: true, // 持久緩存,默認(rèn)開啟
sourceMap: true, // 支持產(chǎn)生source-map,默認(rèn)不開啟
terserOptions: {
//... 設(shè)置terser的壓縮選項(xiàng),比如是否清除注釋,詳見官方文檔
}
}),
],
},
};
不要使用 UglifyjsWebpackPlugin,此插件不支持ES6,并已經(jīng)停止維護(hù)。
16. 使用 optimize-css-assets-webpack-plugin 最小化CSS
環(huán)境:生產(chǎn)
目標(biāo):打包體積
使用 optimize-css-assets-webpack-plugin 最小化CSS,減少CSS代碼的體積。webpack4 中不包含此插件,需先安裝。
// npm i -D optimize-css-assets-webpack-plugin
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
optimization: {
minimizer: [
new OptimizeCSSAssetsPlugin({})
],
},
};
17. 優(yōu)化圖片
環(huán)境:生產(chǎn)
目標(biāo): 打包體積
圖片是網(wǎng)站重要的一部分,不僅體積大,而且數(shù)量多,占用大量帶寬,下面是優(yōu)化圖片的一些方法:
- 使用
url-loader將小尺寸圖片進(jìn)行內(nèi)聯(lián)??梢耘渲?limit選項(xiàng),將小于此值的圖片轉(zhuǎn)換成base64 格式并存在JS代碼中,以節(jié)少請求數(shù)量。file-loader有和url-loader類似的功能,可以解析靜態(tài)文件,但不能設(shè)置limit屬性進(jìn)行內(nèi)聯(lián)。 - 使用
svg-loader將svg圖片進(jìn)行內(nèi)聯(lián)。此插件和url-loader類似,但SVG是文本,壓縮體積上更有效率。 - 雪碧圖可將多張小圖片組裝合成一張大圖片,減少請求數(shù)量。由于雪碧圖配置較復(fù)雜,不推薦使用??梢岳?
webpack-spritesmith插件實(shí)現(xiàn)雪碧圖。 - 使用
image-webpack-loader壓縮圖片。它支持JPG,PNG,GIF和SVG等幾乎所有格式。 它不會(huì)將圖片內(nèi)聯(lián),需與url-loader等配合使用,即先壓縮圖片,再進(jìn)行內(nèi)聯(lián)??梢酝ㄟ^enforce: 'pre'確保在其他loader前執(zhí)行。
配置示例如下:
// npm i -D image-webpack-loader
// npm i -D url-loader
// npm i -D svg-url-loader
module.exports = {
module: {
rules: [
// 壓縮圖片
{
test: /\\.(jpe?g|png|gif|svg)$/,
loader: 'image-webpack-loader',
enforce: 'pre', // 在其他loader調(diào)用前,先行調(diào)用,避免重復(fù)代碼
},
// 內(nèi)聯(lián)圖片
{
test: /\\.(jpe?g|png|gif)$/,
loader: 'url-loader',
options: {
limit: 10 * 1024, // 小于10kb的會(huì)被內(nèi)聯(lián)
},
}
// 內(nèi)聯(lián)svg
{
test: /\\.svg$/,
loader: 'svg-url-loader',
options: {
limit: 10 * 1024, // 小于10kb的會(huì)被內(nèi)聯(lián)
noquotes: true, // 刪除引號(hào)
iesafe: true, // 支持IE,但會(huì)增加體積
},
},
],
},
};
18. 優(yōu)化第三方依賴包
環(huán)境: 開發(fā),生產(chǎn)
目標(biāo): 構(gòu)建速度、增量構(gòu)建速度、打包體積
幾乎一半以上的JS體積來源于第三方依賴包,如果能優(yōu)化依賴包體積,會(huì)大幅減少構(gòu)建體積。我們通常只用到包的幾個(gè)方法,但卻引入包的全部內(nèi)容。比如,我們只在中文環(huán)境使用Moment.js,卻引入了Moment.js的各國語言包,使得項(xiàng)目臃腫。
Googel 在Github repo上收集了一些優(yōu)化建議,其中包括babel-loard、loadsh、react、bootstrap等的優(yōu)化建議。請?jiān)趃ithub上搜索“webpack-libs-optimizations”查看。
19. 對html-webpack-plugin的優(yōu)化
環(huán)境:生產(chǎn)
目標(biāo):打包體積
hteml-webpack-plguin 可以很方便的創(chuàng)建入口html文件,但它對自定義模板默認(rèn)不開啟壓縮的,請配置 minify 選項(xiàng),進(jìn)行最小化代碼
// npm i -D html-webpack-plugin
// npm i -D uglifyjs-webpack-plugin 壓縮js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
// 使用'ejs'后綴防止模板文件被html—loader 解析
template: path.join(__dirname, 'index.ejs'),
filename: 'index.html',
minify: { // 開啟最小化操作
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true,
minifyCSS: true,
minifyJS: true, // 使用uglifyjs進(jìn)行壓縮,所以需要安裝相關(guān)依賴
}
})
]
}
20. 優(yōu)化Node核心庫的polyfill
環(huán)境:生產(chǎn)
目標(biāo):打包體積,加載體積
版本變動(dòng):webpack5
webpack打包時(shí)會(huì)自動(dòng)加入Node環(huán)境的polyfill,所以在瀏覽器中也可使用Node核心庫,比如 process.env.node_env ,這會(huì)略微增加打包的體積。如果不需要瀏覽器使用Node的核心庫,請配置:
module.exports = {
// ...
node: false
}
你也可以對每一個(gè)核心庫進(jìn)行精確配置,以下是webpack4的默認(rèn)配置值(webpack5有差別):
module.exports = {
// ...
node: {
console: false,
global: true,
process: true,
__filename: "mock",
__dirname: "mock",
Buffer: true,
setImmediate: true
}
}
其中的 console ,global ,process 等為node核心庫中的相應(yīng)功能,更多選項(xiàng)需查找 node 核心庫。配置的值的含義如下:
-
true:提供polyfill。 -
mock:提供 mock 實(shí)現(xiàn)預(yù)期接口,但功能很少或沒有。 -
empty:提供一個(gè)空對象。 -
false:不提供polyfill,如果使用相應(yīng)的node庫,會(huì)觸發(fā)錯(cuò)誤。
21. 分離代碼
環(huán)境:生產(chǎn)
目標(biāo):打包體積,加載體積
版本變動(dòng):webpack3,webpack5
webpack將各種資源打包成一個(gè)JS文件,合并請求,優(yōu)化用戶體驗(yàn)?,F(xiàn)在的web應(yīng)用體積越來越大,單一的js文件體積過大,需拆分成多個(gè)js文件,減少加載體積。這也是懶加載、長久緩存等技術(shù)使用的基礎(chǔ)。分離出的每一個(gè)js文件,稱之為chunk。
webpack3使用 CommonsCunksPlugn 配置復(fù)雜,webpack4提供開箱即用的 optimization.splitChunks 。生產(chǎn)環(huán)境自動(dòng)啟用 optimization.splitChunks ,默認(rèn)配置足以應(yīng)對大多數(shù)情況,不需要專門配置。默認(rèn)配置遵照的主要規(guī)則:
- 主入口會(huì)打包成單獨(dú)的chunk。
- 動(dòng)態(tài)導(dǎo)入的每一個(gè)模塊,會(huì)打包成一個(gè)chunk。例如按路由分離3個(gè)頁面,將會(huì)打包出3個(gè)chunk。
- 每個(gè)chunk中用到的node_modules依賴,會(huì)被打單獨(dú)打包成以
vendors~開頭的chunk - 如果兩個(gè)chunk中包含相同的內(nèi)容,相同的內(nèi)容會(huì)被單獨(dú)打包成一個(gè)共享chunk。
比如有個(gè)項(xiàng)目:
- index 入口頁,用到react,index組件
- about 介紹頁,用到react,about組件,common組件
- detail 詳情頁,用到angular,detail組件,common組件
將打包成:
- index.js: react, index組件
- about~detail.js: common組件
- about.js: about組件
- detail.js: detail組件
- vendors~detail.js: angular
如修改默認(rèn)規(guī)則,請?jiān)敿?xì)閱讀官方文檔,并注意評(píng)估打包結(jié)果,防止負(fù)優(yōu)化,例如:
module.exports = {
//...
optimization: {
splitChunks: {
//... 一些配置
}
}
}
webpack5對optimization.splitChunks 進(jìn)行一些優(yōu)化,配置略有不同。
22. 分離CSS
環(huán)境:生產(chǎn)
目標(biāo):打包體積,加載體積
版本變動(dòng):webpack3
在分離JS的基礎(chǔ)上,有時(shí)需分離CSS,以實(shí)現(xiàn)CSS的按需加載。在webpack3 時(shí)使用 ExtractTextWebpackPlugin , 在webpack4 時(shí)請使用 mini-css-extract-plugin :
// npm i -D mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: '[hash].css',
chunkFilename: '[contenthash].css' // 注意使用contenthash
})
],
module: {
rules: [
{
test: /\\.css$/i,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
esModule: true,
hmr: process.env.NODE_ENV === 'development' // 熱響應(yīng)
},
},
'css-loader',
],
},
],
},
};
CC將分離成一個(gè)個(gè)chunk。在開發(fā)階段可以考慮不分離CSS,以提高打包速度。
23. 使用按需加載,懶加載,預(yù)加載
環(huán)境:生產(chǎn)
目標(biāo):加載體積
版本變動(dòng):webpack5
現(xiàn)在網(wǎng)頁應(yīng)用功能越來越多,尺寸越來越大,我們可以只加載用戶用到的部分,而不加載其他部分以提高性能。比如只加載首屏需要的chunk,而其他部分的chunk暫時(shí)不加載。
我們可以使用上文所說的 split-chunk 分離代碼,并通過動(dòng)態(tài)導(dǎo)入實(shí)現(xiàn)按需加載/懶加載。Vue,React等都提供動(dòng)態(tài)導(dǎo)入組件的方案,也可以用 function import() 實(shí)現(xiàn)懶加載,比如:
import(/* webpackChunkName: "lodash" */ 'lodash')
webpack5使用更加智能的chunk命名方式,不需要諸如 /* webpackChunkName: "lodash" */ 的代碼。
對于分離的代碼,我們也可在空閑時(shí)間,將其預(yù)先加載,以提高用戶體驗(yàn),比如:
import(/* webpackPrefetch: true */ 'lodash')
24. 使用長久緩存
環(huán)境:生產(chǎn)
目標(biāo):加載體積
版本變動(dòng):webpack3,webpack5
瀏覽器打開應(yīng)用時(shí)會(huì)下載資源,這會(huì)占用大量的時(shí)間。如果將一部分資源在瀏覽器中緩存,下次打開頁面時(shí)不需要重下載,可以降低首屏渲染時(shí)間。
我們在修改項(xiàng)目時(shí),通常只修改一部份,讓瀏覽器僅更新修改部分即可,其余資源使用緩存。webpack打包后,會(huì)給每一個(gè)靜態(tài)資源添加hash后綴,如果修改該資源,hash會(huì)發(fā)生改變,瀏覽器會(huì)重新加載該資源。如果該資源沒被修改,hash會(huì)保持不變,瀏覽器會(huì)使用緩存,這種技術(shù)叫做“長久緩存”(long cache)。需與前文提到的"持續(xù)緩存”相區(qū)別,持續(xù)緩存是用于提高打包速度的。
webpack5對長久緩存進(jìn)行大量優(yōu)化,配置更為方便。下面的步驟限于webpack4版本,webpack3也需要類似復(fù)雜的配置,但略有不同。
長久緩存依靠 splitChunk 代碼分離,請參考上文。
-
第一步,告知瀏覽器緩存文件
需要在服務(wù)器上設(shè)置緩存文件時(shí)長,比如通過
Cache-Control// Server header Cache-Control: max-age=31536000 // 設(shè)置緩存一年時(shí)間有多種設(shè)置緩存的方法,請查看相關(guān)文章
-
第二步,使用
recordsPath配置項(xiàng),方便調(diào)試在設(shè)置長久緩存時(shí),我們需要知道webpack打包后的文件是如何劃分的,模塊id是否改變,
recordsPath配置項(xiàng)可以產(chǎn)生一份json文件,記錄每次打包后的結(jié)果。const path = require('path') module.exports = { // ... recordsPath: path.join( process.cwd(), // npm run build 在terminal中執(zhí)行時(shí)的目錄路徑 'records.json' // 輸出的文件名 ) } -
第三步,為文件添加hash
需要為打包后的文件設(shè)置hash后綴,相當(dāng)于給該文件添加一個(gè)版本號(hào),告知瀏覽器該文件是否更新,比如設(shè)置
[name].[hash].js。hash值有三種:-
[hash]: webpack每次編譯都會(huì)產(chǎn)生一個(gè)唯一的hash值,每次編譯hash都會(huì)改變 -
[chunkhash]: webpack每次編譯,對每一個(gè)chunk(每一個(gè)js文件)生成不同的hash值,如果修改chunk,會(huì)重新計(jì)算chunkhash。如果沒修改chunk,chunkhash不會(huì)變。對JS資源我們通常添加[chunkhash]后綴。 -
[contenthash]:webpack根據(jù)輸出的文件內(nèi)容,計(jì)算并生成contenthash,每次編譯后都會(huì)根據(jù)內(nèi)容生成contenthash,如果改文件內(nèi)容不變,生成的contenthash是相同的。通常對圖片、字體、樣式等資源添加[contenthash]后綴。
module.exports = { // ... output: { chunkFilename: '[chunkhash].js' // 為chunk添加chunkhash }, module: { rules: [ { test: /\\.ttf/, loader: 'file-loaser', options: { name: '[contenthash].ext'} //為字體添加contenthash } ] } }由于不同的系統(tǒng)、計(jì)算機(jī)使用的hash計(jì)算方式不同,在不同的計(jì)算機(jī)對相同的項(xiàng)目打包,產(chǎn)生的hash值可能不同,導(dǎo)致長久緩存失效。
-
-
第四步,分離runtime
runtime記錄所有的資源文件名和路徑,以幫助瀏覽器找到最新版本的chunk,比如:
{ "0":"js/0.840dc3db.js", "common":"js/common.50055e90.js", "detail":"js/detail.dd333b62.js" //... }通常每個(gè)chunk中都包含runtime信息,只要有一個(gè)chunk發(fā)生變化,runtime就會(huì)變化,進(jìn)而使所有chunk發(fā)生變化??梢酝ㄟ^
optimization.runtimeChunk將runtime分離為一個(gè)單獨(dú)的chunk,我們將將這個(gè)chunk稱為runtime.js,有時(shí)也稱為manifest.js(比如vue-cli中)。module.exports = { // ... optimization: { runtimeChunk: { name: 'runtime' // 分離runtime chunk,并命名為 'runtime' }, } } -
第五步,內(nèi)聯(lián)runtime文件
分離的runtime文件,將作為一個(gè)獨(dú)立的js加載,瀏覽器的請求順序變?yōu)椋?/p>
index.html → runtime.js → 首屏chunk。
可以看出多了次請求,使用
inline-manifest-webpack-plugin將runtime內(nèi)聯(lián)進(jìn)html文件,瀏覽器的請求順序修改為:index.html (包含runtime的代碼) → 首屏chunk
也可以使用
webpack-manifest-plugin實(shí)現(xiàn)同樣功能,inline-manifest-webpack-plugin的配置示例:// npm i -D inline-manifest-webpack-plugin const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin') module.exports = { // ... plugins: [ new InlineManifestWebpackPlugin(), ], }; -
第六步,穩(wěn)定moudle id
webpack會(huì)給每一個(gè)moudle分配一個(gè)id,id是按照順序計(jì)算的,如 0,1,2。如果你刪除了某個(gè)moudle,或者新增某個(gè)moudle,可能導(dǎo)致module id的順序發(fā)生變化,比如:
moudleId 變動(dòng)情況 0 原來的0 1 新增的module 2 原來的1, id變化 3 原來的2,id變化moudle id的變化會(huì)導(dǎo)致chunk文件發(fā)生變化,我們可以用
hashed-module-ids-plugin或者用optimization.moduleIds來穩(wěn)定moudle id:const webpack = require('webpack') module.exports = { // ... plugins: [ new webpack.HashedModuleIdsPlugin(), ] }
經(jīng)過復(fù)雜的配置,終于可以實(shí)現(xiàn)長久緩存。
25. 使用性能預(yù)算,控制體積
環(huán)境:生產(chǎn)
目標(biāo):打包體積
項(xiàng)目初期的體積較小,但隨項(xiàng)目發(fā)展,可能沒注意到,安裝的某個(gè)依賴,使項(xiàng)目打包體積急劇增加。使用性能預(yù)算,及時(shí)發(fā)現(xiàn)打包體積的增加。
配置 performance 即可啟用性能預(yù)算,生成環(huán)境自動(dòng)開啟,默認(rèn)配置如下:
module.exports = {
// ...
performance: {
hints: 'warning', // 尺寸過大后警告,
// 設(shè)為'errors'時(shí)體積過大將報(bào)錯(cuò)
// 設(shè)為false時(shí)關(guān)閉性能預(yù)算
maxEntrypointSize: 250000, // 最大入口體積
maxAssetSize:250000 // 單個(gè)資源最大體積
}
}
打包后,如果有體積過大,將顯示:

使用 bundlesize 工具,可以進(jìn)行更靈活的配置,比如單獨(dú)設(shè)置某個(gè)文件的體積預(yù)算,同時(shí)支持自動(dòng)化CI。
26. 其他優(yōu)化建議
-
并行運(yùn)行多個(gè)webpack實(shí)例 。打包多個(gè)版本,比如打包國內(nèi)版本應(yīng)用、國際版本應(yīng)用,可以使用
parallel-webpack并行運(yùn)行多個(gè)webpack實(shí)例。 -
指定browserslist。指定合適的browserslist,babel將據(jù)此選擇核合適的降級(jí)工具,有助于代碼體積的減少和打包速度的提升。比如開發(fā)環(huán)境需要更快的打包速度,可以設(shè)為
'last 2 Chrome versions';生產(chǎn)環(huán)境需考慮兼容性,可設(shè)為'> 1%, ie >= 11, not dead'。 - 考慮為不同的瀏覽器打包不同版本,可以為IE、現(xiàn)代瀏覽器分別打包,再根據(jù)瀏覽器的標(biāo)識(shí)加載不同的版本。也可以根據(jù)瀏覽器的尺寸將圖片打包成不同的尺寸,對PC端瀏覽器返回大圖,而對移動(dòng)端瀏覽器返回較小尺寸的圖片。
-
如果擔(dān)心打包后的文件包含多個(gè)相同模塊,可以用
duplicate-package-checker-webpack-plugin或者bundle-duplicates-plugin進(jìn)行檢查。
