Vue3组件库架构设计与Monorepo工程搭建
Vue3生态下组件库设计需要兼顾主题定制灵活性和按需加载性能。采用Monorepo架构管理多个组件包,pnpm workspace作为基础方案:
# 项目结构
my-ui/
├── packages/
│ ├── components/ # 组件源码
│ ├── theme/ # 主题样式
│ ├── utils/ # 工具函数
│ └── icons/ # 图标组件
├── playground/ # 开发调试
├── docs/ # 文档站
├── pnpm-workspace.yaml
└── package.json
pnpm-workspace.yaml配置:
packages:
- 'packages/*'
- 'playground'
- 'docs'
根package.json统一管理TypeScript、Vite、ESLint等开发依赖,各子包仅声明自身运行时依赖。这种结构让组件、主题、工具独立版本管理,用户可按需安装子集。
组件开发规范与TypeScript类型设计
组件开发遵循统一规范:Props用TypeScript接口定义,事件用emits声明,暴露ref方法通过defineExpose。以Button组件为例:
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useNamespace } from '@my-ui/utils'
import { buttonProps, buttonEmits } from './props'
const props = defineProps(buttonProps)
const emit = defineEmits(buttonEmits)
const ns = useNamespace('button')
const buttonRef = ref<HTMLButtonElement>()
const classes = computed(() => ({
[ns.b()]: true,
[ns.m(props.type)]: props.type,
[ns.m(props.size)]: props.size,
[ns.is('disabled', props.disabled)]: props.disabled,
[ns.is('loading', props.loading)]: props.loading,
}))
defineExpose({ ref: buttonRef })
</script>
props.ts独立文件定义类型:
import type { PropType } from 'vue'
export type ButtonType = 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'default'
export type ButtonSize = 'large' | 'default' | 'small'
export const buttonProps = {
type: { type: String as PropType<ButtonType>, default: 'default' },
size: { type: String as PropType<ButtonSize>, default: 'default' },
disabled: Boolean,
loading: Boolean,
icon: String,
} as const
export const buttonEmits = {
click: (e: MouseEvent) => e instanceof MouseEvent,
}
TypeScript实战中,类型定义独立于组件逻辑,方便后续自动生成API文档和类型声明文件。
CSS变量驱动的主题定制系统
主题定制是组件库的核心竞争力。基于CSS变量的方案运行时可动态切换,无需重新构建样式文件:
/* packages/theme/src/default.css */
:root {
--my-color-primary: #409eff;
--my-color-success: #67c23a;
--my-color-warning: #e6a23c;
--my-color-danger: #f56c6c;
--my-color-info: #909399;
--my-bg-color: #ffffff;
--my-text-color-primary: #303133;
--my-border-radius: 4px;
--my-font-size: 14px;
}
组件样式引用CSS变量:
/* packages/theme/src/button.css */
.my-button {
display: inline-flex;
align-items: center;
padding: 8px 16px;
font-size: var(--my-font-size);
border-radius: var(--my-border-radius);
color: var(--my-text-color-primary);
background-color: var(--my-bg-color);
border: 1px solid var(--my-border-color);
transition: all 0.3s;
}
.my-button--primary {
color: #fff;
background-color: var(--my-color-primary);
border-color: var(--my-color-primary);
}
用户切换暗色主题只需覆盖变量:
document.documentElement.style.setProperty('--my-bg-color', '#1a1a2e')
document.documentElement.style.setProperty('--my-text-color-primary', '#e0e0e0')
按需加载与Tree-shaking实现方案
组件库按需加载有两套主流方案:ES Module原生Tree-shaking和unplugin-vue-components自动导入。
方案1:ES Module Tree-shaking
每个组件独立导出,配合sideEffects配置:
// packages/components/index.ts
export { Button } from './button'
export { Input } from './input'
export { Select } from './select'
// 用户按需导入
import { Button } from '@my-ui/components'
package.json标记sideEffects避免样式被Tree-shake:
// packages/components/package.json
{
"sideEffects": ["*.css", "*.scss"]
}
方案2:unplugin自动导入
// vite.config.ts
import Components from 'unplugin-vue-components/vite'
import { MyUIResolver } from '@my-ui/auto-import'
export default defineConfig({
plugins: [
Components({
resolvers: [MyUIResolver()],
}),
],
})
自定义Resolver实现:
export function MyUIResolver() {
return {
type: 'component',
resolve: (name: string) => {
if (name.startsWith('My')) {
const partialName = name.slice(2) // MyButton -> Button
return {
name: partialName,
from: '@my-ui/components',
sideEffects: `@my-ui/theme/src/${partialName.toLowerCase()}.css`,
}
}
},
}
}
自动导入方案降低用户心智负担,模板中直接使用<MyButton>即可,无需手动import。两种方案生产包体积差异实测:全量引入Button/Input/Select三个组件约45KB(gzip),按需引入仅12KB,缩减73%。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-jian-ku-she-ji-cong-ling-da-jian-zhi-chi-zhu-ti/