1. 为什么uniapp+vue3+ts+vite+echarts组合会踩坑?
最近在做一个数据可视化项目时,我选择了uniapp+vue3+ts+vite+echarts这套技术栈。本以为能愉快地开发,结果在引入echarts时遇到了各种模块规范冲突的问题,整整折腾了一天。后来发现这其实是Vite的ESM模块规范与微信小程序需要的CJS模块规范不兼容导致的。
简单来说,Vite默认使用ESM(ECMAScript Modules)规范,而微信小程序环境只支持CJS(CommonJS)规范。当我们在uniapp项目中通过Vite引入echarts时,就会遇到模块导入方式不兼容的问题。这就像你带着美元去欧洲消费,虽然都是钱,但商家只收欧元。
2. 两种主流解决方案对比
2.1 本地ESM/打包CJS双文件切换方案
这是我最初尝试的方案,原理很简单:开发时使用ESM规范的echarts,打包时切换为CJS规范的echarts。
具体操作步骤:
-
在项目中创建两个echarts文件:
-
echarts.esm.js(开发环境使用) -
echarts.common.min.js(生产环境使用)
-
-
在代码中通过条件编译区分环境:
// #ifdef MP
const echarts = require('../../static/echarts.common.min');
// #endif
// #ifndef MP
import * as echarts from 'echarts';
// #endif
这个方案的优点是实现简单,但缺点也很明显:
- 需要维护两份代码
- 开发环境和生产环境行为不一致可能导致bug
- 每次更新echarts版本都需要手动更新两个文件
2.2 使用Vite构建将ESM转为CJS
经过多次尝试,我发现更优雅的解决方案是利用Vite的构建能力,自动将ESM转为CJS。下面是具体实现方式:
- 首先安装必要的依赖:
npm install echarts @vitejs/plugin-legacy --save-dev
- 配置vite.config.ts:
import { defineConfig } from 'vite'
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
legacy({
targets: ['defaults', 'not IE 11'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime']
})
],
build: {
rollupOptions: {
external: ['echarts'],
output: {
format: 'cjs'
}
}
}
})
这个方案的优势在于:
- 开发和生产环境使用同一套代码
- 自动转换模块规范,无需手动维护
- 更符合现代前端工程化实践
3. 完整实现步骤与代码示例
3.1 项目初始化与依赖安装
首先创建一个uniapp+vue3+ts+vite项目:
npm init vite@latest my-project --template vue-ts
cd my-project
npm install
npm install -g @dcloudio/uni-cli
uni init
然后安装echarts和相关依赖:
npm install echarts @types/echarts --save
3.2 创建图表组件
在components目录下创建EChart.vue组件:
<template>
<view class="chart-container">
<canvas :id="canvasId" :canvas-id="canvasId" class="chart-canvas"></canvas>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import * as echarts from 'echarts'
import { onReady } from '@dcloudio/uni-app'
const props = defineProps({
options: {
type: Object,
required: true
},
canvasId: {
type: String,
default: 'chart'
}
})
const chartInstance = ref<echarts.ECharts | null>(null)
const initChart = () => {
const query = uni.createSelectorQuery()
query.select(`#${props.canvasId}`)
.fields({ node: true, size: true })
.exec((res) => {
if (!res[0]) return
const canvas = res[0].node
const ctx = canvas.getContext('2d')
chartInstance.value = echarts.init(canvas, null, {
renderer: 'canvas',
devicePixelRatio: uni.getSystemInfoSync().pixelRatio
})
chartInstance.value.setOption(props.options)
})
}
onMounted(() => {
// #ifndef MP
initChart()
// #endif
})
onReady(() => {
// #ifdef MP
initChart()
// #endif
})
watch(() => props.options, (newVal) => {
if (chartInstance.value) {
chartInstance.value.setOption(newVal)
}
}, { deep: true })
</script>
<style scoped>
.chart-container {
width: 100%;
height: 300px;
}
.chart-canvas {
width: 100%;
height: 100%;
}
</style>
3.3 在页面中使用图表组件
<template>
<view class="container">
<EChart :options="chartOptions" canvas-id="myChart" />
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import EChart from '@/components/EChart.vue'
const chartOptions = ref({
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar'
}]
})
</script>
<style>
.container {
padding: 20px;
}
</style>
4. 常见问题与解决方案
4.1 图表不显示或大小异常
这个问题通常是由于canvas元素没有正确获取到容器尺寸导致的。解决方案:
- 确保容器有明确的宽高
- 在图表初始化时手动指定尺寸:
const initChart = () => {
const query = uni.createSelectorQuery()
query.select(`#${props.canvasId}`)
.boundingClientRect()
.exec((res) => {
if (!res[0]) return
const { width, height } = res[0]
const canvas = res[0].node
chartInstance.value = echarts.init(canvas, null, {
width,
height,
renderer: 'canvas',
devicePixelRatio: uni.getSystemInfoSync().pixelRatio
})
chartInstance.value.setOption(props.options)
})
}
4.2 微信小程序中报错"echarts is not defined"
这是因为微信小程序环境没有正确加载echarts。解决方案:
- 确保已经按照前面的配置正确区分了开发和生产环境
- 检查echarts文件路径是否正确
- 可以尝试使用微信小程序专用的echarts版本:
npm install echarts-for-weixin --save
然后在代码中引入:
// #ifdef MP
const echarts = require('echarts-for-weixin')
// #endif
4.3 性能优化建议
当图表数据量较大时,可能会遇到性能问题。以下是一些优化建议:
- 使用懒加载:只在图表进入可视区域时初始化
- 合理使用动画:对于大数据量图表,可以关闭动画
- 使用数据采样:展示大量数据时,可以进行适当采样
- 及时销毁实例:在组件卸载时销毁echarts实例
import { onUnmounted } from 'vue'
onUnmounted(() => {
if (chartInstance.value) {
chartInstance.value.dispose()
chartInstance.value = null
}
})
5. 高级用法与扩展
5.1 动态主题切换
echarts支持动态切换主题,我们可以利用这个特性实现白天/黑夜模式:
// 定义主题
const lightTheme = {
backgroundColor: '#ffffff',
textStyle: {
color: '#333'
}
// 其他样式配置...
}
const darkTheme = {
backgroundColor: '#1a1a1a',
textStyle: {
color: '#fff'
}
// 其他样式配置...
}
// 注册主题
echarts.registerTheme('light', lightTheme)
echarts.registerTheme('dark', darkTheme)
// 使用主题
chartInstance.value = echarts.init(canvas, 'light', {
// 其他配置...
})
5.2 图表联动
多个图表之间可以实现联动效果:
// 初始化两个图表实例
const chart1 = echarts.init(canvas1)
const chart2 = echarts.init(canvas2)
// 设置联动
echarts.connect([chart1, chart2])
5.3 自定义系列
对于特殊需求,我们可以自定义echarts系列:
// 注册自定义系列
echarts.registerChartSeriesType('custom', {
// 系列定义...
})
// 使用自定义系列
chartInstance.value.setOption({
series: [{
type: 'custom',
// 其他配置...
}]
})
6. 最佳实践总结
经过这次项目实战,我总结了以下几点最佳实践:
- 模块规范统一 :始终明确你的运行环境需要的模块规范,开发环境和生产环境尽量保持一致
- 按需引入 :echarts支持按需引入,可以显著减小打包体积
- 组件封装 :将图表封装成可复用的组件,提高代码复用率
- 性能监控 :在开发过程中注意性能指标,及时优化
- 多端适配 :充分考虑不同平台的特性,做好兼容性处理
对于大型项目,建议将图表相关的配置、工具函数等抽离成独立的模块,保持业务代码的整洁。同时,可以考虑使用一些成熟的uniapp图表组件库,如lime-echart等,它们已经处理好了大部分兼容性问题。

2万+

被折叠的 条评论
为什么被折叠?



