组件的构建
当组件在项目里已经跑得不错,下一步通常就是把它提炼成一个可复用、可发布的包。
这时“构建”就开始变得重要。
因为你不再只是关心“当前项目能不能跑”,而是要回答这些问题:
- 别人的项目能不能稳定消费你的组件
- 你的组件要以什么格式输出
- 类型、样式和运行时依赖要怎样一起交付
- 你的包是否会把不该暴露的实现细节泄漏出去
所以组件构建的本质,不只是“把代码打包一下”,而是把组件从项目内部实现,转换成可分发的公共产物。
为什么要构建
在单个项目里直接复制组件当然也能用,但这会带来明显问题:
- 组件更新要反复复制
- 不同项目会逐渐产生分叉
- 类型、样式、依赖版本更难统一
把组件提炼成单独的 npm 包之后,分发和升级才会有稳定入口。
能不能直接发布 .vue 源码
答案是:技术上可以,工程上通常不推荐。
为什么“可以”
从技术角度看,npm 并不限制你上传什么文件。你完全可以发布原始的 .vue 文件,或者包含 JSX 的 .tsx/.jsx 文件。
只要使用者项目里已经配置好了相应的编译支持,例如:
@vitejs/plugin-vue@vitejs/plugin-vue-jsxvue-loader
那么这些文件理论上就可以被继续编译和运行。
所以“能不能发布源码”这个问题,单从技术可行性上说,答案是能。
为什么“不推荐”
问题出在“能跑”和“适合作为公共包分发”之间差得很远。
1. 过度依赖使用方的编译环境
一旦你发布 .vue 源码,使用者项目就必须具备兼容的编译器版本和配置。
例如你在源码里用了 vue@3.3+ 才支持的 defineOptions,而使用者的编译环境停留在更低版本,就会直接出问题。
2. 构建成本转嫁给下游
每个使用你组件库的项目,都要重新编译这些源码。
这会增加下游构建时间,也会放大不同工具链之间的差异。
3. 类型交付不完整
如果你不额外处理类型声明,使用者往往拿不到完整、稳定的 .d.ts 支持。
4. 公共 API 边界变模糊
发布源码通常会把内部目录结构也一起暴露出去,使用者更容易误 import 你不打算公开维护的内部模块。
5. Node / SSR / 测试环境兼容性更差
不是所有环境都会原生处理 .vue 文件。
源码发布在服务端、测试工具、老旧构建链路里更容易踩坑。
“预编译”解决了什么
把组件预编译成 .js / .css / .d.ts 后,本质上是在提前帮下游完成这些事:
- SFC 解析
- 模板编译
- 样式提取
- 类型声明生成
这能显著降低消费者的环境要求,也能让你的包边界更清晰。
但要注意:
预编译并不等于自动兼容所有低版本 Vue。
最终能不能运行,仍然取决于:
- 运行时依赖是否兼容
- helper 是否存在
- 输出格式是否与使用方环境匹配
例如某些新宏虽然被编译掉了,但若产物依赖较新的运行时 helper,低版本 Vue 运行时依然可能不兼容。
开始构建
在打包 Vue 组件库时,优先推荐使用 Vite 的库模式。
这是当前 Vue 生态里相对自然、维护成本较低的一条路线。
快速示例见本项目的
packages/ice-ui目录
基础配置长什么样
一个通用的库模式配置大致如下:
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vite'
const __dirname = dirname(fileURLToPath(import.meta.url))
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'lib/index.ts'),
name: 'ui',
fileName: 'index',
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue',
},
},
},
},
})这里最值得理解的两个点
1. lib.entry
这是你对外的库入口,决定最终产物从哪里开始收集模块依赖。
2. external: ['vue']
这非常重要。
因为组件库通常不应该把 vue 自己一并打进产物里,否则就会出现:
- 包体积膨胀
- 运行时重复实例
- 消费方和组件库各自持有不同 Vue 副本
所以大多数情况下,vue 应该作为外部依赖保留。
为什么只靠这段配置还不够
上面的配置只能很好地处理 ts / js 文件。
如果你的组件库包含 .vue 组件,Vite 还需要对应插件来理解 SFC。
Vue 3
如果组件库是 Vue 3 主线,通常需要:
@vitejs/plugin-vue- 如果用了 JSX,再加
@vitejs/plugin-vue-jsx
Vue 2
Vue 2 已进入 EOL,本节仅用于维护历史项目。
vite-plugin-vue2vite-plugin-vue2-jsx
构建产物通常长什么样
在 Vite 库模式下,常见输出会包括:
esm格式的 JavaScriptumd或兼容构建格式- 组件样式文件
- 类型声明文件
例如:
index.css
index.d.ts
index.js
index.umd.cjs这些文件分别解决不同问题:
index.js:现代构建工具和 ESM 消费index.umd.cjs:兼容 CommonJS / 某些传统环境index.css:样式交付index.d.ts:类型交付
类型声明为什么不能省
默认情况下,Vite 不会自动生成 .d.ts。
如果你想让组件库在 TypeScript 项目里有完整的智能提示和类型校验,通常需要额外使用 vite-plugin-dts。
常见配置如下:
plugins: [
dts({
tsconfigPath: './tsconfig.app.json',
entryRoot: './lib',
}),
]这里最重要的不是记配置,而是记住:
公共组件库的交付物不只有运行时代码,还包括类型契约。
构建阶段真正要保证什么
一个“能发布”的组件库构建,至少要保证这几件事:
- 消费方不需要理解你的内部源码结构
vue这类宿主依赖不会被错误打包进去- 样式可以被稳定引入
- 类型声明完整可用
- 输出格式能匹配主要使用场景
如果这些做不到,构建就还只是“能生成文件”,不算真正完成交付。
一句话理解
组件构建的目标,不是把源码机械压缩成几个文件,而是把组件变成一个对外稳定、可安装、可类型推断、可长期维护的公共接口。
下一步
构建完成之后,下一步就应该进入测试和发布:
这样整条组件库链路才算真正闭合。