Component Library Template
The Component Library template provides a modern Vue 3 component library development environment with TypeScript, perfect for building reusable UI components and utility libraries.
Technical Stack
- Vue 3 - Progressive JavaScript framework
- TypeScript - Type-safe development
- tsup - TypeScript bundler
- Vite - Fast build tool and dev server
- ESBuild - Fast JavaScript bundler
- Tailwind CSS - Utility-first CSS framework
- SCSS - CSS preprocessor
Quick Start
1. Create Project
bash
# Initialize project
vup init my-component-library
cd my-component-library
# Add Component Library template
vup add my-components2. Install Dependencies
bash
# Install dependencies
pnpm install3. Start Development
bash
# Start development server
cd apps/my-components
pnpm devProject Structure
apps/my-components/
├── src/
│ ├── components/ # Vue components
│ │ ├── Input/ # Input component
│ │ │ ├── Input.vue # Component file
│ │ │ ├── Input.ts # Component logic
│ │ │ └── index.ts # Component export
│ │ └── index.ts # Components index
│ ├── libs/ # Utility libraries
│ │ ├── http/ # HTTP utilities
│ │ │ ├── index.ts # HTTP client
│ │ └── index.ts # Libraries index
│ ├── index.ts # Main entry point
│ └── vue-shim.d.ts # Vue type declarations
├── scripts/ # Build scripts
│ └── build-index.cjs # Index generation script
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── tsup.config.ts # tsup configuration
└── vite.config.ts # Vite configurationCore Features
Component Development
Basic Component
vue
<!-- src/components/Input/Input.vue -->
<template>
<div class="input-wrapper">
<label v-if="label" class="input-label">{{ label }}</label>
<input
:type="type"
:value="modelValue"
:placeholder="placeholder"
:disabled="disabled"
:class="inputClasses"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
/>
<div v-if="error" class="input-error">{{ error }}</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
interface Props {
modelValue: string;
label?: string;
type?: 'text' | 'email' | 'password' | 'number';
placeholder?: string;
disabled?: boolean;
error?: string;
size?: 'small' | 'medium' | 'large';
}
const props = withDefaults(defineProps<Props>(), {
type: 'text',
size: 'medium',
disabled: false,
});
const emit = defineEmits<{
'update:modelValue': [value: string];
focus: [event: FocusEvent];
blur: [event: FocusEvent];
}>();
const inputClasses = computed(() => [
'input',
`input--${props.size}`,
{
'input--error': props.error,
'input--disabled': props.disabled,
},
]);
const handleInput = (event: Event) => {
const target = event.target as HTMLInputElement;
emit('update:modelValue', target.value);
};
const handleFocus = (event: FocusEvent) => {
emit('focus', event);
};
const handleBlur = (event: FocusEvent) => {
emit('blur', event);
};
</script>
<style lang="scss" scoped>
.input-wrapper {
@apply w-full;
}
.input-label {
@apply block text-sm font-medium text-gray-700 mb-1;
}
.input {
@apply w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm;
@apply focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500;
@apply disabled:bg-gray-50 disabled:text-gray-500 disabled:cursor-not-allowed;
&--small {
@apply px-2 py-1 text-sm;
}
&--medium {
@apply px-3 py-2;
}
&--large {
@apply px-4 py-3 text-lg;
}
&--error {
@apply border-red-300 focus:ring-red-500 focus:border-red-500;
}
&--disabled {
@apply bg-gray-50 text-gray-500 cursor-not-allowed;
}
}
.input-error {
@apply mt-1 text-sm text-red-600;
}
</style>Component Logic
typescript
// src/components/Input/Input.ts
import { ref, computed } from 'vue';
export interface InputProps {
modelValue: string;
label?: string;
type?: 'text' | 'email' | 'password' | 'number';
placeholder?: string;
disabled?: boolean;
error?: string;
size?: 'small' | 'medium' | 'large';
}
export interface InputEmits {
'update:modelValue': [value: string];
focus: [event: FocusEvent];
blur: [event: FocusEvent];
}
export function useInput(props: InputProps, emit: InputEmits) {
const isFocused = ref(false);
const inputClasses = computed(() => [
'input',
`input--${props.size}`,
{
'input--error': props.error,
'input--disabled': props.disabled,
'input--focused': isFocused.value,
},
]);
const handleInput = (event: Event) => {
const target = event.target as HTMLInputElement;
emit('update:modelValue', target.value);
};
const handleFocus = (event: FocusEvent) => {
isFocused.value = true;
emit('focus', event);
};
const handleBlur = (event: FocusEvent) => {
isFocused.value = false;
emit('blur', event);
};
return {
isFocused,
inputClasses,
handleInput,
handleFocus,
handleBlur,
};
}Component Export
typescript
// src/components/Input/index.ts
export { default as Input } from './Input.vue';
export type { InputProps, InputEmits } from './Input';
export { useInput } from './Input';Utility Library Development
HTTP Client
typescript
// src/libs/http/index.ts
export interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
}
export interface RequestOptions {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
headers?: Record<string, string>;
body?: any;
timeout?: number;
}
export interface HttpClientConfig {
baseURL: string;
timeout: number;
headers: Record<string, string>;
}
export class HttpClient {
private config: HttpClientConfig;
constructor(config: Partial<HttpClientConfig> = {}) {
this.config = {
baseURL: '',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
...config,
};
}
async request<T = any>(
url: string,
options: RequestOptions = {}
): Promise<ApiResponse<T>> {
const controller = new AbortController();
const timeoutId = setTimeout(
() => controller.abort(),
options.timeout || this.config.timeout
);
try {
const response = await fetch(`${this.config.baseURL}${url}`, {
method: options.method || 'GET',
headers: {
...this.config.headers,
...options.headers,
},
body: options.body ? JSON.stringify(options.body) : undefined,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
throw error;
}
}
get<T = any>(url: string, options: Omit<RequestOptions, 'method'> = {}) {
return this.request<T>(url, { ...options, method: 'GET' });
}
post<T = any>(
url: string,
body?: any,
options: Omit<RequestOptions, 'method' | 'body'> = {}
) {
return this.request<T>(url, { ...options, method: 'POST', body });
}
put<T = any>(
url: string,
body?: any,
options: Omit<RequestOptions, 'method' | 'body'> = {}
) {
return this.request<T>(url, { ...options, method: 'PUT', body });
}
delete<T = any>(url: string, options: Omit<RequestOptions, 'method'> = {}) {
return this.request<T>(url, { ...options, method: 'DELETE' });
}
}
export const createHttpClient = (config: Partial<HttpClientConfig> = {}) => {
return new HttpClient(config);
};
export const httpClient = createHttpClient();Development Tools
tsup Configuration
typescript
// tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
entry: {
index: 'src/index.ts',
},
outDir: '.output',
format: ['cjs', 'esm'],
dts: {
resolve: true,
},
splitting: false,
sourcemap: true,
clean: true,
external: ['vue'], // External dependencies
treeshake: true,
});Vite Configuration
typescript
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import dts from 'vite-plugin-dts';
import { resolve } from 'node:path';
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',
},
},
},
},
});TypeScript Configuration
json
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"declaration": true,
"declarationMap": true,
"outDir": ".output",
"strict": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.*"]
}Available Scripts
json
{
"scripts": {
"dev": "vite build --watch",
"build": "vite build",
"type-check": "vue-tsc --noEmit",
"lint": "eslint src/ --ext .vue,.ts,.js",
"lint:fix": "eslint src/ --ext .vue,.ts,.js --fix",
"format": "prettier --write \"src/**/*.{js,ts,vue,json,css,scss}\"",
"format:check": "prettier --check \"src/**/*.{js,ts,vue,json,css,scss}\""
}
}Build and Publishing
Build for Production
bash
cd apps/my-components
# Build the component library
pnpm build
# The built files will be in the .output directoryPublish to NPM
bash
# Login to NPM
npm login
# Publish the package
npm publishPackage Configuration
json
// package.json
{
"name": "@your-org/my-components",
"version": "1.0.0",
"description": "A modern Vue 3 component library",
"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"
},
"devDependencies": {
"typescript": "^5.0.0",
"vite": "^5.0.0",
"vue": "^3.0.0",
"@vitejs/plugin-vue": "^5.0.0",
"vite-plugin-dts": "^3.0.0"
}
}Related Resources
- Vue 3 Documentation
- TypeScript Documentation
- Vite Documentation
- tsup Documentation
- ESBuild Documentation
Best Practices
1. Component Design Principles
- Single Responsibility: Each component should have one clear purpose
- Props Interface: Define clear TypeScript interfaces for all props
- Event Naming: Use consistent event naming conventions
- Slot Design: Provide flexible slot APIs for customization
2. TypeScript Best Practices
- Use strict type checking
- Define interfaces for all public APIs
- Export type definitions with components
- Use generic types for reusable utilities
3. Build Optimization
- Mark peer dependencies as external
- Generate source maps for debugging
- Enable tree shaking for smaller bundles
- Optimize CSS output
4. Documentation
- Document all public components
- Provide usage examples
- Include TypeScript type information
- Keep changelog updated
5. Testing Strategy
- Unit tests for component logic
- Integration tests for component interaction
- Visual regression tests for UI consistency
- Type tests for TypeScript definitions