Vite 中导入 UMD 模块的坑与解决方案------以 ECharts 为例
前言
在 Vite 项目中导入第三方库时,我们通常习惯直接使用 import 语句。但当遇到 UMD 格式的库文件(尤其是非 npm 安装的本地文件)时,可能会遇到各种奇怪的问题。本文以 ECharts 为例,记录我在 uni-app + Vite 项目中解决 UMD 模块导入问题的完整过程。当然,如果第三方能够提供esModule格式的esm.js格式的文件时,可以直接使用import引入使用。
问题背景
项目需要将 ECharts 从主包迁移到分包以优化小程序体积。由于分包无法访问主包的 node_modules,我们选择将 UMD 格式的 echarts.min.js(约 540KB)直接放入分包目录中。
然而,当在 Vue 组件中导入这个本地 UMD 文件时,问题出现了:
javascript
// 尝试 1:命名空间导入
import * as echarts from '../utils/echarts.min.js'
// 运行时错误:echarts.graphic 是 undefined
// 构建产物中出现:new (void 0).LinearGradient(...)
javascript
// 尝试 2:默认导入
import echarts from '../utils/echarts.min.js'
// 构建报错:"default" is not exported by echarts.min.js
根因分析
UMD 模块的结构
UMD(Universal Module Definition)格式的代码通常长这样:
javascript
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['exports'], factory);
} else if (typeof exports === 'object') {
// CommonJS
factory(exports);
} else {
// 全局变量
factory((root.echarts = {}));
}
}(this, function (exports) {
// ECharts 代码...
exports.init = function() { ... };
exports.graphic = { LinearGradient: ... };
}));
Vite/Rollup 的处理方式
Vite 使用 Rollup 进行构建。对于 ESM 模块,Rollup 可以静态分析出所有命名导出。但 UMD 模块的动态导出模式让 Rollup 无法在编译时确定导出了哪些成员。
import * as echarts:Rollup 创建命名空间对象,但由于无法静态分析 UMD 的导出,echarts.graphic等属性在 tree-shaking 时被优化为undefinedimport echarts from:UMD 没有显式的default导出,Rollup 直接报错
解决方案
方案一:自定义 Vite 插件(推荐)
最简洁的方案是编写一个 Vite 插件,在模块转换阶段为 UMD 文件注入 export default:
javascript
// vite.config.js
export default defineConfig({
plugins: [
// 自定义插件:为 UMD 模块注入 ESM default export
{
name: 'umd-esm-default',
transform(code, id) {
// 匹配目标 UMD 文件
if (id.includes('echarts.min.js') && !id.includes('node_modules')) {
// 在文件末尾追加 ESM 默认导出
return code + '\nexport default exports;'
}
}
},
// 其他插件...
]
})
原理 :UMD 模块在执行时会将导出挂载到 exports 对象上。我们在文件末尾追加 export default exports,让 Rollup 能够识别默认导出。
使用方式:
javascript
// 组件中直接导入
import echarts from '../utils/echarts.min.js'
// 正常使用
echarts.init(dom)
new echarts.graphic.LinearGradient(...)
方案二:ESM Wrapper + CommonJS 配置
如果不想写自定义插件,也可以通过配置 @rollup/plugin-commonjs 来处理:
javascript
// 1. 创建 wrapper 文件 echarts-wrapper.js
const echarts = require('./echarts.min.js')
module.exports = echarts
// 2. vite.config.js 配置 commonjs 插件
export default defineConfig({
build: {
commonjsOptions: {
// 注意:必须保留 node_modules 的处理,否则会破坏其他依赖
include: [/node_modules\//, /echarts\.min\.js/, /echarts-wrapper\.js/],
transformMixedEsModules: true
}
}
})
注意事项 :commonjsOptions.include 会覆盖默认配置,必须显式包含 /node_modules\//,否则 dayjs 等依赖的 CommonJS 模块处理会被破坏。
⚠️ 此方案的局限性:在微信小程序等跨分包场景中,可能导致主包与分包之间的循环引用问题。
方案三:使用 ESM 版本的库
如果库提供了 ESM 版本,直接使用是最省心的:
javascript
// 使用 ESM 版本
import * as echarts from 'echarts/dist/echarts.esm.min.js'
但 ESM 版本通常体积更大(ECharts ESM 版约 1.7MB vs UMD 版约 540KB),且不是所有库都提供 ESM 格式。
方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 自定义插件 | 代码简洁、无侵入性 | 需要了解 UMD 内部结构 | 本地 UMD 文件 |
| CommonJS 配置 | 无需写插件 | 配置复杂、有跨分包风险 | 简单项目 |
| ESM 版本 | 原生支持、Tree-shaking | 体积大、不是所有库都有 | 对体积不敏感的场景 |
完整代码示例
javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue(),
// UMD 模块 ESM 化插件
{
name: 'umd-esm-default',
transform(code, id) {
const umdFiles = ['echarts.min.js', 'other-umd-lib.js']
if (umdFiles.some(file => id.endsWith(file))) {
return {
code: code + '\nexport default exports;',
map: null
}
}
}
}
]
})
vue
<!-- Component.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import echarts from '@/utils/echarts.min.js'
const chartRef = ref(null)
onMounted(() => {
const chart = echarts.init(chartRef.value)
chart.setOption({
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
yAxis: { type: 'value' },
series: [{ data: [150, 230, 220], type: 'bar' }]
})
})
</script>
踩坑记录
坑 1:import * as 的陷阱
使用命名空间导入时,构建不会报错,但运行时属性为 undefined。这是因为 Rollup 在 tree-shaking 阶段将未识别的命名空间属性优化掉了。
坑 2:CommonJS 配置的副作用
配置 commonjsOptions.include 时如果只写目标文件路径,会覆盖默认的 node_modules 处理规则,导致 dayjs 等库构建失败。
坑 3:微信小程序跨分包引用
使用 CommonJS wrapper 方案时,echarts 代码可能被打包到主包 vendor.js,导致分包组件引用时出现跨分包引用错误。
总结
在 Vite 项目中处理本地 UMD 模块时,自定义 transform 插件是最稳妥的方案:
- 代码侵入性小,只需在 vite.config.js 中添加几行配置
- 不依赖 CommonJS 插件的全局配置,避免副作用
- 构建产物正确,运行时行为符合预期
如果你的项目也遇到了 UMD 模块导入的问题,不妨试试这个方案。
参考链接: