组件库拆分与重用:el-admin-components 抽离
概述
真正把模板项目里的业务组件抽离成独立组件库,第一步不是「复制文件」,而是先决定组件库对 Element Plus 的依赖策略——这个决定会一路影响是否 external element-plus、是否把样式打进产物、包体积大小和能否脱离 admin 模板单独复用。本文以 el-admin-components 为例,讲清楚抽离工程里的两条并存的链路(库模式负责产物交付、Playground 负责本地验证)、npm link 联调的坑、包名/构建文件名/exports/类型声明必须对齐,以及迁移组件时最容易漏掉的样式系统与隐形前置依赖。
学习目标
- 在抽离前先决定 Element Plus 依赖策略(消费方全局注入 vs 库内自带),而不是先复制组件
- 理解 Playground 是组件库的第一个真实消费方,比直接发包更能暴露问题
- 掌握
npm link链接的是package.json.name而非目录名这一关键事实 - 保证包名、构建文件名、
exports与类型声明路径四者一致,避免「能运行但类型失效」 - 迁移组件时把样式系统、预处理器、Vue 宏、Auto Import 等隐形环境依赖一并迁过来
一、抽离前先决定 Element Plus 依赖策略
课程一开始就抛出两种使用方式:
- 方案 A:消费方全局使用 Element Plus(通过 CDN 或
app.use(ElementPlus)注入),组件库只依赖消费方已有的环境; - 方案 B:组件库内部自行带上 Element Plus 的组件和样式,更独立,但对非 admin 模板更友好,代价是包体积更大、升级更被动。
这两个方案没有绝对对错,但它们影响的东西完全不同:是否 external element-plus、是否需要把样式一并打包、还能不能方便走 CDN external、组件库是否强绑定某套 UI 生态。课程这里先实现「从 0 到 1」,优化问题留到后面,这个节奏是对的。
二、库模式与 Playground 是两条并存的链路
这套组件库工程通常同时包含三类目录:src/(组件源码)、playground/(本地调试与验证入口)、dist/(最终构建产物)。库模式由 build.lib、exports、类型声明等配置决定,用于构建可发布产物;Playground 是一个真实的消费方实验场。
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
name: 'ElAdminComponents',
fileName: 'el-admin-components',
},
rollupOptions: {
external: ['vue'],
},
},
})Playground 不只是「随便放个页面」,它本质上是组件库的第一个真实消费方。课程后面大多数排错,其实都是通过 Playground 暴露出来的。
三、npm link 链接的是 package.json.name
课程的本地联调路径是:在组件库根目录执行 npm link,再在 playground 里执行 npm link 包名。npm 官方文档明确:npm link 是两步过程,第二步使用的包名来自 package.json.name,不是目录名。这也解释了课程里的报错——目录名改了、包名改了,但 exports、构建文件名、Playground 导入名没完全同步,就会出现「模块能看到,但声明文件或导出成员对不上」。
# 组件库项目
npm link
# playground 项目
npm link el-admin-componentsnpm link 默认不会把链接依赖写进 package.json。改了 package.json.name 后,所有基于包名的联调入口都要一起改。「目录名对了但仍然找不到模块」时,先检查包名,而不是先怀疑 Vite。
四、包名、构建文件名、exports、类型声明必须对齐
课程中反复碰到的问题本质上都指向一点:组件库构建产物有名字,package.json 暴露入口有名字,Playground 里导入的还是那个名字。这几处不一致时,就会出现「模块路径能解析、运行没问题,但 TS 报找不到声明文件或没有导出某个成员」的半联通状态。
{
"name": "el-admin-components",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/el-admin-components.js",
"require": "./dist/el-admin-components.umd.cjs"
}
}
}类型声明能否被消费方识别,取决于 types / exports.types 是否对得上。课程里「重启扩展宿主 / 重新 install / link 后恢复正常」的根因,其实是入口和声明链路重新对齐了。
五、迁移组件最容易漏的是样式系统与隐形前置
把业务组件拷进 components 项目后,马上就会遇到依赖缺失。迁移的真实难点在于:你复制过来的不是一个纯 .vue 文件,它往往依赖 UnoCSS 原子类、UnoCSS reset、Sass、Vue 宏语法实验特性、Auto Import 约定、Element Plus 基础组件。正确姿势不是「复制后直接 build」,而是缺什么补什么,并且想清楚这个依赖是组件库需要还是 Playground 需要。
UnoCSS 在 Vite 组件库里要分成两段接入:构建期插 UnoCSS(),运行期入口导入 virtual:uno.css,如需 Tailwind 风格 reset 再导入 @unocss/reset/tailwind.css。
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import UnoCSS from 'unocss/vite'
export default defineConfig({
plugins: [
vue({
script: {
defineModel: true,
propsDestructure: true,
},
}),
UnoCSS(),
],
})// src/main.ts
import 'virtual:uno.css'
import '@unocss/reset/tailwind.css'只复制 uno.config.ts 不等于 UnoCSS 已接通。Vue 宏语法(defineModel、props 解构)、computed 等自动导入、.scss 依赖都属于「隐形前置条件」,不补齐就会构建成功但体验不完整。sass 是明确的构建依赖,缺了会直接构建失败。
六、消费方全局注册 Element Plus 验证链路
如果组件库选「消费方全局使用 Element Plus」,那么 Playground 就必须先把 Element Plus 当成真实外部依赖装起来并全局注册。课程最后头像组件没正常渲染,原因不是组件逻辑错,而是它依赖的 ElAvatar 没有可用的 Element Plus 运行环境——这正好验证了前面的架构题。
// playground/src/main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
createApp(App).use(ElementPlus).mount('#app')组件库项目和 Playground 项目是两套依赖语境,不要混成一个。「组件构建成功但页面空白 / 基础组件不渲染」时,优先检查 Playground 是否补齐了消费方依赖。npm link 联调时,构建产物更新、声明文件更新和 Playground 依赖刷新并不总是同步,经常需要重新安装或重新链接并重启编辑器 TS 服务。
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
npm link 后 Playground 仍找不到包 | 用了目录名而不是 package.json.name | 检查组件库 name 字段,按包名重新 link |
| 模块能导入但 TS 提示找不到声明文件 | types / exports.types 与产物路径不一致或旧缓存未刷新 | 对齐 exports、重新构建、重新安装 / link 并刷新 TS 服务 |
| 业务组件迁移后样式全乱 | 没补 UnoCSS 插件、入口样式或 reset | 配置 UnoCSS(),入口导入 virtual:uno.css 与对应 reset |
| 构建报 Sass 相关错误 | 组件依赖 scss / sass 但新项目没装 | 安装 sass 并检查样式依赖链 |
| 头像组件只看到占位不显示 Element Plus 效果 | Playground 没全局安装 Element Plus | 在 Playground 安装 element-plus 并 app.use(ElementPlus) |
| 改了包名后 Playground 又报模块不存在 | 构建文件名、exports、导入名未同步 | 统一包名、产物名、入口映射和导入名 |
| 组件迁移过去后缺什么爆什么 | 迁的是「组件 + 工程环境」而非 .vue 文件 | 逐项补齐宏语法、Auto Import、样式系统、预处理器和三方库依赖 |