Component 模板
基于 Vue 3 + TypeScript 的现代化组件库开发模板,支持组件开发、工具库构建和完整的发布流程。
技术栈
- Vue 3 - 渐进式 JavaScript 框架
- TypeScript - JavaScript 的超集,提供类型安全
- tsup - 基于 ESBuild 的 TypeScript 打包器
- Vite - 下一代前端构建工具
- ESBuild - 极速的 JavaScript 打包器
快速开始
创建项目
bash
# 初始化项目
vup init my-project
# 进入项目目录
cd my-project
# 添加 Component 应用
vup add my-component
# 选择 Component 模板
开发
bash
# 安装依赖
pnpm install
# tsup 开发模式(监听文件变化)
pnpm dev:tsup
# Vite 开发模式(监听文件变化)
pnpm dev:vite
构建
bash
# 完整构建(tsup + vite)
pnpm build
# 仅构建 tsup
pnpm build:tsup
# 仅构建 Vite
pnpm build:vite
项目结构
my-component/
├── src/
│ ├── components/ # Vue 组件
│ │ ├── Input/ # Input 组件
│ │ │ ├── Input.vue # 组件模板
│ │ │ ├── Input.ts # 组件逻辑
│ │ │ ├── types.ts # 类型定义
│ │ │ └── index.ts # 导出文件
│ │ └── index.ts # 组件统一导出
│ ├── libs/ # 工具库
│ │ ├── http/ # HTTP 请求工具
│ │ │ └── index.ts
│ │ └── index.ts # 工具库统一导出
│ ├── index.ts # 主入口文件
│ └── vue-shim.d.ts # Vue 类型声明
├── scripts/ # 构建脚本
│ └── build-index.cjs # 索引构建脚本
├── .output/ # 构建输出目录
├── tsup.config.ts # tsup 配置
├── vite.config.ts # Vite 配置
├── package.json # 项目配置
└── tsconfig.json # TypeScript 配置
核心特性
🎯 TypeScript 支持
完整的 TypeScript 支持,提供类型安全和智能提示:
typescript
// 组件类型定义
interface InputProps {
value: string;
placeholder?: string;
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
clearable?: boolean;
}
interface InputEmit {
(e: 'update:value', value: string): void;
(e: 'change', value: string): void;
(e: 'focus', event: FocusEvent): void;
(e: 'blur', event: FocusEvent): void;
}
🎨 Vue 3 组件开发
完整的 Vue 3 组件开发支持:
vue
<template>
<div class="input-wrapper">
<input
:class="inputClasses"
:type="showPassword ? 'text' : type"
:disabled="disabled"
:placeholder="placeholder"
:value="value"
@input="handleInput"
@change="handleChange"
/>
<button v-if="clearable && value" @click="handleClear">✕</button>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import type { InputProps, InputEmit } from './types';
defineOptions({
name: 'Input',
});
const props = withDefaults(defineProps<InputProps>(), {
size: 'medium',
disabled: false,
clearable: false,
});
const emit = defineEmits<InputEmit>();
const inputClasses = computed(() => {
const classes = ['input'];
classes.push(`input--${props.size}`);
if (props.disabled) classes.push('input--disabled');
return classes;
});
const handleInput = (event: Event) => {
const target = event.target as HTMLInputElement;
emit('update:value', target.value);
};
</script>
🛠️ 工具库开发
纯 TypeScript 工具函数开发:
typescript
// HTTP 客户端
export class HttpClient {
private config: RequestConfig;
private requestInterceptors: RequestInterceptor[] = [];
private responseInterceptors: ResponseInterceptor[] = [];
constructor(config: RequestConfig = {}) {
this.config = {
baseURL: config.baseURL || '',
timeout: config.timeout || 10000,
headers: { 'Content-Type': 'application/json', ...config.headers },
retries: config.retries || 0,
retryDelay: config.retryDelay || 1000,
};
}
async request<T = any>(
url: string,
options: RequestInit = {}
): Promise<ResponseData<T>> {
// 请求逻辑实现
}
get<T = any>(url: string, options?: RequestInit): Promise<ResponseData<T>> {
return this.request<T>(url, { ...options, method: 'GET' });
}
post<T = any>(
url: string,
data?: any,
options?: RequestInit
): Promise<ResponseData<T>> {
return this.request<T>(url, {
...options,
method: 'POST',
body: data ? JSON.stringify(data) : undefined,
});
}
}
📦 双构建系统
tsup + Vite 双重构建支持:
- tsup - 基于 ESBuild 的极速构建
- Vite - 完整的 Vue 组件构建
- 多格式输出 - CommonJS 和 ESM 支持
- 类型声明 - 自动生成 .d.ts 文件
开发工具
代码质量
- ESLint - 代码质量检查
- Prettier - 代码格式化
- TypeScript - 类型检查
开发命令
bash
# 开发模式
pnpm dev:tsup # tsup 开发模式
pnpm dev:vite # Vite 开发模式
# 构建
pnpm build # 完整构建
pnpm build:tsup # 仅构建 tsup
pnpm build:vite # 仅构建 Vite
# 类型检查
pnpm type-check:tsup # tsup 类型检查
pnpm type-check:vite # Vite 类型检查
# 代码质量
pnpm lint # 代码检查
pnpm lint:fix # 自动修复问题
pnpm format # 代码格式化
热重载
提供极速的热重载体验:
- 修改 TypeScript 文件时自动重新构建
- 修改 Vue 组件时热更新
- 保持开发状态
组件开发
创建新组件
- 创建组件目录结构
src/components/MyComponent/
├── MyComponent.vue # 组件模板
├── MyComponent.ts # 组件逻辑
├── types.ts # 类型定义
└── index.ts # 导出文件
- 定义组件类型
typescript
// types.ts
export interface MyComponentProps {
title: string;
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
}
export interface MyComponentEmit {
(e: 'change', value: string): void;
(e: 'click', event: MouseEvent): void;
}
- 实现组件逻辑
typescript
// MyComponent.ts
import { computed } from 'vue';
import type { MyComponentProps, MyComponentEmit } from './types';
export function useMyComponent(props: MyComponentProps, emit: MyComponentEmit) {
const classes = computed(() => {
return [
'my-component',
`my-component--${props.size}`,
{ 'my-component--disabled': props.disabled },
];
});
const handleClick = (event: MouseEvent) => {
if (!props.disabled) {
emit('click', event);
}
};
return { classes, handleClick };
}
- 实现组件模板
vue
<!-- MyComponent.vue -->
<template>
<div :class="classes" @click="handleClick">
<h3>{{ title }}</h3>
<slot />
</div>
</template>
<script setup lang="ts">
import { useMyComponent } from './MyComponent';
import type { MyComponentProps, MyComponentEmit } from './types';
defineOptions({
name: 'MyComponent',
});
const props = withDefaults(defineProps<MyComponentProps>(), {
size: 'medium',
disabled: false,
});
const emit = defineEmits<MyComponentEmit>();
const { classes, handleClick } = useMyComponent(props, emit);
</script>
<style scoped>
.my-component {
/* 组件样式 */
}
</style>
- 导出组件
typescript
// index.ts
export { default as MyComponent } from './MyComponent.vue';
export type { MyComponentProps, MyComponentEmit } from './types';
组件使用示例
vue
<template>
<div>
<Input
v-model:value="inputValue"
placeholder="请输入内容"
size="large"
clearable
@change="handleChange"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Input } from 'your-component-lib';
const inputValue = ref('');
const handleChange = (value: string) => {
console.log('Input changed:', value);
};
</script>
工具库开发
创建工具函数
typescript
// src/libs/utils/index.ts
export function formatDate(date: Date, format: string = 'YYYY-MM-DD'): string {
// 日期格式化逻辑
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return format
.replace('YYYY', String(year))
.replace('MM', month)
.replace('DD', day);
}
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout;
return (...args: Parameters<T>) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}
HTTP 客户端使用
typescript
import { http, HttpClient } from 'your-component-lib';
// 使用默认实例
const data = await http.get('/api/users');
// 创建自定义实例
const apiClient = new HttpClient({
baseURL: 'https://api.example.com',
timeout: 5000,
retries: 3,
});
// 添加拦截器
apiClient.addRequestInterceptor({
onRequest: (config) => {
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`,
};
return config;
},
});
// 发送请求
const users = await apiClient.get('/users');
const newUser = await apiClient.post('/users', { name: 'John' });
构建配置
tsup 配置
typescript
// tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
entry: {
index: 'src/index.ts',
},
outDir: '.output',
format: ['cjs', 'esm'], // 输出 CommonJS 和 ESM 格式
dts: {
resolve: true, // 解析类型声明
},
splitting: false, // 不分割代码
sourcemap: true, // 生成 sourcemap
clean: true, // 清理输出目录
external: ['vue'], // 外部依赖
treeshake: true, // 启用 Tree Shaking
});
Vite 配置
typescript
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import dts from 'vite-plugin-dts';
export default defineConfig({
plugins: [
vue(),
dts({
outDir: '.output',
include: ['src/**/*'],
exclude: ['**/*.test.*', '**/*.spec.*'],
insertTypesEntry: true,
copyDtsFiles: false,
rollupTypes: true,
}),
],
build: {
outDir: '.output',
lib: {
entry: {
index: resolve(__dirname, 'src/index.ts'),
},
name: 'ComponentLib',
fileName: (format: string, entryName: string) =>
`${entryName}.${format}.js`,
},
rollupOptions: {
external: ['vue', 'vue-router', 'pinia'],
output: {
globals: {
vue: 'Vue',
},
},
},
},
});
发布配置
package.json 配置
json
{
"name": "your-component-lib",
"version": "1.0.0",
"main": "./.output/index.js",
"module": "./.output/index.mjs",
"types": "./.output/index.d.ts",
"exports": {
".": {
"types": "./.output/index.d.ts",
"import": "./.output/index.mjs",
"require": "./.output/index.js"
}
},
"files": ["./.output"],
"peerDependencies": {
"vue": "^3.0.0"
}
}
发布到 NPM
bash
# 构建项目
pnpm build
# 发布到 NPM
pnpm publish:npm
# 发布 Beta 版本
pnpm publish:beta
最佳实践
组件开发
- 使用 TypeScript 提供类型安全
- 合理使用 Vue 3 Composition API
- 遵循组件设计原则
- 提供完整的 Props 和 Events 类型定义
工具库开发
- 保持函数纯度和可测试性
- 提供完整的类型定义
- 使用模块化设计
- 避免外部依赖
构建优化
- 合理配置外部依赖
- 启用 Tree Shaking
- 生成 sourcemap 便于调试
- 支持多格式输出
发布管理
- 遵循语义化版本控制
- 提供完整的类型声明
- 配置正确的入口点
- 测试不同环境下的兼容性
部署
本地测试
bash
# 构建组件库
pnpm build
# 在测试项目中安装
npm install /path/to/your-component-lib
发布到 NPM
bash
# 发布到 NPM
pnpm publish:npm
部署到 Vercel
Component 库可以部署到 Vercel。详细的部署配置和步骤请参考 Vercel 部署指南。