Tema
Crear proyecto
Vas a crear tu primer proyecto profesional de Vue con create-vue (basado en Vite), entender la estructura generada y la anatomía de un Single File Component (.vue): plantilla, lógica y estilos en un mismo archivo.
Concepto
create-vue es el asistente oficial. Crea un proyecto con Vite (servidor de desarrollo instantáneo y build optimizado) y te pregunta qué herramientas del ecosistema quieres agregar: TypeScript, Vue Router, Pinia, pruebas, ESLint...
Requisitos: Node.js 20.19+ o 22.12+ (instalación).
Crear el proyecto
bash
npm create vue@latestEl asistente pregunta (respuestas sugeridas para este curso):
txt
✔ Project name: … mi-app-vue
✔ Add TypeScript? … No
✔ Select features to include in your project:
◉ Router (SPA development)
◉ Pinia (state management)
◯ Vitest (unit testing)
◯ End-to-End Testing
◉ ESLint (error prevention)
◉ Prettier (code formatting)bash
cd mi-app-vue
npm install
npm run devAbre http://localhost:5173.
TIP
Si ya dominas TypeScript, elige "Yes": Vue tiene un soporte excelente y los ejemplos de este curso funcionan igual agregando lang="ts" al script.
Estructura generada
txt
mi-app-vue/
├── public/
│ └── favicon.ico
├── src/
│ ├── assets/ ← CSS e imágenes que importas desde el código
│ │ ├── base.css
│ │ └── main.css
│ ├── components/ ← componentes reutilizables
│ │ └── HelloWorld.vue
│ ├── router/
│ │ └── index.js ← rutas (Vue Router)
│ ├── stores/
│ │ └── counter.js ← estado global (Pinia)
│ ├── views/ ← páginas (una por ruta)
│ │ ├── HomeView.vue
│ │ └── AboutView.vue
│ ├── App.vue ← componente raíz
│ └── main.js ← punto de entrada
├── index.html
├── package.json
├── vite.config.js
└── eslint.config.jsLos archivos clave
js
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
// Crea la aplicación, le agrega plugins y la monta en <div id="app">
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')html
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mi app Vue</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
export default defineConfig({
plugins: [vue(), vueDevTools()],
resolve: {
alias: {
// "@/components/X.vue" apunta a "src/components/X.vue"
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})json
{
"name": "mi-app-vue",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --fix",
"format": "prettier --write src/"
},
"dependencies": {
"pinia": "^3.0.0",
"vue": "^3.5.0",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.0",
"vite": "^7.0.0",
"vite-plugin-vue-devtools": "^8.0.0"
}
}| Comando | Qué hace |
|---|---|
npm run dev | Servidor de desarrollo con recarga instantánea |
npm run build | Build optimizado en dist/ |
npm run preview | Sirve dist/ para probar el build |
npm run lint | Revisa y corrige errores |
Anatomía de un Single File Component
Un archivo .vue tiene hasta tres bloques:
vue
<!-- src/components/TarjetaPerfil.vue -->
<!-- 1. Lógica: <script setup> -->
<script setup>
import { ref, computed } from 'vue'
// Todo lo que declares aquí está disponible en la plantilla
const nombre = ref('Laura Méndez')
const puesto = ref('Desarrolladora Frontend')
const seguidores = ref(1280)
const siguiendo = ref(false)
const textoBoton = computed(() => (siguiendo.value ? 'Siguiendo ✓' : 'Seguir'))
function alternar() {
siguiendo.value = !siguiendo.value
seguidores.value += siguiendo.value ? 1 : -1
}
</script>
<!-- 2. Plantilla: el HTML del componente -->
<template>
<article class="tarjeta">
<img class="avatar" src="https://i.pravatar.cc/120?img=47" :alt="`Foto de ${nombre}`">
<h2>{{ nombre }}</h2>
<p class="puesto">{{ puesto }}</p>
<p>{{ seguidores.toLocaleString('es-MX') }} seguidores</p>
<button :class="{ activo: siguiendo }" @click="alternar">{{ textoBoton }}</button>
</article>
</template>
<!-- 3. Estilos: scoped = sólo afectan a este componente -->
<style scoped>
.tarjeta {
max-width: 280px;
padding: 1.5rem;
text-align: center;
border-radius: 16px;
box-shadow: 0 8px 24px rgb(0 0 0 / 0.08);
font-family: system-ui, sans-serif;
}
.avatar { width: 96px; height: 96px; border-radius: 50%; }
.puesto { color: #6b7280; }
button {
padding: 0.5rem 1.25rem;
border: 2px solid #42b883;
border-radius: 999px;
background: white;
color: #42b883;
font-weight: 600;
cursor: pointer;
}
button.activo { background: #42b883; color: white; }
</style>Usarlo en otro componente
Con <script setup>, basta con importarlo: queda disponible en la plantilla.
vue
<!-- src/App.vue -->
<script setup>
import TarjetaPerfil from './components/TarjetaPerfil.vue'
</script>
<template>
<main style="display: grid; place-items: center; min-height: 100vh">
<TarjetaPerfil />
</main>
</template>Estilos con scoped
Vue agrega un atributo único (por ejemplo data-v-7a8b9c) a los elementos del componente y reescribe los selectores. Así, la clase .tarjeta de este componente no choca con otra .tarjeta de otro archivo.
vue
<style scoped>
/* Para dar estilo a un elemento dentro de un componente hijo */
.contenedor :deep(.titulo) { color: #42b883; }
</style>
<style>
/* Sin scoped: estilos globales (úsalo con moderación) */
body { margin: 0; }
</style>También puedes usar CSS Modules (<style module>), Sass (<style lang="scss">, instalando sass) o Tailwind CSS.
Tailwind CSS v4 en Vue
bash
npm i tailwindcss @tailwindcss/vitejs
// vite.config.js
import tailwindcss from '@tailwindcss/vite'
// plugins: [vue(), vueDevTools(), tailwindcss()]css
/* src/assets/main.css */
@import "tailwindcss";Limpiar el proyecto
Para empezar desde cero, deja App.vue mínimo y borra los componentes de ejemplo:
vue
<!-- src/App.vue -->
<script setup>
import { RouterLink, RouterView } from 'vue-router'
</script>
<template>
<header>
<nav>
<RouterLink to="/">Inicio</RouterLink> |
<RouterLink to="/about">Acerca de</RouterLink>
</nav>
</header>
<!-- Aquí se muestra la vista de la ruta actual -->
<RouterView />
</template>Herramientas recomendadas
- VS Code con la extensión oficial Vue - Official (antes Volar): resaltado, autocompletado y errores en archivos
.vue. - Vue DevTools: ya viene integrado como plugin de Vite (
vite-plugin-vue-devtools); también existe como extensión del navegador. Muestra componentes, estado de Pinia, rutas y eventos.
Buena práctica
- Nombra los componentes en PascalCase y con al menos dos palabras (
TarjetaPerfil.vue, noTarjeta.vue) para evitar choques con etiquetas HTML presentes o futuras. - Las páginas (una por ruta) van en
views/; las piezas reutilizables, encomponents/.
Error común
Olvidar la extensión .vue al importar: import Tarjeta from './Tarjeta' falla. Escribe siempre ./Tarjeta.vue.
Ejercicios
- Crea un proyecto con
create-vue(con Router y Pinia), limpia los ejemplos y crea un componenteTarjetaProducto.vuecon nombre, precio y un botón "Agregar" que cambie su texto a "Agregado ✓" al hacer clic. - Muestra tres
TarjetaProductoenApp.vuey verifica en Vue DevTools que cada una tiene su propio estado.
Ver solución
vue
<script setup>
import { ref } from 'vue'
const agregado = ref(false)
</script>
<template>
<article class="producto">
<h3>Café de Chiapas</h3>
<p>$249 MXN</p>
<button :disabled="agregado" @click="agregado = true">
{{ agregado ? 'Agregado ✓' : 'Agregar' }}
</button>
</article>
</template>
<style scoped>
.producto { border: 1px solid #e5e7eb; border-radius: 12px; padding: 1rem; }
button { background: #42b883; color: white; border: 0; padding: 0.5rem 1rem; border-radius: 8px; }
button:disabled { opacity: 0.7; }
</style>vue
<script setup>
import TarjetaProducto from './components/TarjetaProducto.vue'
</script>
<template>
<main style="display: flex; gap: 1rem; padding: 2rem">
<TarjetaProducto />
<TarjetaProducto />
<TarjetaProducto />
</main>
</template>En la lección de componentes aprenderás a pasar nombre y precio distintos a cada tarjeta con props.
Resumen
npm create vue@latestcrea un proyecto con Vite y, opcionalmente, Router, Pinia y TypeScript.main.jscrea la app concreateApp, registra plugins y la monta en#app.- Un Single File Component tiene
<script setup>,<template>y<style scoped>. - Con
<script setup>, lo que declaras e importas está disponible directamente en la plantilla.