perf(web): ECharts 按需注册 + Vite vendor manualChunks #244

- 新增 web/src/utils/echarts.js 作为全站唯一入口: echarts/core
  按需 use() bar/line/pie/heatmap + title/tooltip/grid/legend/
  visualMap/markPoint/markLine/markArea + CanvasRenderer
- BaseChart.vue 删整包 import * as echarts, 改引 @/utils/echarts
- vite.config.js manualChunks: echarts/zrender、element-plus、
  vue 生态、dayjs、其余 vendor 独立成块

分包效果:
- BaseChart chunk 1128KB → 0.87KB
- echarts 按需块 627KB(整包约 1MB)
- index 主包 1275KB → 28.7KB
- element-plus 954KB / vue-vendor 111KB / vendor 228KB 独立缓存

lint 0 error, build 通过, 120 例全通过;
另跑运行时冒烟确认四类图表+mark*+visualMap 无缺失注册错误。

S-P1-3 / B2-9, 关联 #199
This commit is contained in:
2026-10-08 14:59:50 +08:00
parent e9c33dc4ea
commit 45d5bd1567
3 changed files with 84 additions and 2 deletions

View File

@@ -6,8 +6,9 @@
// BaseChart: ECharts 包装组件。
// #233 B1.5-2 新增 chartClick 事件 emit, 用于趋势图 markPoint/markLine 点击
// 弹事件详情;其他用法保持不变, 默认不监听 click。
// S-P1-3/B2-9: echarts 改为按需注册, 唯一入口 @/utils/echarts(不再引整包)。
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
import * as echarts from 'echarts'
import { init } from '@/utils/echarts'
const props = defineProps({
option: { type: Object, required: true },
@@ -21,7 +22,7 @@ let chart = null
function renderChart() {
if (!chartRef.value) return
if (!chart) {
chart = echarts.init(chartRef.value)
chart = init(chartRef.value)
// #233 绑定 click 事件一次 (避免 setOption 时重复绑定)
chart.on('click', (params) => {
// 只把 markPoint / markLine 类点击透出去 (componentType 区分)

49
web/src/utils/echarts.js Normal file
View File

@@ -0,0 +1,49 @@
// -*- coding: utf-8 -*-
/**
* ECharts 按需注册(S-P1-3 / B2-9)
*
* 设计说明(Design Rationale):
* 原先 BaseChart.vue 写 `import * as echarts from 'echarts'` 引整包,
* 地图/雷达/树图/仪表盘等全站从未使用的能力(~半数体积)全被打进 BaseChart chunk。
* 本模块作为全站唯一入口, 只注册实际用到的 charts/components/renderer:
* charts : bar / line / pie / heatmap
* components: title / tooltip / grid / legend / visualMap(热力图)
* markPoint / markLine / markArea(事件标注 #233/#234)
* renderer: Canvas
*
* 取舍:
* · 不引 SVGRenderer —— 全站默认 canvas, 无导出 SVG 需求;
* · 新增图表类型时必须在此登记, 否则 setOption 静默不渲染(刻意收敛)。
*/
import { use, init } from 'echarts/core'
import { BarChart, LineChart, PieChart, HeatmapChart } from 'echarts/charts'
import {
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
VisualMapComponent,
MarkPointComponent,
MarkLineComponent,
MarkAreaComponent,
} from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
use([
CanvasRenderer,
BarChart,
LineChart,
PieChart,
HeatmapChart,
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
VisualMapComponent,
MarkPointComponent,
MarkLineComponent,
MarkAreaComponent,
])
export { init }

View File

@@ -31,4 +31,36 @@ export default defineConfig({
},
},
},
build: {
rollupOptions: {
output: {
// S-P1-3/B2-9: 第三方依赖拆成独立缓存块, 业务改动不再使 vendor 缓存失效;
// 同时让 echarts 按需模块独立成块(不再挤进 BaseChart chunk)。
manualChunks(id) {
if (!id.includes('node_modules')) return undefined
// echarts 及其底层渲染库 zrender
if (id.includes('node_modules/echarts/') || id.includes('node_modules/zrender/')) {
return 'echarts'
}
// element-plus 组件库 + 官方图标
if (id.includes('node_modules/element-plus/') || id.includes('node_modules/@element-plus/')) {
return 'element-plus'
}
// vue 核心生态(vue/@vue/runtime 等、vue-router、pinia)
if (
id.includes('node_modules/vue/')
|| id.includes('node_modules/@vue/')
|| id.includes('node_modules/vue-router/')
|| id.includes('node_modules/pinia/')
) {
return 'vue-vendor'
}
// dayjs 全站广泛使用, 独立小块
if (id.includes('node_modules/dayjs/')) return 'dayjs'
// 其余第三方(axios/marked/dompurify 等)统一 vendor
return 'vendor'
},
},
},
},
})