项目分析
痛点
- 项目打包后的体积很大,打包时间也很长,首次打包时长预计8min-20min,导致每次遇到紧急线上问题或者紧急需求时,开发两分钟,发布两小时。整个发布时长拉长,用户体验不好
- 项目启动非常慢,热更新非常慢,开发效率大大减少
分析
- 原项目总的打包体积为****146.83MB!! ,压缩后也****38.9MB!! ,体积非常大。

- 很多公共的模块出现在多个chunk 里面,比如antd相关等

- 启动时间非常慢,热更新速度非常慢,影响开发效率,启动速度大约164s/3min~ ,热更新大约16s

体积优化
step1:升级到umi3.5.0
umi版本较低,许多新特性无法使用,修改 umi
的版本为 ^3.5.0
或以上
json
{
"devDependencies": {
- "umi": "^3.2.0-beta.6",
+ "umi": "^3.5.0",
}
}
step2:提取公共依赖
调整 splitChunks 策略,增加公共依赖的提取,减少整体尺寸
php
// 如果开了 dynamicImport,然后产物特别大,每个出口文件都包含了相同的依赖,比如 antd,可尝试通过 splitChunks 配置调整公共依赖的提取策略。
dynamicImport: {
loading: '@/components/Loading',
},
chunks: ['vendors', 'umi'],
chainWebpack: function(config) {
config.merge({
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000,
minChunks: 3,
automaticNameDelimiter: '.',
cacheGroups: {
vendor: {
name: 'vendors',
test({ resource }) {
return /[\/]node_modules[\/]/.test(resource);
},
priority: 10,
},
},
},
},
});
},
- 首次见效



做完以上两步骤后可以看出,以上修改后包体积大大减少,提升率达到91%!!
step3:删除多余依赖
项目中一些依赖包没有用到,但仍然安装,可以删除这些不必要的依赖包
json
{
"dependencies": {
-"add": "^2.0.6",
-"behooks": "0.8.3",
},
"devDependencies": {
-"webpack": "^4.43.0"
},
}
step4:优化冗余依赖(暂时注释)
对于一些大尺寸依赖,比如图表库、antd 等,可尝试通过 externals 的配置引入相关 umd 文件,减少编译消耗
xlsx/react-dom/react
不再使用npm方式安装xlsx 通过externals 引入cdn链接


xml
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.16.9/xlsx.mini.min.js" integrity="sha512-bF+HLFD/vkMdiECsWsJfRUkaQObWj/BhCE4E9eDfZL2VN6HDh1JKMzRFs0uOPUwic71Zvodr5imoD7eT3eVdGA==" crossorigin="anonymous"></script>
rust
externals: {
react: 'window.React',
'react-dom': 'window.ReactDOM',
xlsx: 'XLSX',
},
// 区分 development 和 production,使用不同的产物
scripts:
process.env.NODE_ENV === 'development'
? [
'https://gw.alipayobjects.com/os/lib/react/17.0.0/umd/react.development.js',
'https://gw.alipayobjects.com/os/lib/react-dom/17.0.0/umd/react-dom.development.js',
]
: [
'https://gw.alipayobjects.com/os/lib/react/17.0.0/umd/react.production.min.js',
'https://gw.alipayobjects.com/os/lib/react-dom/17.0.0/umd/react-dom.production.min.js',
],
通过externals引入后,xlsx/react-dom/react依赖将不再占用包内存! 大约减少了5mb!
由于外部cdn可能挂掉,这项优化项暂时不做~
moment.js
忽略 moment 的 locale 文件,用于减少尺寸
yaml
ignoreMomentLocale: true,


优化前大小:1.05mb、压缩大小:271.68kb
优化前大小:118.41kb、压缩大小:37.73kb
可以看出moment体积大大减小!!
step5:开启gzip压缩
需要Nginx开启Gzip属性
npm install compression-webpack-plugin
javascript
const CompressionWebpackPlugin = require('compression-webpack-plugin');
// 开启gzip压缩
config.plugin('CompressionPlugin').use(
new CompressionWebpackPlugin({
test: /.(js|css)$/,
threshold: 10240, // 超过10kb的文件就压缩
deleteOriginalAssets: false, // 不删除源文件
minRatio: 0.8,
}),
);
编译提速
webpack4升级webpack5
webpack.docschina.org/migrate/5/
webpack5的加载速度比webpack4加载更快
css
webpack5: {},
esbuild
编译慢中压缩时间占了大部分,所以如果编译时不压缩可节约大量的时间和内存消耗,但尺寸会增加不少。替换terser压缩器为esbuild,来提高编译速度。
css
esbuild: {},

fastRefresh
开发环境下,可以保持组件状态 ,同时编辑提供即时反馈
css
fastRefresh: {},
nodeModulesTransform
Umi 默认编译 node_modules 下的文件,带来一些收益的同时,也增加了额外的编译时间。如果不希望 node_modules 下的文件走 babel 编译,可通过以下配置减少 40% 到 60% 的编译时间
css
nodeModulesTransform: {
type: 'none',
exclude: [],
},
mfsu
arduino
// 有缓存时启动 1s+,热更新平均 500ms 内
mfsu: {},
分析对比
产物分布对比

未压缩体积对比

已压缩体积对比

首次启动速度对比

二次启动速度对比

热更新速度对比

tone首次发布时长对比


花费预计10分钟


花费预计5分钟
tone二次发布时长对比


花费预计4分钟


花费预计1分钟
总结
未压缩体积m | 已压缩体积m | 首次启动速度s | 二次启动速度s | 热更新速度s | tone首次发布时min | tone二次发布时长min | |
---|---|---|---|---|---|---|---|
优化前 | 146.83 | 38.9 | 141 | 98.4 | 7 | 10 | 4 |
优化后 | 9.89 | 2.78 | 9 | 3.7 | 2 | 5 | 1 |
提升率% | 93.26431928 | 92.85347044 | 93.61702128 | 96.2398374 | 71.42857143 | 50 | 75 |

可以看出数据呈现直线下降趋势,效率大大提高!或许一些数据只提升了几分钟甚至几秒钟的效率,但当我们在开发一个版本时,不停修改保存打包代码,次数不断累加时,就会发现效率产生质的飞跃!
过程问题
- 开启了mfsu报错:Unhandled Rejection (ChunkLoadError): Loading chunk mf-dep_vendors failed.
将splitChunks策略放在生产环境执行
- tone构建出现告警:(node:45214) [DEP_WEBPACK_CONFIGURATION_OPTIMIZATION_NO_EMIT_ON_ERRORS] DeprecationWarning: optimization.noEmitOnErrors,压缩css失败 optimization,optimize-css-assets-webpack-plugin插件与webpack5不兼容引起的警告,但不影响压缩
webpack5中同等功能的插件是css-minimizer-webpack-plugin,安装并修改配置
由于无法下载css-minimizer-webpack-plugin 暂未使用
csharp
yarn add -D css-minimizer-webpack-plugin
yaml
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
],
}
- 时间组件全部变成英文格式
由于配置了 ignoreMomentLocale: true 忽略了moment.js所有语言包,需要单独在app.js导入中文语言包
javascript
import moment from 'moment'
// 导入中文语言包
import 'moment/locale/zh-cn';
// 设置中文
moment.locale('zh-cn');
umirc.ts配置
javascript
import { defineConfig, IConfig } from 'umi';
import routes from './routes';
import path from 'path';
const CompressionWebpackPlugin = require('compression-webpack-plugin')
// 生产环境配置
let configPro = {};
if (process.env.NODE_ENV !== 'development') {
// 开启了mfsu报错:Unhandled Rejection (ChunkLoadError): Loading chunk mf-dep_vendors failed.
// splitChunks 策略 只能放在生产环境
configPro = {
// Umi 默认编译 node_modules 下的文件,带来一些收益的同时,也增加了额外的编译时间。如果不希望 node_modules 下的文件走 babel 编译,可通过以下配置减少 40% 到 60% 的编译时间
nodeModulesTransform: {
type: 'none',
exclude: [],
},
chunks: ['vendors', 'umi'],
chainWebpack: function(config: any) {
if (process.env.UMI_ENV !== 'development') {
config.merge({
optimization: {
// 提取公共依赖,调整 splitChunks 策略,减少整体尺寸
splitChunks: {
chunks: 'all',
minSize: 30000,
minChunks: 3,
automaticNameDelimiter: '.',
cacheGroups: {
vendor: {
name: 'vendors',
test({ resource }) {
return /[\/]node_modules[\/]/.test(resource);
},
priority: 10,
},
},
},
},
});
// 开启gzip压缩
config.plugin('CompressionPlugin').use(
new CompressionWebpackPlugin({
test: /.(js|css)$/,
threshold: 10240, // 超过10kb的文件就压缩
deleteOriginalAssets: false, // 不删除源文件
minRatio: 0.8
})
)
}
},
// 替换压缩器为 esbuild
esbuild: {},
};
}
const config: IConfig = {
title: '陆运通运营后台',
antd: {},
dva: {},
hash: true,
history: {
type: 'hash',
},
publicPath: '/s/lytAdminSite/',
theme: {
'primary-color': '#2B82D8',
},
routes,
alias: {
'@': path.resolve(__dirname, 'src'),
// TODO 本地开发使用的
// 'tf-exportFile/libs': path.join(__dirname, 'dev-npm/lyt-components/tf-exportFile/src/components/index'),
// 'tf-traceService/libs': path.join(__dirname, 'dev-npm/lyt-components/tf-traceService/src/components/index'),
// 'tf-lookHd/libs': path.join(__dirname, 'dev-npm/lyt-components/tf-lookHd/src/components/index.js'),
},
devServer: {
host: '0.0.0.0',
port: 4000,
proxy: {
'/landTransAdmin': {
target: 'https://optest.tf56.com',
// target: 'http://optest.tf56.lo', //容器云环境
// target: 'http://10.50.9.154:8080',
changeOrigin: true,
},
},
https: {
key: './public/mytest.tf56.com-key.pem',
cert: './public/mytest.tf56.com.pem',
},
},
/**-------------------------------------编译提速配置-------------------------------- */
// 如果开了 dynamicImport,然后产物特别大,每个出口文件都包含了相同的依赖,比如 antd,可尝试通过 splitChunks 配置调整公共依赖的提取策略。
dynamicImport: {
loading: '@/components/Loading',
},
// 忽略 moment 的 locale 文件,用于减少尺寸。
ignoreMomentLocale: true,
// 快速刷新
fastRefresh: {},
// 对于一些大尺寸依赖,比如图表库、antd 等,可尝试通过 externals 的配置引入相关 umd 文件,减少编译消耗。
// externals: {
// "react": 'window.React',
// 'react-dom': 'window.ReactDOM',
// 'xlsx': 'XLSX'
// },
// 区分 development 和 production,使用不同的产物
// scripts:
// process.env.NODE_ENV === 'development'
// ? [
// 'https://gw.alipayobjects.com/os/lib/react/17.0.0/umd/react.development.js',
// 'https://gw.alipayobjects.com/os/lib/react-dom/17.0.0/umd/react-dom.development.js',
// ]
// : [
// 'https://gw.alipayobjects.com/os/lib/react/17.0.0/umd/react.production.min.js',
// 'https://gw.alipayobjects.com/os/lib/react-dom/17.0.0/umd/react-dom.production.min.js',
// ],
// 有缓存时启动 1s+,热更新平均 500ms 内
mfsu: {},
webpack5: {},
...configPro,
};
export default defineConfig(config);