webpack性能優化
- 2021 年 7 月 2 日
- 筆記
- javascript, webpack優化, webpack打包
為什麼要優化?
如果你的項目很小,構建很快,其實不用特別在意性能方面的問題。但是隨着項目涉及到的頁面越來越多,功能和業務代碼也會越來越複雜,相應的 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 [email protected] 注意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 [email protected]
// 此插件需要依賴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: '//unpkg.com/vue/dist/vue.js',
global: 'Vue',
}, {
module: 'vue-router',
entry: '//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 [email protected] 注意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地址:
//github.com/Shenjieping/webpack-optimization/tree/main/webpack
參考文章:
//github.com/darrell0904/webpack-doc