Una librería de componentes compartida da a múltiples apps (o equipos) un conjunto consistente de componentes UI. El objetivo es reutilización sin acoplamiento. Cada consumidor importa lo que necesita, obtiene type safety y theming, y puede actualizar a su propio ritmo.
Estructura del proyecto
La librería vive en su propio paquete, ya sea un repo independiente o un workspace dentro de un monorepo. Cada componente es una unidad autocontenida con props tipadas, emits tipados, y slots para flexibilidad:
packages/ui/
├── src/
│ ├── components/
│ │ ├── Button/
│ │ │ ├── Button.vue
│ │ │ └── index.ts
│ │ ├── Input/
│ │ └── Modal/
│ ├── tokens/
│ │ └── colors.css
│ └── index.ts ← named exports para tree-shaking
├── package.json
└── vite.config.ts ← build en modo libreríaPrincipios de diseño de componentes
Tipa todo. Cada componente debería tener props tipadas, emits tipados y slots tipados. Los consumidores obtienen autocompletado y errores en tiempo de compilación:
<script setup lang="ts">
interface Props {
variant?: 'primary' | 'secondary' | 'danger'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
}
withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'md'
})
defineEmits<{
click: [event: MouseEvent]
}>()
defineSlots<{
default: () => void
icon?: () => void
}>()
</script>Usa slots en lugar de props para contenido. Un botón que recibe props label e icon es limitado. Un botón con un slot por defecto y un slot icon permite a los consumidores poner cualquier cosa dentro: texto, iconos, badges, markup complejo.
Usa CSS custom properties para theming. No hardcodees colores ni espaciado. Expón design tokens que los consumidores puedan sobreescribir:
.btn {
background: var(--ui-color-primary, #3b82f6);
padding: var(--ui-spacing-md, 0.75rem 1.5rem);
border-radius: var(--ui-radius, 0.375rem);
}Los consumidores sobreescriben tokens a nivel de app sin tocar el código de la librería.
Build y distribución
Usa el modo librería de Vite para hacer build. Exporta componentes con nombre para tree-shaking, de forma que los consumidores solo empaquetan lo que importan:
// src/index.ts
export { default as Button } from './components/Button/Button.vue'
export { default as Input } from './components/Input/Input.vue'
export { default as Modal } from './components/Modal/Modal.vue'Establece sideEffects: false en package.json para que los bundlers puedan eliminar componentes no usados.
Documentación
Usa VitePress o Storybook para docs interactivos. Cada componente debería tener ejemplos en vivo mostrando variantes, tamaños, slots y edge cases. Storybook es más completo para desarrollo de componentes (testing visual, checks de accesibilidad); VitePress es más ligero si tus docs también cubren guías de uso y referencia de API.
Versionado y testing
- Semver: cambios breaking en props son bumps major, nuevos componentes son minor
- Vitest para tests unitarios (renderizado de props, comportamiento de emits, contenido de slots)
- Regresión visual (Chromatic, Percy, o screenshots de Playwright) para detectar cambios de estilo no intencionados
Ver también: ¿Cómo se declaran props con TypeScript? · ¿Qué son los slots? · ¿Qué son los atributos fallthrough?
Referencias
- Library Mode - Vite docs
- Storybook for Vue - Storybook docs
- VitePress - VitePress docs