现代组件库的基石是稳健的工程架构。我们采用PNPM Workspaces搭建Monorepo结构,实现模块化开发与高效管理:
packages/
├─ components/ # 组件源码
├─ theme/ # 主题引擎
├─ docs/ # 文档系统
├─ cli/ # 脚手架工具
└─ playground/ # 开发调试环境
// pnpm-workspace.yaml
packages:
- 'packages/*'
- 'internal/*'
- 'scripts'
# 全局安装公共依赖
pnpm add -wD typescript vite @types/node
# 模块间添加依赖
pnpm add @your-lib/components -r --filter @your-lib/docs
优势对比:
方案 | 安装速度 | 磁盘空间 | 依赖一致性 |
---|---|---|---|
Multirepo | 慢 | 高 | 差 |
Pnpm Workspace | 快30% | 节省40% | 强 |
// Button组件接口设计
export interface ButtonProps {
/**
* 按钮类型
* @default 'primary'
*/
type?: 'primary' | 'ghost' | 'text'
/**
* 禁用状态
* @default false
*/
disabled?: boolean
/**
* 点击事件处理器
*/
onClick?: (e: MouseEvent) => void
}
// 实现代码
const Button = defineComponent({
props: {
type: { type: String, default: 'primary' },
disabled: Boolean,
onClick: Function
},
setup(props) {
const theme = useTheme() // 主题注入
return () => (
<button
class={[
'btn',
`btn-${props.type}`,
theme.value === 'dark' && 'btn-dark'
]}
disabled={props.disabled}
onClick={props.onClick}
>
<slot />
</button>
)
}
})
// Button.spec.ts
import { mount } from '@vue/test-utils'
import Button from '../src/Button.vue'
describe('Button Component', () => {
it('触发点击事件', async () => {
const onClick = vi.fn()
const wrapper = mount(Button, {
props: { onClick }
})
await wrapper.trigger('click')
expect(onClick).toHaveBeenCalledTimes(1)
})
it('禁用状态下不触发事件', async () => {
const onClick = vi.fn()
const wrapper = mount(Button, {
props: { disabled: true, onClick }
})
await wrapper.trigger('click')
expect(onClick).toHaveBeenCalledTimes(0)
})
})
// 基础变量定义
:root {
--primary-color: #1890ff;
--text-color: rgba(0, 0, 0, 0.85);
--border-radius: 4px;
}
// 组件级变量
.btn {
background: var(--primary-color);
color: var(--text-color);
border-radius: var(--border-radius);
}
// ThemeProvider组件
const ThemeContext = Symbol('theme')
export const useTheme = () => {
const theme = inject(ThemeContext, ref('light'))
return theme
}
export const ThemeProvider = defineComponent({
props: {
theme: { type: String, required: true }
},
setup(props, { slots }) {
const root = ref<HTMLElement>()
watch(() => props.theme, (val) => {
document.documentElement.setAttribute('data-theme', val)
}, { immediate: true })
provide(ThemeContext, toRef(props, 'theme'))
return () => (
<div ref={root}>
{slots.default?.()}
</div>
)
}
})
// 自动生成API表格
function parseComponent(code: string) {
const propsRE = /props:\s*({[\s\S]*?})/g
const matches = code.match(propsRE)
return parseProps(matches[1])
}
interface PropMeta {
name: string
type: string
default?: string
description?: string
}
// 输出Markdown表格
function genMarkdownTable(props: PropMeta[]) {
return `
| 属性名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
${props.map(p => `| ${p.name} | ${p.type} | ${p.default} | ${p.description} |`).join('\n')}
`
}
// 使用Vite插件实现
export function docPlugin() {
return {
name: 'doc-plugin',
transform(code, id) {
if (id.endsWith('.md')) {
return code.replace(
/```vue demo\n([\s\S]*?)```/g,
(_, code) => genDemoComponent(code)
)
}
}
}
}
# .github/workflows/publish.yml
name: Publish
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm build
- run: pnpm test
- uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_TOKEN }}
检查项 | 阈值 | 失败策略 |
---|---|---|
单元测试覆盖率 | ≥ 80% | 阻断 |
构建时间 | < 5分钟 | 警告 |
包体积增长 | < 10% | 阻断 |
// components/package.json
{
"name": "@your-lib/components",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.mjs",
"types": "dist/types/index.d.ts",
"exports": {
".": {
"import": "./dist/esm/index.mjs",
"require": "./dist/cjs/index.js"
}
}
}
// babel-plugin-import
module.exports = function (babel) {
return {
visitor: {
ImportDeclaration(path) {
const source = path.node.source.value
if (source === '@your-lib/components') {
path.node.specifiers.forEach(spec => {
if (spec.type === 'ImportSpecifier') {
const component = spec.imported.name
// 转换为按需引入
path.insertAfter(
babel.template.ast`
import '${source}/es/${component.toLowerCase()}'
`
)
}
})
}
}
}
}
}
下篇预告:《自动化测试策略(Vitest+Cypress)》将深入: