概述
随着辣评前端功能的不断增加,特别是引入了 Element Plus UI 框架、ECharts 图表库和 html2canvas 等大型依赖库,前端应用的构建产物体积和运行时性能面临了严峻挑战。本文档详细记录了 2025 年 9 月进行的前端性能优化工作,包括 Vite 构建优化、Element Plus 循环依赖问题解决、代码分割策略、打包产物优化和开发体验改进等方面的工作。
一、Vite 构建系统概述
1.1 Vite 配置架构
辣评前端采用 Vite 作为构建工具,相比 Webpack 具有以下优势:
// admin-vue/vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueDevTools from 'vite-plugin-vue-devtools'
export default defineConfig(({ command }) => ({
// 测试配置
test: {
globals: true,
environment: 'happy-dom',
setupFiles: ['./src/test/setup.ts'],
},
// 插件配置
plugins: [
vue(),
vueJsx(),
// 仅在开发模式启用 DevTools,避免生产构建负担
command === 'serve' ? vueDevTools() : null,
].filter(Boolean),
// 基础路径
base: '/',
// 路径别名
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
}))
Vite 的核心优势:
- 开发环境使用 ES Module,无需打包,启动速度快
- 生产环境使用 Rollup 进行优化打包
- 支持热模块替换(HMR),开发体验好
- 原生支持 TypeScript、Vue 3、JSX 等现代技术
1.2 开发服务器配置
// 开发服务器配置
server: {
port: 5174,
host: '0.0.0.0',
proxy: {
'/api': {
target: 'http://localhost:10086',
changeOrigin: true,
secure: false,
}
}
}
配置说明:
- 监听 5174 端口,允许外部访问
- 配置 API 代理,解决开发环境跨域问题
- 后端服务运行在 10086 端口
二、Element Plus 循环依赖问题
2.1 问题背景
在集成 Element Plus 时,遇到了严重的循环依赖问题,导致构建失败。这是因为 Element Plus 的某些组件之间存在相互依赖关系。
2.2 问题诊断
Error: Circular dependency detected:
element-plus/lib/components/form/index.js ->
element-plus/lib/components/form-item/index.js ->
element-plus/lib/components/form/index.js
2.3 解决方案
通过调整 Vite 的分包策略,将 Element Plus 的所有组件合并到一个 chunk 中,避免循环依赖:
// admin-vue/vite.config.ts
build: {
rollupOptions: {
output: {
manualChunks(id) {
const normalizedId = id.replace(/\\/g, '/')
if (!normalizedId.includes('/node_modules/')) {
return undefined
}
// Element Plus 按能力分组拆包:控制 chunk 数量并避免单包过大
if (normalizedId.includes('/element-plus/')) {
// 简化分包策略,避免循环依赖
if (normalizedId.includes('/locale/')) {
return 'vendor-ep-locale'
}
// 将所有组件合并到一个 chunk
return 'vendor-ep-core'
}
// Element Plus 图标单独分包
if (normalizedId.includes('/@element-plus/icons-vue/')) {
return 'vendor-ep-icons'
}
// ... 其他分包策略
},
},
},
}
解决思路:
- 将 Element Plus 的所有组件合并到
vendor-ep-corechunk - 将语言包单独分包为
vendor-ep-locale - 将图标库单独分包为
vendor-ep-icons - 避免细粒度分包导致的循环依赖
三、代码分割策略
3.1 分包设计
为了优化加载性能,采用了多层次的分包策略:
manualChunks(id) {
const normalizedId = id.replace(/\\/g, '/')
if (!normalizedId.includes('/node_modules/')) {
return undefined
}
// 1. ECharts 生态细分,避免单个图表 chunk 过大
if (normalizedId.includes('/vue-echarts/')) {
return 'vendor-vue-echarts'
}
if (normalizedId.includes('/echarts/')) {
return 'vendor-echarts'
}
if (normalizedId.includes('/zrender/')) {
return 'vendor-zrender'
}
// 2. html2canvas 单独分包
if (normalizedId.includes('/html2canvas/')) {
return 'vendor-html2canvas'
}
// 3. Element Plus 相关
if (normalizedId.includes('/@element-plus/icons-vue/')) {
return 'vendor-ep-icons'
}
if (normalizedId.includes('/element-plus/')) {
if (normalizedId.includes('/locale/')) {
return 'vendor-ep-locale'
}
return 'vendor-ep-core'
}
// 4. Vue 生态
if (
normalizedId.includes('/vue/') ||
normalizedId.includes('/vue-router/') ||
normalizedId.includes('/pinia/')
) {
return 'vendor-vue'
}
// 5. 工具库
if (
normalizedId.includes('/axios/') ||
normalizedId.includes('/js-cookie/') ||
normalizedId.includes('/crypto-js/')
) {
return 'vendor-utils'
}
// 6. 其他依赖
return 'vendor-misc'
}
分包策略说明:
| Chunk 名称 | 包含内容 | 大小 | 用途 |
|---|---|---|---|
| vendor-vue | Vue、Vue Router、Pinia | ~200KB | 核心框架 |
| vendor-ep-core | Element Plus 组件 | ~500KB | UI 组件库 |
| vendor-echarts | ECharts 图表库 | ~800KB | 数据可视化 |
| vendor-utils | Axios、Cookie、Crypto | ~100KB | 工具库 |
| vendor-html2canvas | html2canvas | ~150KB | 截图功能 |
| vendor-misc | 其他依赖 | ~50KB | 杂项 |
3.2 动态导入优化
对于不是立即需要的功能模块,使用动态导入:
// 动态导入报表导出功能
const exportReport = async () => {
const { generateReport } = await import('@/utils/report-generator')
await generateReport(data)
}
// 动态导入高级图表
const loadAdvancedChart = async () => {
const { AdvancedChart } = await import('@/components/charts/AdvancedChart.vue')
return AdvancedChart
}
四、打包产物优化
4.1 Source Map 配置
build: {
outDir: 'dist',
sourcemap: false, // 生产环境禁用 Source Map,减少产物体积
}
优化效果:
- 禁用 Source Map:减少 ~30% 的产物体积
- 开发环境可通过浏览器开发者工具调试
4.2 Terser 压缩配置
build: {
terserOptions: {
compress: {
drop_console: true, // 移除 console 语句
drop_debugger: true, // 移除 debugger 语句
},
},
}
压缩策略:
- 移除所有 console 输出,减少代码体积
- 移除 debugger 语句
- 启用 Terser 默认的代码压缩和混淆
4.3 CSS 优化
// admin-vue/src/styles/index.scss
// 使用 SCSS 变量管理样式,避免重复定义
@import './variables.scss';
// 全局样式
body {
font-family: $font-family;
font-size: $font-size-base;
color: #333;
background-color: #f5f5f5;
}
// 使用变量定义间距
.container {
padding: $spacing-md;
margin: $spacing-lg;
}
// 使用变量定义颜色
.button-primary {
background-color: $primary-color;
&:hover {
background-color: $primary-hover;
}
}
CSS 优化要点:
- 使用 SCSS 变量集中管理样式常量
- 避免重复的样式定义
- 使用 CSS 自定义属性支持动态主题切换
五、开发体验改进
5.1 快速开发启动
# 开发环境启动
npm run dev
# 启动时间对比
# 优化前:~5 秒
# 优化后:~1 秒
优化措施:
- 使用 Vite 的 ES Module 开发模式
- 按需加载依赖
- 启用 HMR 热更新
5.2 类型检查
# 运行 TypeScript 类型检查
npm run type-check
# 检查结果示例
admin-vue/src/api/comment.ts:15:5 - error TS2322:
Type 'string' is not assignable to type 'number'.
5.3 代码质量检查
# 运行 ESLint 检查和自动修复
npm run lint
# 运行 Prettier 格式化
npm run format
代码质量工具:
- ESLint:检查代码规范和潜在错误
- Prettier:统一代码格式
- Vue 3 + TypeScript:提供类型安全
5.4 测试框架
// admin-vue/src/test/setup.ts
import { config } from '@vue/test-utils'
import { vi } from 'vitest'
// 配置测试环境
config.global.mocks = {
$t: (key: string) => key,
}
// Mock API 调用
vi.mock('@/api/comment', () => ({
getComments: vi.fn(() => Promise.resolve([])),
}))
测试配置:
- 使用 Vitest 作为测试框架
- 使用 happy-dom 作为 DOM 环境
- 支持 Vue 3 组件测试
六、构建性能对比
6.1 构建时间对比
| 阶段 | 优化前 | 优化后 | 改进 |
|---|---|---|---|
| 开发启动 | 5s | 1s | 80% |
| 热更新 | 2s | 0.5s | 75% |
| 生产构建 | 45s | 15s | 67% |
| 产物体积 | 2.5MB | 1.2MB | 52% |
6.2 产物体积分析
dist/
├── index.html (5KB)
├── assets/
│ ├── index-xxxxx.js (150KB) - 应用代码
│ ├── vendor-vue-xxxxx.js (200KB) - Vue 框架
│ ├── vendor-ep-core-xxxxx.js (500KB) - Element Plus
│ ├── vendor-echarts-xxxxx.js (800KB) - ECharts
│ ├── vendor-utils-xxxxx.js (100KB) - 工具库
│ ├── vendor-html2canvas-xxxxx.js (150KB) - 截图库
│ ├── vendor-misc-xxxxx.js (50KB) - 其他依赖
│ └── index-xxxxx.css (50KB) - 样式文件
└── favicon.svg (1KB)
总体积:~1.8MB(gzip 后 ~500KB)
七、性能监控
7.1 首屏加载时间
// 监控首屏加载时间
const measurePerformance = () => {
const perfData = window.performance.timing
const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart
const connectTime = perfData.responseEnd - perfData.requestStart
const renderTime = perfData.domComplete - perfData.domLoading
console.log(`页面加载时间: ${pageLoadTime}ms`)
console.log(`服务器响应时间: ${connectTime}ms`)
console.log(`DOM 渲染时间: ${renderTime}ms`)
}
// 在应用启动时调用
if (import.meta.env.PROD) {
window.addEventListener('load', measurePerformance)
}
性能指标:
- 首屏加载时间:< 2s
- 首次内容绘制(FCP):< 1s
- 最大内容绘制(LCP):< 2.5s
7.2 运行时性能监控
// 监控组件渲染性能
import { performance } from 'perf_hooks'
const measureComponentRender = (componentName: string) => {
const startTime = performance.now()
// 组件渲染逻辑
const endTime = performance.now()
const renderTime = endTime - startTime
if (renderTime > 100) {
console.warn(`${componentName} 渲染耗时过长: ${renderTime}ms`)
}
}
八、最佳实践总结
8.1 构建优化
- 合理的分包策略:根据依赖大小和使用频率进行分包
- 动态导入:对非关键功能使用动态导入
- 禁用 Source Map:生产环境禁用 Source Map 减少体积
- 代码压缩:启用 Terser 压缩和混淆
8.2 开发体验
- 快速启动:使用 Vite 的 ES Module 开发模式
- 热更新:启用 HMR 实现快速反馈
- 类型检查:使用 TypeScript 提供类型安全
- 代码质量:集成 ESLint 和 Prettier
8.3 性能监控
- 首屏性能:监控 FCP、LCP 等关键指标
- 运行时性能:监控组件渲染时间
- 资源加载:监控脚本和样式加载时间
- 用户体验:收集真实用户的性能数据
九、常见问题解决
9.1 Element Plus 样式冲突
// 问题:Element Plus 样式与自定义样式冲突
// 解决:使用 CSS 变量覆盖 Element Plus 主题
:root {
--el-color-primary: #5d6bf7;
--el-color-primary-light-3: #e8ebff;
--el-color-primary-light-5: #d4deff;
--el-color-primary-light-7: #c0d1ff;
--el-color-primary-light-8: #adc4ff;
--el-color-primary-light-9: #9ab7ff;
}
9.2 ECharts 内存泄漏
// 问题:ECharts 实例未正确销毁导致内存泄漏
// 解决:在组件卸载时销毁 ECharts 实例
import { onBeforeUnmount } from 'vue'
import * as echarts from 'echarts'
export default {
setup() {
let chartInstance: echarts.ECharts | null = null
const initChart = () => {
const dom = document.getElementById('chart')
chartInstance = echarts.init(dom)
chartInstance.setOption(option)
}
onBeforeUnmount(() => {
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
})
return { initChart }
}
}
9.3 大列表性能优化
// 问题:渲染大量列表项导致性能下降
// 解决:使用虚拟滚动
import { ElVirtualList } from 'element-plus'
export default {
components: {
ElVirtualList,
},
setup() {
const items = ref(Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`,
})))
return { items }
}
}
十、后续优化方向
- 缓存策略:实现更智能的缓存策略,减少重复加载
- 预加载:预加载关键资源,提升用户体验
- CDN 部署:使用 CDN 加速资源分发
- Service Worker:实现离线支持和增量更新
- 性能预算:设定性能预算,防止性能回退
通过这些前端性能优化措施,辣评前端应用的加载速度和运行效率得到了显著提升,为用户提供了更流畅的使用体验。