為什么要優化?
如果你的項目很小,構建很快,其實不用特別在意性能方面的問題。但是隨着項目涉及到的頁面越來越多,功能和業務代碼也會越來越復雜,相應的 webpack 的構建時間也會越來越久,打包后的體積也會越來越大,這個時候我們就不得不考慮性能優化的事情了。

分析工具
在動手優化之前,我們需要有一個量化的指標,得知影響構建時間的問題究竟出在哪里,是某個 chunk 的體積太大,還是某個loader或者 plugin的耗時太久了等等。
我們可以通過一些工具對項目的 體積 和 速度 進行分析,讓后對症下葯。
體積分析
- 初級分析
可以通過官方提供的 stat.json 幫助我們分析打包結果, stat.json 可以通過下面的語句快速生成
npx webpack --profile --json > stats.json
接下來我們就可以通過官方提供的 stat.json分析工具 進行分析,目前已無法打開
- 使用第三方工具
webpack-bundle-analyzer 是一個打包分析神器,它的界面也清晰,而且能很直觀的給出每一個打包出來的文件的大小以及各自的依賴,能夠更加方便的幫助我們對項目進行分析。
使用如下:
// npm install --save-dev webpack-bundle-analyzer
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
// ...
plugins: [
new BundleAnalyzerPlugin({
analyzerPort: 8889, // 指定端口號
openAnalyzer: false,
})
],
// ...
}

webpack-bundle-analyzer其底層也是依賴stat.json文件的,通過對stat.json的分析,得出最后的分析頁面
通過分析工具的分析,我們可以知道哪些文件打包出來的體積比較大,從而對有問題的文件進行優化。
速度分析
我們可以通過 speed-measure-webpack-plugin 這個插件幫助我們分析整個打包的總耗時,以及每一個loader 和每一個 plugins 構建所耗費的時間,從而幫助我們快速定位到可以優化 Webpack 的配置。
// 安裝 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()
]
});

如上圖所示,耗時比較長的都會以紅色標出
注意:
speed-measure-webpack-plugin對於webpack的升級還不夠完善,暫時還無法與你自己編寫的掛載在html-webpack-plugin提供的hooks上的自定義Plugin(add-asset-html-webpack-plugin就是此類)共存,有人已經在 github 上提了 issue 了,但是貌似還是沒有解決。
優化策略
經過相應的體積分析和速度分析之后,我們便可以着手進行優化了。
使用新版本
這個是 webpack 性能優化的萬能膏葯,升級版本必定能帶來性能提升,而且提升很明顯。


從上圖中我們可以看到,
webpack4.0的構建速度遠遠快於webpack3.0,官方也說升級版本之后,構建時間可以降低60% - 98%左右。
在每一個版本的更新,webpack 內部肯定會做很多優化,而 webpack 是依賴 Node 的 js 運行環境,升級他們對應的版本,webpack 的速度肯定也能夠獲得提升。
如果要遷移到 Webpack 4 也只需要檢查一下 checklist,看看這些點是否都覆蓋到了,就可以了。
webpack 4.0 帶來的優化
v8引擎帶來的優化(for of替代forEach、Map和Set替代Object、includes替代indexOf)- 默認使用更快的
md4 hash算法 webpack AST可以直接從loader傳遞給AST,減少解析時間- 使用字符串方法替代正則表達式
我們可以在 github 上的 webpack 庫的 releases 版本迭代 頁面中查看其帶來的性能優化:

一個V8性能的例子
我們可以來看一個例子,比較使用 includes 替代 indexOf 之后帶來的速度提升,創建 compare-includes-indexof.js 文件,在這個文件中建一個 10000000 長度的數組,記錄兩個函數分別消耗的時間:
const ARR_SIZE = 10000000;
const hugeArr = new Array(ARR_SIZE).fill(1);
// includes
const includesTest = () => {
const arrCopy = [];
console.time('includes')
let i = 0;
while (i < hugeArr.length) {
arrCopy.includes(i++);
}
console.timeEnd('includes');
}
// indexOf
const indexOfTest = () => {
const arrCopy = [];
console.time('indexOf');
for (let item of hugeArr) {
arrCopy.indexOf(item);
}
console.timeEnd('indexOf');
}
includesTest();
indexOfTest();

所以在項目上盡可能使用比較新的 webpack、Node、Npm、Yarn 版本,是我們提升打包速度的第一步。
體積優化
webpack 是個項目打包工具,一般項目打完包以后,需要發布到服務器上供用戶使用,為了用戶體驗,我們的項目體積需要越小越好,所以 webpack 中打包的體積是 webpack 中重要的一環。
js壓縮
webpack4.0 默認在生產環境的時候是支持代碼壓縮的,即 mode=production 模式下
實際上 webpack4.0 默認是使用 terser-webpack-plugin 這個壓縮插件,在此之前是使用 uglifyjs-webpack-plugin,兩者的區別是后者對 ES6 的壓縮不是很好,同時我們可以開啟 parallel 參數,使用多進程壓縮,加快壓縮。
// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: 4, // 開啟幾個進程來處理壓縮,默認是 os.cpus().length - 1
}),
],
},
// ...
}
css壓縮
我們可以借助 optimize-css-assets-webpack-plugin 插件來壓縮 css,其默認使用的壓縮引擎是 cssnano。 具體使用如下:
// npm install --save-dev optimize-css-assets-webpack-plugin
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
// ...
const prodConfig = {
// ...
optimization: {
minimizer: [
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.optimize\.css$/g, // 一個正則表達式,指示應優化\最小化的資產的名稱。提供的正則表達式針對配置中
cssProcessor: require('cssnano'), // 用於優化/最小化CSS的CSS處理器,默認為cssnano。
cssProcessorPluginOptions: { // 傳遞給cssProcessor的插件選項,默認為 {}
preset: ['default', { discardComments: { removeAll: true } }],
},
canPrint: true, // 一個布爾值,指示插件是否可以將消息打印到控制台,默認為 true
})
]
},
}
擦除無用的css
使用 PurgeCSS 來完成對無用 css 的擦除,它需要和 mini-css-extract-plugin 配合使用。
// npm install --save-dev mini-css-extract-plugin purgecss-webpack-plugin
const path = require('path');
const glob = require('glob');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PurgecssPlugin = require('purgecss-webpack-plugin');
// ...
const PATHS = {
src: path.join(__dirname, 'src')
}
const commonConfig = {
// ...
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
}),
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
]
// ...
}
在未使用此插件之前,比如我只用到了bootsrap 里面的 btn btn-primary 這個類,其他的都沒用到,但是在打包之后發現未用到的css也會被打包進去

引入插件后,重新打包,發現沒有用到的css都被擦除了

更多的使用方法可以參考: PurgeCSS 文檔
圖片壓縮
一般來說在打包之后,一些圖片文件的大小是遠遠要比 js 或者 css 文件大很多,所以我們首先要做的就是對於圖片的優化,我們可以手動的去通過線上的圖片壓縮工具,如 tiny png 幫我們來壓縮圖片。
但是這個比較繁瑣,在項目中我們希望能夠更加自動化一點,自動幫我們做好圖片壓縮,這個時候我們就可以借助 image-webpack-loader 幫助我們來實現。它是基於 imagemin 這個 Node 庫來實現圖片壓縮的。
使用很簡單,我們只要在 file-loader 之后加入 image-webpack-loader 即可:
// npm install --save-dev file-loader image-webpack-loader
// ...
module: {
rules: [
{
test: /\.(png|jpeg|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
}
},
{
loader: 'image-webpack-loader',
options: {
// 壓縮 jpeg 的配置
mozjpeg: {
progressive: true,
quality: 65
},
// 使用 imagemin**-optipng 壓縮 png,enable: false 為關閉
optipng: {
enabled: false,
},
// 使用 imagemin-pngquant 壓縮 png
pngquant: {
quality: '65-90',
speed: 4
},
// 壓縮 gif 的配置
gifsicle: {
interlaced: false,
},
// 開啟 webp,會把 jpg 和 png 圖片壓縮為 webp 格式
webp: {
quality: 75
}
}
}
]
},
]
}
// ...
我們先不用這個loader打包,圖片大小是100K

使用 image-webpack-loader 之后,圖片大小是 55KB

gzip壓縮
我們可以使用 compression-webpack-plugin 插件對靜態文件做一個gzip壓縮,能極大的減小文件體積,是節省帶寬和加快站點速度的有效方法,但是這種方式需要服務端支持,比如Nginx需要開啟gzip 才能正常使用,Nginx的gzip設置
// npm install --save-dev compression-webpack-plugin@5.x 注意webpack4使用5.x版本
const CompressionWebpackPlugin = require('compression-webpack-plugin');
module.exports = {
// ...
plugins: [
new CompressionWebpackPlugin({
test: new RegExp('\\.(js|css)$'), // 需要壓縮的文件,正則匹配
threshold: 1024, // 只處理比這個值大的資源。按字節計算
deleteOriginalAssets: false // 是否刪除原資源
})
]
}
打包之后的結果:

本地預先生成.gz文件,這樣服務器端就不用自己去生成.gz了,從而降低第一次服務器生成.gz的壓力。關於gzip的更多配置可以參考 官方文檔
Tree-shaking
有時候我們寫的某些模塊根本沒有使用,但是還是被打包了,這樣實際上會拖累 webpack 的打包速度,而且也會增加打包文件的體積,所以我們可以使用 tree-shaking 將這些代碼剔除掉。這個是webpack自帶的功能,默認支持,注意必須是 ES6 的語法,CJS 的方式不支持。
代碼拆分
從 webpack v4 開始,移除了 CommonsChunkPlugin,取而代之的是 optimization.splitChunks。它可以把一個大的文件分割成幾個小的文件,這樣也可以有效的提升 webpack 的打包速度。
webpack 將根據以下條件自動拆分 chunks:
- 新的 chunk 可以被共享,或者模塊來自於
node_modules文件夾 - 新的 chunk 體積大於 20kb(在進行 min+gz 之前的體積)
- 當按需加載 chunks 時,並行請求的最大數量小於或等於 30
- 當加載初始化頁面時,並發請求的最大數量小於或等於 30
optimization.splitChunks 官方給了默認配置:
splitChunks: {
chunks: "async", // "initial" | "all"(推薦) | "async" (默認就是async) | 函數
minSize: 20000, // 生成 chunk 的最小體積(以 bytes 為單位)
minChunks: 1, // 最小 chunk ,默認1
maxAsyncRequests: 30, // 最大異步請求數, 默認30
maxInitialRequests: 30, // 最大初始化請求書,默認30
automaticNameDelimiter: '~', // 打包分隔符, 例如 vendors~main.js
name: true, // 打包后的名稱,此選項可接收 function
cacheGroups: { // 這里開始設置緩存的 chunks ,緩存組
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true, // 可設置是否重用該chunk
}
}
}
可以先通過 webpack-bundle-analyzer 分析出哪些文件占用比較大,再使用splitChunks進行合理的拆分。
下面看是一個之前老項目的例子,在沒有使用此插件拆分之前:

我們對項目進行一些配置,拆分占用較大的文件
optimization: {
splitChunks: {
maxInitialRequests: 4,
automaticNameDelimiter: '_',
cacheGroups: {
swiper: {
name: 'chunk-swiper',
test: /[\\/]node_modules[\\/]swiper[\\/]/,
chunks: 'all',
priority: 5,
reuseExistingChunk: true,
enforce: true
},
vant: {
name: 'chunk-vant',
test: /[\\/]node_modules[\\/]vant[\\/]/,
chunks: 'all',
priority: 5,
reuseExistingChunk: true,
enforce: true
},
videojs: {
name: 'chunk-videojs',
test: /[\\/]node_modules[\\/](video\.js)|(videojs-contrib-hls)[\\/]/,
chunks: 'all',
priority: 5,
reuseExistingChunk: true,
enforce: true
},
utils: {
name: 'chunk-utils',
test: /[\\/]node_modules[\\/](crypto-js)|(md5\.js)|(core-js)|(axios)[\\/]/,
chunks: 'all',
priority: 5,
reuseExistingChunk: true,
enforce: true
}
}
}
}
再次打包之后:

更多的配置項,可以參考 split-chunks-plugin
html-webpack-externals-plugin
html-webpack-externals-plugin插件可以將一些公用包提取出來使用 cdn 引入,不打入 bundle 中
下面我們先來寫一個vue的例子:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const Foo = { template: '<div>foo</div>' };
const Bar = { template: '<div>bar</div>' };
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
];
const router = new VueRouter({
routes
});
const app = new Vue({
router
}).$mount('#app');
我們打包看到main.bundle.js大小是 97.7K

接下來我們配置一下 html-webpack-externals-plugin
// npm i --save-dev html-webpack-externals-plugin html-webpack-plugin@4.x
// 此插件需要依賴html-webpack-plugin,注意webpack4安裝4.x版本
const htmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
module.exports = {
//...
plugins: [
new htmlWebpackPlugin({
filename: './index.html',
}),
new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'vue',
entry: 'https://unpkg.com/vue/dist/vue.js',
global: 'Vue',
}, {
module: 'vue-router',
entry: 'https://unpkg.com/vue-router/dist/vue-router.js',
global: 'VueRouter'
}
]
})
]
}
再重新打包,可以看到 main.bundle.js 大小已經變成了1.21K ,同時在html中也自動幫我們引入了cdn腳本

速度優化
上面列舉了一些常用的體積優化方案,接下來我們介紹一些速度優化方案
分離配置文件
一般情況下,我們需要區分開發和生產兩套環境,各司其職。
在開發階段:我們需要 webpack-dev-server 來幫我們進行快速的開發,同時需要 HMR 熱更新 幫我們進行頁面的無刷新改動,而這些在 生產環境 中都是不需要的。
在生產階段:我們需要進行 代碼壓縮、目錄清理、計算 hash、提取 CSS 等等;
在實現起來也非常簡單,可以參考vue-cli2.9的配置,將文件拆分為三個:
- webpack.dev.js 開發環境的配置文件
- webpack.prod.js 生產環境的配置文件
- webpack.common.js 公共配置文件
通過 webpack-merge 來整合兩個配置文件共同的配置 webpack.common.js
減少查找過程
對 webpack 的 resolve 參數進行合理配置,使用 resolve 字段告訴 webpack 怎么去搜索文件
- 合理使用
resolve.extensions
很多時候我們在引入模塊的時候,都是不帶后綴的,webpack 會自動帶上后綴后去嘗試詢問文件是否存在,查詢的順序是按照我們配置 的 resolve.extensions 順序從前到后查找,webpack 默認支持的后綴是 js 與 json。所以我們應該把常用到的文件后綴寫在前面,或者 我們導入模塊時,盡量帶上文件后綴名。
雖然
extensions會優先查找數組內的值,但是我們不要一股腦兒的把所有后綴都往里面塞,這會調用多次文件的查找,這樣就會減慢打包速度。
- 優化
resolve.modules
這個屬性告訴 webpack 解析模塊時應該搜索的目錄,絕對路徑和相對路徑都能使用。使用絕對路徑之后,將只在給定目錄中搜索,從而減少模塊的搜索層級:
- 使用
resolve.alias減少查找過程
alias 的意思為 別名,能把原導入路徑映射成一個新的導入路徑。這種寫法不僅在編寫時更方便,也能讓 webpack 減少查找過程,比如以下配置:
module.exports = {
// ...
resolve: {
extensions: ['.vue', '.js'],
mainFiles: ['index', 'list'],
alias: {
alias: path.resolve(__dirname, '../src/alias'),
},
modules: [
path.resolve(__dirname, 'node_modules'), // 指定當前目錄下的 node_modules 優先查找
'node_modules', // 將默認寫法放在后面
]
},
//...
}
縮小構建目標
排除 Webpack 不需要解析的模塊,即使用 loader 的時候,在盡量少的模塊中去使用。
我們可以借助 include 和 exclude 這兩個參數,規定 loader 只在那些模塊應用和在哪些模塊不應用。
module.exports = {
// ...
module: {
rules: [
{
test: /\.js|jsx$/,
exclude: /node_modules/,
include: path.resolve(__dirname, '../src'),
use: ['babel-loader']
},
// ...
]
},
// ...
}
首先我們不加 exclude 和 include 兩個參數,打包一下 npm run build,打包時間 3280ms 左右:

接着我們加上這兩個參數,意思分別是:
exclude: /node_modules/:排除node_modules下面的文件include: path.resolve(__dirname, '../src'):只對src下面的文件使用
重新打包,結果變成了1021ms

利用多線程提升構建速度
由於運行在 Node.js 之上的 webpack 是單線程模型的,所以 webpack 需要處理的事情需要一件一件的做,不能多件事一起做
HappyPack
原理:每次 webapck 解析一個模塊,HappyPack 會將它及它的依賴分配給 worker 線程中。處理完成之后,再將處理好的資源返回給 HappyPack 的主進程,從而加快打包速度。

在
webpack4.0中使用happypack需要使用其5.0版本
我們將 HappyPack 引入公共配置文件,他的用法就是將相應的 loader 替換成 happypack/loader,同時將替換的 loader 放入其插件的 loaders 選項,我們暫且替換一下 babel-loader:
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');
const HappyPack = require('happypack');
module.exports = {
mode: 'production',
entry: {
index: './src/index.js',
entry1: './src/entry1.js',
entry2: './src/entry2.js',
entry3: './src/entry3.js',
entry4: './src/entry4.js',
entry5: './src/entry5.js',
},
output: {
filename: '[name].bundle.js',
path: path.join(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: [
// 'babel-loader'
'happypack/loader?id=happyBabel'
],
exclude: /node_modules/,
include: path.resolve(__dirname, 'src'),
}
]
},
plugins: [
new htmlWebpackPlugin({
filename: './index.html',
}),
new HappyPack({
id: 'happyBabel',
loaders: ['babel-loader'],
verbose: true,
})
]
}
為了讓打包更明顯,在入口多增加了幾個頁面,首先是在使用 happypack 的情況下打包,耗時是5.7s左右

開啟 happypack 之后,我們可以從控制台中看到,happypack 默認幫我們開啟了 3 個進程,打包時間變成了1.1 左右:

注意:
HappyPack的作者現在基本上也不維護這個插件了,因為作者對此項目的興趣正在減弱。他也推薦我們使用webpack官方 thread-loader。
更多參數大家可以參考 HappyPack 官網
thread-loader
webpack 官方推出的一個多進程方案,用來替代 HappyPack。
原理和 HappyPack 類似,webpack 每次解析一個模塊,thread-loader 會將它及它的依賴分配給 worker 線程中,從而達到多進程打包的目的。
使用很簡單,直接在我們使用的 loader 之前加上 thread-loader 就行,我們需要先注釋掉 HappyPack 代碼:
// npm install --save-dev thread-loader
module.exports = {
// ...
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: [
{
loader: 'thread-loader',
options: {
workers: 3, // 開啟幾個 worker 進程來處理打包,默認是 os.cpus().length - 1
}
},
'babel-loader'
// 'happypack/loader?id=happyBabel'
],
exclude: /node_modules/,
include: path.resolve(__dirname, 'src'),
}
]
},
// ...
}
重新打包之后和 happypack 打包的結果差不多

預先編譯資源模塊(DllPlugin)
我們在打包的時候,一般來說第三方模塊是不會變化的,所以我們想只要在第一次打包的時候去打包一下第三方模塊,並將第三方模塊打包到一個特定的文件中,當第二次 webpack 進行打包的時候,就不需要去 node_modules 中去引入第三方模塊,而是直接使用我們第一次打包的第三方模塊的文件就行。
webpack.DllPlugin 就是來解決這個問題的插件,使用它可以在第一次編譯打包后就生成一份不變的代碼供其他模塊引用,這樣下一次構建的時候就可以節省開發時編譯打包的時間。
DllPlugin是webpack內置的插件,不需要額外安裝,直接配置webpack.dll.config.js文件:
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
// 需要打包的第三方庫
react: ['react', 'react-dom']
},
output: {
// 輸出的動態鏈接庫的文件名稱,[name] 代表當前動態鏈接庫的名稱,
filename: '[name].dll.js',
path: path.resolve(__dirname, 'dist/dll'),
// library必須和后面dllplugin中的name一致 后面會說明
library: '[name]_dll_[hash]'
},
plugins: [
// 接入 DllPlugin
new webpack.DllPlugin({
// 動態鏈接庫的全局變量名稱,需要和 output.library 中保持一致
// 該字段的值也就是輸出的 manifest.json 文件 中 name 字段的值
name: '[name]_dll_[hash]',
// 描述動態鏈接庫的 manifest.json 文件輸出時的文件名稱
path: path.join(__dirname, 'dist/dll', '[name].manifest.json')
}),
]
}
- 上面的
library的意思其實就是將dll文件以一個全局變量的形式導出出去,便於接下來引用,如下圖: mainfest.json文件是一個映射關系,它的作用就是幫助webpack使用我們之前打包好的***.dll.js文件,而不是重新再去node_modules中去尋找。
我們在 package.json 中配置打包的命令,進行dll打包,可以看到根目錄生成了一個 dll 文件夾,並且在下面生成了相應的文件
{
"scripts": {
"build": "webpack",
"dll": "webpack --config webpack.dll.config"
},
}

接着我們需要去修改公共配置文件 webpack.config.js,將我們之前生成的 dll 文件導入到 html 中去,如果我們不想自己手動向 html 文件去添加 dll 文件的時候,我們可以借助一個插件 add-asset-html-webpack-plugin,此插件顧名思義,就是將一些文件加到 html 中去。
// npm install --save-dev add-asset-html-webpack-plugin
const path = require('path');
const webpack = require('webpack');
const htmlWebpackPlugin = require('html-webpack-plugin');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
// ...
plugins: [
new htmlWebpackPlugin({
filename: './index.html'
}),
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, 'dist/dll/react.dll.js')
}),
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, 'dist/dll/react.manifest.json')
})
]
}
我們再進行一次打包,可以看到打包耗時為 697ms,縮短了打包時間

緩存 Cache 相關
我們可以開啟相應 loader 或者 plugin 的緩存,來提升二次構建的速度。一般我們可以通過下面幾項來完成:
babel-loader開啟緩存terser-webpack-plugin開啟緩存- 使用
cache-loader或者 hard-source-webpack-plugin
如果項目中有緩存的話,在 node_modules 下會有相應的 .cache 目錄來存放相應的緩存。
babel-loader
首先我們開啟 babel-loader 的緩存,我們修改 babel-loader 的參數,將參數 cacheDirectory 設置為 true即可
module.exports = {
// ...
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
}
},
],
// exclude: /node_modules/,
// include: path.resolve(__dirname, 'src'),
}
]
}
}
首次打包時間為 3.3s 左右,打包完成之后,我們可以發現在 node_modules 下生成了一個 .cache 目錄,里面存放了 babel 的緩存文件:


再重新打包一次,時間變成了 774ms:

TerserPlugin
我們通過將 TerserPlugin 中的 cache 設為 true,就可以開啟緩存:
// npm install --save-dev terser-webpack-plugin@4.x 注意webpack4 要使用4.x版本
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: 4, // 開啟幾個進程來處理壓縮,默認是 os.cpus().length - 1
cache: true,
}),
],
},
// ...
}
和上面一樣,也會在 node_modules 目錄下生成 .cache 文件,下面包含 terser-webpack-plugin 目錄
HardSourceWebpackPlugin
這個插件其實就是用於給模塊提供一個中間的緩存。
使用如下,我們直接在插件中引入就 ok 了:
// npm install --save-dev hard-source-webpack-plugin
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
// ...
plugins: [
new HardSourceWebpackPlugin()
],
// ...
}
具體的一些其他配置,參考 hard-source-webpack-plugin
合理使用 sourceMap
打包生成 sourceMap 的時候,如果信息越詳細,打包速度就會越慢
eval: 生成代碼 每個模塊都被eval執行,並且存在@sourceURL
cheap-eval-source-map: 轉換代碼(行內) 每個模塊被eval執行,並且sourcemap作為eval的一個dataurl
cheap-module-eval-source-map: 原始代碼(只有行內) 同樣道理,但是更高的質量和更低的性能
eval-source-map: 原始代碼 同樣道理,但是最高的質量和最低的性能
cheap-source-map: 轉換代碼(行內) 生成的sourcemap沒有列映射,從loaders生成的sourcemap沒有被使用
cheap-module-source-map: 原始代碼(只有行內) 與上面一樣除了每行特點的從loader中進行映射
source-map: 原始代碼 最好的sourcemap質量有完整的結果,但是會很慢
對於打包后的sourceMap,webpack提供多種類型的配置。

其他優化
- 盡量都使用
ES6 Modules語法,以保證Tree-Shaking起作用
因為 tree-shaking 只對 ES6 Modules 靜態引入生效,對於類似於 CommonJs 的動態引入方式是無效的
- 合理使用
Ployfill
如果我們對於引入的 polyfill 不做處理的話,Webpack 會把所有的 Polyfill 都加載進來,導致產出文件過大。推薦使用 @babel/preset-env 的 useBuiltIns='usage' 方案,此配置項會根據瀏覽器的兼容性幫助我們按需引入所需的墊片;此外我們也可以使用動態 polyfill 服務,每次根據瀏覽器的 User Agent,下發不同的 Polyfill,具體可以參考 polyfill.io。
- 預加載資源
webpackPrefetch
使用 webpackPrefetch 來提前預加載一些資源,意思就是 將來可能需要一些模塊資源,在核心代碼加載完成之后帶寬空閑的時候再去加載需要用到的模塊代碼。
icon類圖片使用css Sprite來合並圖片
如果 icon 類圖片太多的話,就使用雪碧圖合成一張圖片,減少網絡請求,或者使用字體文件 iconfont
- 合理配置
chunk的哈希值
在生產環境打包,一定要配置文件的 hash,這樣有助於瀏覽器緩存我們的文件,當我們的代碼文件沒變化的時候,用戶就只需要讀取瀏覽器緩存的文件即可。一般來說 javascript 文件使用 [chunkhash]、css 文件使用 [contenthash]、其他資源(例如圖片、字體等)使用 [hash]。
-
vue-router 路由懶加載
-
動態組件
demo地址:
https://github.com/Shenjieping/webpack-optimization/tree/main/webpack
參考文章:
