在使用 Webpack 時,若是不注意性能優化,可能會產生性能問題,會致使在開發體驗上不是很是絲滑,性能問題主要是編譯速度慢,打包體積過大,所以性能優化也主要從這些方面來分析。本文主要是本身平時的工做積累和參考別人的文章,而進行總結,基於 Webpack4 版本。javascript
對 Webpack 構建速度進行優化的首要任務就是去知道哪些地方值得咱們注意。speed-measure-webpack-plugin
插件可以測量 Webpack 構建速度css
SMP ⏱ General output time took 38.3 secs SMP ⏱ Plugins HtmlWebpackPlugin took 1.31 secs CopyPlugin took 0.016 secs OptimizeCssAssetsWebpackPlugin took 0.002 secs ContextReplacementPlugin took 0.001 secs MiniCssExtractPlugin took 0 secs DefinePlugin took 0 secs SMP ⏱ Loaders _babel-loader@8.1.0@babel-loader took 29.98 secs module count = 1503 _babel-loader@8.1.0@babel-loader, and _eslint-loader@3.0.4@eslint-loader took 18.74 secs module count = 86 _css-loader@3.6.0@css-loader, and _less-loader@5.0.0@less-loader took 16.45 secs module count = 64 modules with no loaders took 2.24 secs module count = 7 _file-loader@5.1.0@file-loader took 1.03 secs module count = 17 _style-loader@1.3.0@style-loader, and _css-loader@3.6.0@css-loader, and _less-loader@5.0.0@less-loader took 0.102 secs module count = 64 _html-webpack-plugin@3.2.0@html-webpack-plugin took 0.021 secs module count = 1
竟然達到了驚人的38.3秒,雖然有點不是很準確,可是很是慢。發現 babel-loader、eslint-loader、css-loader、less-loader 佔據了大頭。html
const webpackBase = require('./webpack.base.conf'); const path = require('path'); const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin'); const smp = new SpeedMeasureWebpackPlugin(); module.exports = smp.wrap({ // 配置源碼顯示方式 devtool: 'eval-source-map', mode: 'development', entry: { app: ['./src/index.jsx'] }, output: { path: path.resolve(__dirname, 'dist'), filename: 'index.js' }, resolve: webpackBase.resolve, module: webpackBase.module, stats: webpackBase.stats, optimization: webpackBase.optimization, plugins: [ webpackBase.plugins.html, webpackBase.plugins.miniCssExtract, webpackBase.plugins.optimizeCssAssets, // webpackBase.plugins.progressBarPlugin, webpackBase.plugins.ContextReplacementPlugin, webpackBase.plugins.DefinePlugin, // webpackBase.plugins.AntdDayjsWebpackPlugin, webpackBase.plugins.CopyPlugin // webpackBase.plugins.HotModuleReplacementPlugin ], devServer: webpackBase.devServer, watchOptions: webpackBase.watchOptions, externals: webpackBase.externals });
經過 webpack-bundle-analyzer
插件可以在 Webpack 構建結束後生成構建產物體積報告,配合可視化的頁面,可以直觀知道產物中的具體佔用體積。java
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { plugins: bundleAnalyzer: new BundleAnalyzerPlugin({ analyzerPort: 8081 })], };
效果圖以下:node
能夠看出一個很明顯的問題就是 ant、trtc、mobx 這些庫,沒有排除。react
打包體積以下:webpack
const path = require('path'); module.exports = { resolve: { // 自動解析肯定的擴展 extensions: ['.js', '.jsx', '.css', '.less', '.json'], alias: { // 建立 import 或 require 的別名,來確保模塊引入變得更簡單 'react': path.resolve( __dirname ,'./node_modules/react/dist/react.min.js') }, // 當從 npm 包導入模塊時,此選項將決定在 `package.json` 中使用哪一個字段導入模塊 // 默認值爲 browser -> module -> main mainFields: ['main'] }, module: { rules: [ { // 排除node_modules模塊 test: /\.(js|jsx)$/, exclude: /node_modules/, // 開啓緩存 loader: 'babel-loader?cacheDirectory=true' } ] } };
thread-loader 會將你的 loader 放置在一個 worker 池裏面運行,每一個 worker 都是一個單獨的有 600ms 限制的 node.js 進程。同時跨進程的數據交換也會被限制。請在高開銷的 loader 中使用,不然效果不佳。git
module.exports = { module: { rules: [ { test: /\.js$/, include: path.resolve('src'), use: [ 'thread-loader', // your expensive loader (e.g babel-loader) ], }, ], }, };
在 Webpack4 中,hard-source-webpack-plugin 是 DLL 的更好替代者。github
hard-source-webpack-plugin 是 Webpack 的插件,爲模塊提供中間緩存步驟。爲了查看結果,您須要使用此插件運行 Webpack 兩次:第一次構建將花費正常的時間。第二次構建將顯着加快(大概提高 90%的構建速度)。不過該插件好久沒更新了,不太建議使用。web
因爲我項目中使用了 eslint-loader
若是配置了 precommit,其實能夠去掉的。
Webpack
module.exports = { // externals 排除對應的包,注:排除掉的包必需要用script標籤引入下 externals: { react: 'React', 'react-dom': 'ReactDOM', 'trtc-js-sdk': 'TRTC', bizcharts: 'BizCharts', antd: 'antd', mobx: 'mobx', 'mobx-react': 'mobxReact' } };
index.html
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" /> <meta name="baidu-site-verification" content="ptk9VJudKz" /> <link rel="stylesheet" href="https://xxx/antd.min3.26.20.css" /> <title>webpack</title> <script type="text/javascript" src="https://xxx/17.0.0react.production.min.js" ></script> <script type="text/javascript" src="https://xxx/17.0.0react-dom.production.min.js" ></script> <script type="text/javascript" src="https://xxx/BizCharts3.5.8.js" ></script> <script type="text/javascript" src="https://xxx/trtc4.6.7.js" ></script> <script type="text/javascript" src="https://xxx/moment2.29.1.min.js" ></script> <script type="text/javascript" src="https://xxx/moment2.29.1zh-cn.js" ></script> <script type="text/javascript" src="https://xxx/polyfill.min7.8.0.js" ></script> <script type="text/javascript" src="https://xxx/antd.min3.26.20.js" ></script> <script type="text/javascript" src="https://xxx/mobx.umd.min5.13.1.js" ></script> <script type="text/javascript" src="https://xxx/mobx-react.index.min5.4.4.js" ></script> </head> <body> <div id="root"></div> </body> </html>
從 Webpack4 開始,默認狀況下使用 terser 壓縮生產環境下的輸出結果。Terser 是一款兼容 ES2015 + 的 JavaScript 壓縮器。與 UglifyJS(許多項目的早期標準)相比,它是面向將來的選擇。有一個 UglifyJS 的分支—— uglify-es,但因爲它再也不維護,因而就從這個分支誕生出了一個獨立分支,它就是 terser。
const TerserPlugin = require('terser-webpack-plugin'); module.exports = { optimization: { minimizer: [ // 壓縮js new TerserPlugin({ test: /\.(jsx|js)$/, extractComments: true, parallel: true, cache: true }) ] }, };
Webpack 4.0 之後,官方推薦使用 mini-css-extract-plugin 插件來打包 CSS 文件。
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { module: { rules: [ { test: /\.(css|less)$/, use: [MiniCssExtractPlugin.loader] } ] }, };
請確保加載順序,moment、polyfill 放在 ant 前面加載
mobx引入 mobx.umd.min.js 庫,mobx-react須要引入
{ "name": "webpack", "version": "1.0.0", "private": true, "main": "index.js", "dependencies": { "antd": "^3.26.20", "babel-eslint": "^10.0.3", "babel-loader": "^8.0.0", "babel-plugin-import": "^1.13.0", "babel-plugin-react-css-modules": "^5.2.6", "bizcharts": "^3.5.8", "china-division": "^2.3.1", "compression-webpack-plugin": "^3.0.1", "copy-webpack-plugin": "^5.1.1", "css-loader": "^3.2.0", "eslint": "^6.8.0", "eslint-config-prettier": "^6.11.0", "eslint-config-standard": "^14.1.0", "eslint-loader": "^3.0.4", "eslint-plugin-import": "^2.20.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-react": "^7.17.0", "eslint-plugin-standard": "^4.0.1", "html-webpack-plugin": "^3.2.0", "less": "^3.8.1", "less-loader": "^5.0.0", "lint-staged": "^10.0.8", "mini-css-extract-plugin": "^0.8.0", "mobx": "^5.13.1", "mobx-react": "^5.4.4", "optimize-css-assets-webpack-plugin": "^5.0.1", "pre-commit": "^1.2.2", "progress-bar-webpack-plugin": "^1.12.1", "react": "^17.0.0", "react-dom": "^17.0.0", "speed-measure-webpack-plugin": "^1.3.1", "style-loader": "^1.2.1", "terser-webpack-plugin": "^2.2.1", "trtc-js-sdk": "^4.6.7", "viewerjs": "^1.5.0", "webpack": "^4.41.2", "webpack-bundle-analyzer": "^3.6.0", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.10.1" } }
打包體積:
打包體積由原先 2.1M 變成了 882KB,能夠說效果很是巨大。
包依賴:
ant、trtc、mobx 這些庫也沒了
編譯速度:
SMP ⏱ General output time took 10.67 secs SMP ⏱ Plugins HtmlWebpackPlugin took 1.69 secs BundleAnalyzerPlugin took 0.091 secs CopyPlugin took 0.011 secs MiniCssExtractPlugin took 0.003 secs OptimizeCssAssetsWebpackPlugin took 0.002 secs DefinePlugin took 0.001 secs ContextReplacementPlugin took 0 secs SMP ⏱ Loaders _babel-loader@8.1.0@babel-loader took 8.26 secs module count = 277 _babel-loader@8.1.0@babel-loader, and _eslint-loader@3.0.4@eslint-loader took 7.18 secs module count = 86 _css-loader@3.6.0@css-loader, and _less-loader@5.0.0@less-loader took 1.94 secs module count = 28 modules with no loaders took 0.728 secs module count = 12 _file-loader@5.1.0@file-loader took 0.392 secs module count = 17 _style-loader@1.3.0@style-loader, and _css-loader@3.6.0@css-loader, and _less-loader@5.0.0@less-loader took 0.052 secs module count = 28 _html-webpack-plugin@3.2.0@html-webpack-plugin took 0.026 secs module count = 1
編譯速度由原先 38.3 secs(實際編譯速度大概15秒左右),減小到 10.67 secs(實際編譯速度10秒左右)。