微信小程序 ECharts 瘦身实战
ECharts 的 echarts.js 体积接近 1MB,直接放在主包里,很容易让主包超过 2MB。
普通分包适合按页面拆分,但主包页面不能直接同步使用分包中的组件。需要在页面中配置 componentPlaceholder,先渲染一个占位节点,等 ECharts 分包下载完成后再替换成真实组件。
目录结构
miniprogram/
├─ app.json
├─ pages/
│ └─ chart/
│ ├─ index.json
│ ├─ index.wxml
│ └─ index.scss
└─ subpackages/
└─ echarts/
├─ ec-canvas/
│ ├─ ec-canvas.js
│ ├─ ec-canvas.json
│ ├─ ec-canvas.wxml
│ ├─ ec-canvas.wxss
│ ├─ echarts.js
│ └─ wx-canvas.js
└─ components/
└─ echart/
├─ index.json
├─ index.ts
├─ index.wxml
└─ index.scss2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
echarts.js、ec-canvas 和包装组件都放在同一个分包中。主包页面只使用包装组件,不要再直接导入 ECharts。
声明 ECharts 分包
在 app.json 中增加一个只提供组件的分包:
{
"pages": [
"pages/chart/index"
],
"subPackages": [
{
"root": "subpackages/echarts",
"pages": []
}
],
"lazyCodeLoading": "requiredComponents"
}2
3
4
5
6
7
8
9
10
11
12
分包本身仍受单包 2MB 限制。如果完整 ECharts 构建超过限制,需要使用 ECharts 自定义构建,只保留项目实际用到的图表和组件。
在分包内包装 ec-canvas
subpackages/echarts/components/echart/index.json:
{
"component": true,
"styleIsolation": "apply-shared",
"usingComponents": {
"ec-canvas": "/subpackages/echarts/ec-canvas/ec-canvas"
}
}2
3
4
5
6
7
index.wxml:
<view class="chart-shell">
<ec-canvas
id="echart-canvas"
class="chart"
canvas-id="{{canvasId}}"
ec="{{ec}}"
/>
</view>2
3
4
5
6
7
8
index.ts:
import * as echarts from '../../ec-canvas/echarts';
import type { EChartsInstance } from '../../ec-canvas/echarts';
interface EChartsCanvas {
setChart(chart: EChartsInstance): void;
}
interface EcCanvasComponent {
init(
callback: (canvas: EChartsCanvas, width: number, height: number, devicePixelRatio: number) => EChartsInstance,
): void;
}
interface EChartData {
ec: {
lazyLoad: boolean;
};
}
type EChartProperties = Record<string, WechatMiniprogram.Component.AllProperty> & {
canvasId: {
type: StringConstructor;
value: string;
};
option: {
type: ObjectConstructor;
value: Record<string, unknown>;
};
};
type EChartMethods = Record<string, Function> & {
renderChart(): void;
};
const charts = new WeakMap<object, EChartsInstance>();
Component<EChartData, EChartProperties, EChartMethods, []>({
properties: {
canvasId: {
type: String,
value: 'echart',
},
option: {
type: Object,
value: {},
},
},
data: {
ec: {
lazyLoad: true,
},
},
observers: {
option(): void {
this.renderChart();
},
},
lifetimes: {
ready(): void {
this.renderChart();
},
detached(): void {
charts.get(this)?.dispose();
charts.delete(this);
},
},
methods: {
renderChart(): void {
const option = this.properties.option;
if (Object.keys(option).length === 0) return;
const chart = charts.get(this);
if (chart) {
chart.setOption(option, true);
return;
}
const canvasComponent = this.selectComponent('#echart-canvas') as unknown as EcCanvasComponent | null;
if (!canvasComponent) return;
canvasComponent.init((canvas, width, height, devicePixelRatio) => {
const newChart = echarts.init(canvas, null, { width, height, devicePixelRatio });
charts.set(this, newChart);
canvas.setChart(newChart);
newChart.setOption(this.properties.option, true);
return newChart;
});
},
},
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
这里使用 lazyLoad,由包装组件在 ready 后调用 ec-canvas.init。option 为空时不初始化,数据先于分包加载完成时,真实组件创建后会读取最新的 option。
实例保存在 WeakMap 中,每个组件实例分别持有自己的 ECharts 实例。组件销毁时调用 dispose(),同一页面放置多个图表也不会共享实例。
组件自身的 index.scss 负责把 Canvas 高度传递到宿主尺寸:
:host,
.chart-shell,
.chart {
display: block;
height: 100%;
width: 100%;
}2
3
4
5
6
7
主包页面配置 componentPlaceholder
pages/chart/index.json:
{
"usingComponents": {
"echart": "/subpackages/echarts/components/echart/index"
},
"componentPlaceholder": {
"echart": "view"
}
}2
3
4
5
6
7
8
usingComponents 和 componentPlaceholder 中的组件名必须一致。
页面首次渲染时,如果 ECharts 分包还没有下载,基础库会先把 <echart> 当作 view 渲染。分包可用后,占位 view 会被真实组件自动替换。
页面使用
业务页面只负责生成 ECharts option 并传给组件:
<view class="chart-frame">
<echart
class="chart"
canvas-id="history-chart"
option="{{chartOption}}"
/>
</view>2
3
4
5
6
7
index.scss:
.chart-frame {
display: block;
width: 100%;
height: 520rpx;
}
.chart {
display: block;
width: 100%;
height: 100%;
}2
3
4
5
6
7
8
9
10
11
外层必须提前设置高度。否则占位节点没有尺寸,真实组件替换后,ECharts 初始化得到的 Canvas 高度也可能是 0。
实际加载过程如下:
主包页面开始渲染
→ 使用 view 代替 echart
→ 异步下载 ECharts 分包
→ 真实组件替换占位 view
→ 组件 ready
→ ec-canvas.init
→ echarts.init
→ setOption2
3
4
5
6
7
8
开发者工具中可以通过“代码依赖分析”检查结果:echarts.js 应只存在于 subpackages/echarts,主包中不应再有一份副本。
componentPlaceholder 需要基础库 2.11.2 及以上版本。发布前还需要清除开发者工具缓存,模拟首次进入页面,检查占位切换、Canvas 尺寸和真机渲染是否正常。
