第 134 章:组件库发布
学习目标
- 掌握 npm 包的发布流程
- 学会 Monorepo 管理与版本控制
- 理解语义化版本、Changesets、CI 发布
- 搭建组件库文档站
一、为什么要发布组件库
二、库模式构建
2.1 vite.config.ts
typescript
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
name: 'MyUI',
fileName: (format) => `my-ui.${format}.js`,
formats: ['es', 'cjs', 'umd']
},
rollupOptions: {
external: ['vue'],
output: {
globals: { vue: 'Vue' },
assetFileNames: 'my-ui.[name].[ext]'
}
},
sourcemap: true,
emptyOutDir: false
}
});2.2 package.json
json
{
"name": "my-ui",
"version": "1.0.0",
"description": "My UI Component Library",
"type": "module",
"files": [
"dist",
"*.d.ts",
"README.md"
],
"main": "./dist/my-ui.cjs.js",
"module": "./dist/my-ui.es.js",
"unpkg": "./dist/my-ui.umd.js",
"jsdelivr": "./dist/my-ui.umd.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/my-ui.es.js",
"require": "./dist/my-ui.cjs.js"
},
"./style.css": "./dist/style.css"
},
"scripts": {
"build": "vue-tsc --emitDeclarationOnly && vite build",
"dev": "vite",
"preview": "vite preview"
},
"peerDependencies": {
"vue": "^3.0.0"
},
"keywords": ["vue", "ui", "components"],
"license": "MIT"
}2.3 入口文件
typescript
// src/index.ts
import type { App } from 'vue';
import MyButton from './components/Button.vue';
import MyInput from './components/Input.vue';
const components = [MyButton, MyInput];
export { MyButton, MyInput };
export default {
install(app: App) {
components.forEach((comp) => {
app.component(comp.__name || 'Comp', comp);
});
}
};2.4 类型声明
json
// package.json
{
"scripts": {
"build:types": "vue-tsc --emitDeclarationOnly --declaration --outDir dist"
}
}三、Monorepo 架构
3.1 pnpm workspace
yaml
# pnpm-workspace.yaml
packages:
- 'packages/*'
- 'play'3.2 目录结构
my-ui/
├── packages/
│ ├── components/
│ │ ├── src/
│ │ │ ├── Button/
│ │ │ ├── Input/
│ │ │ └── index.ts
│ │ └── package.json
│ ├── theme/
│ │ ├── src/
│ │ └── package.json
│ └── utils/
│ ├── src/
│ └── package.json
├── play/ # 测试用
│ ├── src/
│ └── package.json
├── docs/ # 文档
├── scripts/
├── package.json
└── pnpm-workspace.yaml3.3 子包 package.json
json
// packages/components/package.json
{
"name": "@my-ui/components",
"version": "1.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"dependencies": {
"@my-ui/utils": "workspace:*",
"@my-ui/theme": "workspace:*",
"vue": "^3.0.0"
}
}3.4 根 package.json
json
{
"name": "my-ui-monorepo",
"private": true,
"scripts": {
"dev": "pnpm --filter play dev",
"build": "pnpm -r build",
"build:components": "pnpm --filter @my-ui/components build",
"lint": "eslint \"packages/**/*.{ts,vue}\"",
"test": "vitest",
"release": "pnpm build && changeset publish"
}
}四、版本管理
4.1 语义化版本
主版本.次版本.修订号
↓ ↓ ↓
1 . 2 . 3- 主版本(MAJOR):不兼容 API 变更
- 次版本(MINOR):向后兼容的功能新增
- 修订号(PATCH):向后兼容的 bug 修复
4.2 预发布版本
1.0.0-alpha.0
1.0.0-beta.1
1.0.0-rc.04.3 Changesets
bash
pnpm add -D @changesets/cli
pnpm changeset init4.4 添加变更
bash
pnpm changesetmarkdown
# .changeset/abc123.md
---
'@my-ui/components': minor
---
新增 Button 组件的 loading 属性4.5 发布
bash
pnpm changeset version # 更新版本
pnpm changeset publish # 发布到 npm4.6 Changesets CI
yaml
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: pnpm test
- run: pnpm changeset version
- run: pnpm changeset publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}五、文档站
5.1 VitePress
bash
pnpm add -D vitepress5.2 配置
typescript
// docs/.vitepress/config.ts
import { defineConfig } from 'vitepress';
export default defineConfig({
title: 'MyUI',
themeConfig: {
nav: [
{ text: '指南', link: '/guide/' },
{ text: '组件', link: '/components/button' }
],
sidebar: {
'/components/': [
{
text: '基础组件',
items: [
{ text: 'Button 按钮', link: '/components/button' },
{ text: 'Input 输入', link: '/components/input' }
]
}
]
}
}
});5.3 组件示例
markdown
# Button 按钮
常用的操作按钮。
## 基础用法
<div class="example">
<MyButton>默认</MyButton>
<MyButton type="primary">主要</MyButton>
</div>
::: details 查看代码
```vue
<template>
<MyButton>默认</MyButton>
<MyButton type="primary">主要</MyButton>
</template>:::
API
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| type | 类型 | 'primary' | 'success' | default |
| size | 尺寸 | 'small' | 'medium' | 'large' | medium |
### 5.4 自动注册
```typescript
// docs/.vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme';
import MyUI from '../../../packages/components/src';
import 'my-ui/dist/style.css';
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.use(MyUI);
}
};六、构建与发布
6.1 构建脚本
typescript
// scripts/build.ts
import { execSync } from 'child_process';
const packages = ['components', 'theme', 'utils'];
for (const pkg of packages) {
console.log(`Building ${pkg}...`);
execSync(`pnpm --filter @my-ui/${pkg} build`, { stdio: 'inherit' });
}6.2 发布脚本
bash
# .github/workflows/publish.yml
name: Publish
on:
push:
branches: [main]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org/
- run: pnpm install
- run: pnpm build
- run: pnpm publish -r --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}6.3 预发布
bash
# alpha 版本
pnpm changeset pre enter alpha
pnpm changeset version
pnpm changeset publish --tag alpha
# 退出预发布
pnpm changeset pre exit七、CI 完整流程
八、CDN 分发
8.1 jsDelivr
html
<!-- 加载 UMD 打包 -->
<script src="https://cdn.jsdelivr.net/npm/my-ui@1.0.0/dist/my-ui.umd.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/my-ui@1.0.0/dist/style.css" />8.2 unpkg
html
<script src="https://unpkg.com/my-ui@1.0.0/dist/my-ui.umd.js"></script>8.3 ESM CDN
html
<script type="module">
import { MyButton } from 'https://cdn.jsdelivr.net/npm/my-ui@1.0.0/dist/my-ui.es.js';
</script>九、Playground
9.1 内部测试
typescript
// play/src/App.vue
<script setup lang="ts">
import { MyButton } from '@my-ui/components';
</script>
<template>
<div class="playground">
<MyButton>测试</MyButton>
</div>
</template>9.2 运行
bash
pnpm --filter play dev十、注意事项
10.1 样式导出
typescript
// 主入口
import './styles/index.css';
export { MyButton, MyInput } from './components';10.2 Tree-shaking
typescript
// ✅ 每个组件单独 export
export { MyButton } from './Button';
export { MyInput } from './Input';
// ❌ 避免循环引用10.3 依赖 Peer
json
{
"peerDependencies": {
"vue": "^3.0.0"
}
}10.4 文件体积
bash
pnpm add -D rollup-plugin-visualizertypescript
// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
visualizer({ open: true })
]
});十一、常见问题
11.1 样式丢失
typescript
// 用户必须手动导入
import 'my-ui/dist/style.css';11.2 类型错误
bash
# 生成 .d.ts
pnpm vue-tsc --emitDeclarationOnly11.3 版本冲突
bash
# 锁定依赖
pnpm install --frozen-lockfile11.4 路径别名
typescript
// vite.config.ts
{
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
}十二、推荐工具
| 工具 | 用途 |
|---|---|
| Changesets | 版本管理 |
| VitePress | 文档站 |
| Rollup | 打包 |
| TypeScript | 类型 |
| Storybook | 组件预览 |
| Size Limit | 体积控制 |
十三、本章小结
| 概念 | 关键 |
|---|---|
| 库模式 | build.lib + 多格式 |
| Monorepo | pnpm workspace |
| 版本管理 | Changesets |
| 文档站 | VitePress |
| 自动化 | GitHub Actions |
| 发布 | npm + tag |
动手练习
- 库构建:将你的组件库配置为库模式
- Monorepo:用 pnpm workspace 管理多个包
- Changesets:为每次变更添加 changeset
- 文档站:用 VitePress 搭建组件库文档
- CI 发布:配置 GitHub Actions 自动发布
推荐阅读
- 📖 Vite 库模式 — 官方文档
- 📖 Changesets — 版本管理
- 📖 pnpm workspace — Monorepo
- 🌐 Vue 组件库实践 — Element Plus 源码
下一章:第 135 章:性能优化 →