共享 Vue 组件如何避免重复打包运行时
聚焦组件库与宿主的运行时依赖边界:external 和 peerDependencies 各负责什么,ES 与 UMD 怎样找到外部依赖,以及如何验证没有悄悄多打一份 Vue。
- Vue 3
- Vite
- 组件库
- 工程化
关联项目:跨系统共享业务组件库。本文只讨论运行时依赖边界,构建示例对应项目使用的 Vite 4 方式。
组件能跑,与宿主能接,是两次验收
反馈和支付二维码这类业务组件会被多个系统复用。在组件库自己的开发页面中运行正常,只能说明本地环境满足了依赖;宿主可能使用另一套 Vue 版本、另一种资源加载方式,甚至没有加载组件所需样式。
因此,一个可交付组件至少包含:可导入的入口、运行时依赖约定、Props 和事件契约、样式资源,以及明确的构建版本。
external 解决依赖边界,不会自动加载依赖
项目的构建配置把 Vue 和 ant-design-vue 外置,并支持按组件入口生成产物。一个去除业务路径的配置示意如下:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: 'src/entry.ts',
name: 'BusinessWidgets',
formats: ['es', 'umd'],
fileName: format => `widgets.${format}.js`,
},
rollupOptions: {
external: ['vue', 'ant-design-vue'],
output: {
globals: {
vue: 'Vue',
'ant-design-vue': 'antd',
},
},
},
},
})
external 表示这些模块不打进当前库;UMD 的 globals 则告诉产物从哪些宿主全局对象取依赖。相关配置与格式约定见 Vite 4 Library Mode。
需要区别两种消费方式:
| 产物 | 宿主需要处理的事 |
|---|---|
| ES 模块 | 解析外部导入;经宿主构建工具处理,或配置可用的浏览器模块映射 |
| UMD | 在库执行前提供正确的全局依赖对象,并确认导出的全局名称 |
仅把 Vue 声明为 peerDependencies,不会在浏览器中凭空出现 window.Vue。同样,直接 import() 一个仍包含裸模块名的远程 ES 文件,也不保证浏览器可以解析依赖。
不要把所有包都机械地 external
是否外置需要看宿主实际能否提供,以及是否需要共享运行时实例。项目当前配置外置的是 Vue 与 ant-design-vue,不能因为包里出现 router、i18n,就把它们都描述为已外置。
基础运行时重复打包可能造成额外体积与跨实例集成问题,但也不能简单断言“两份 Vue 必然在所有情况下失效”。可验证的要求是:明确支持的版本范围,并在真实宿主中检查组件实例、插件、注入和样式行为。
怎样确认依赖真的来自宿主
开发页成功运行,可能是因为其他脚本恰好先注入了依赖。验证时应建立最小宿主,明确提供 Vue 与 UI 库,再加载当前组件。
检查产物中的外部导入与 UMD 全局参数,配合构建分析确认库中没有额外嵌入预期外置的运行时。再核对插件安装位置、跨组件的 provide/inject 和卸载行为,避免只验证一个静态按钮。
依赖版本也属于契约:声明支持范围,并在代表性的宿主版本上做集成验证。修改声明不会自动让不兼容的 API 获得兼容性。
两种常见误解
“已经 external,就能直接远程加载。” external 只是把解析责任交出去,宿主仍要为裸模块名或全局对象提供实际来源。
“所有依赖都外置,包就最合理。” 包体积只是一个因素。宿主能否提供依赖、是否需要共享实例、组件是否要求特定版本,都影响这个决定。
样式发布、缓存和版本回滚也是组件交付问题,但它们不属于本文的运行时依赖难点,应该独立设计和验证。