Skip to content

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 组件时热更新
  • 保持开发状态

组件开发

创建新组件

  1. 创建组件目录结构
src/components/MyComponent/
├── MyComponent.vue      # 组件模板
├── MyComponent.ts       # 组件逻辑
├── types.ts            # 类型定义
└── index.ts            # 导出文件
  1. 定义组件类型
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;
}
  1. 实现组件逻辑
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 };
}
  1. 实现组件模板
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>
  1. 导出组件
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 部署指南

相关资源