materials

Proyectos Vue. SFC

Crear un proyecto Vue

Aunque como hemos visto, se puede usar Vue enlazando la librería directamente en el HTML, lo más habitual es crear una aplicación Vue completa usando un build tool como Vite (recomendado) o Vue CLI. Las ventajas de hacerlo así son muchas:

Para crear un nuevo proyecto Vue usaremos la herramienta Vite igual que hicimos en Javascript, que es un build tool moderno y muy rápido. Vite permite crear proyectos con diferentes frameworks (Vue, React, Svelte, …).

Podríamos crearlo con el comando npm create vite@latest y eligiendo el framework Vue cuando nos pregunta pero lo haremos con el comando:

npm create vue@latest

Al ejecutar este comando se nos pregunta el nombre del proyecto a crear y nos muestra una serie de herramientas que podemos integrar ya en el proyecto. De momento no marcaremos ninguna.

create vue@latest

Más adelante veremos qué proporciona cada una. Ahora aceptamos las opciones por defecto y se crea el directorio con el scaffolding básico para un proyecto Vue con un proyecto de ejemplo.

Lo primero que haremos es entrar al directorio del proyecto e instalar las dependencias (npm install) y a continuación ejecutar:

npm run dev

Este script de Vite funciona como ya vimos en el bloque de Javascript: compila el código, muestra si hay errores, lanza un servidor web en el puerto 5173 y carga el proyecto en el navegador (http://localhost:5173). Si cambiamos cualquier fichero del directorio src recompila y recarga la página automáticamente.

Además nos va a permitir acceder a una página desde donde explorar los componentes de nuestra aplicación y ver su código, propiedades, etc. igual que podemos hacer desde la consola si hemos instalado las Vue DevTools.

npm run dev

La página generada es por el proyecto es:

Proyecto de plantilla simple

Scaffolding creado

Se ha creado la carpeta con el nombre del proyecto y dentro el scaffolding para nuestro proyecto:

Directorios del proyecto de plantilla simple

Los principales ficheros y directorios creados son:

package.json

Aquí se configura nuestra aplicación:

index.html

Simplemente tiene el <div> app que es el que contendrá la aplicación.

main.js

import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

Es el fichero JS principal. Importa la utilidad createApp de la librería Vue y el componente App.vue. Crea la instancia de Vue con el componente definido en App.vue y lo renderiza en el elemento #app del index.html.

Los ficheros SFC (Single File Component)

Lo normal es separar el código de nuestra aplicación en diferentes componentes y que cada uno de ellos pueda guardarse en su propio fichero y así no tenemos un fichero con demasiado código. A estos ficheros que contienen un componente se les llama Single File Components (SFC) y tienen extensión .vue.

Estos ficheros contienen 3 secciones diferentes:

Aunque esto va contra la norma de tener el HTML, JS y CSS en ficheros separados en realidad sí están separados en diferentes secciones y tenemos la ventaja de tener en un único fichero todo lo que necesita el componente.

La mayoría de editores soportan estos ficheros instalándoles algún plugin, (como Vue-Oficial para Visual Studio Code) por lo que el resaltado de las diferentes partes es correcto. Además Vite nos permite usar ES2015 o posterior y los preprocesadores más comunes (SASS, Pug/Jade, Stylus, …) y ya se se traducirá automáticamente el código a ES5, HTML5 y CSS3.

Veamos en detalle cada una de las secciones del SFC.

<template>

Aquí incluiremos el HTML que sustituirá a la etiqueta del componente. Recuerda que en las versiones anteriores a Vue3 dentro sólo puede haber un único elemento HTML (si queremos poner más de uno los incluiremos en otro que los englobe).

<template>
  <div class="greetings">
    <h1 class="green">{ { msg }}</h1>
    <h3>
      You’ve successfully created a project with
      <a href="https://vitejs.dev/">Vite</a> +
      <a href="https://vuejs.org/">Vue 3</a>.
    </h3>
  </div>
</template>

Si el código HTML a incluir en el template es muy largo podemos ponerlo en un fichero externo y vincularlo en el template, así nuestro SFC queda más pequeño y legible:

<template src="./myComp.html">
</template>

Respecto al lenguaje, podemos usar HTML (la opción por defecto) o PUG que es una forma sencilla de escribir HTML. Lo indicamos como atributo de <template>:

<template lang="pug">
div.greetings
  h1.green { { msg }}
  h3
    | You’ve successfully created a project with 
    a(href="https://vitejs.dev/") Vite
    |  +
    a(href="https://vuejs.org/") Vue 3

<script>

Aquí definimos y exportamos el componente-. Si estamos usando la sintaxis de Options API el componente será un objeto con diferentes propiedades, entre otras:

<style>

Aquí pondremos estilos CSS que se aplicarán al componente. Podemos usar CSS, SASS o PostCSS. Si queremos importar ficheros de estilo con @import deberíamos guardarlos dentro de la carpeta assets de nuestra aplicación.

Si la etiqueta incluye el atributo scoped estos estilos se aplicarán únicamente a este componente (y sus descendientes) y no a todos los componentes de nuestra aplicación. Si tenemos estilos que queremos que se apliquen a toda la aplicación y otros que son sólo para el componente y sus descendientes pondremos 2 etiquetas <style>, una sin el atributo scoped y otra con él.

Igual que vimos en la etiqueta <template>, si el código de los estilos es demasiado largo podemos ponerlo en un fichero externo que vinculamos a la etiqueta con el atributo src.

Custom blocks

Además de estos 3 bloques un SFC puede tener otros bloques definidos por el programador para, por ejemplo, incluir la documentación del componente o sus test unitarios:

<custom1 src="./unit-test.js">
    Aquí podríamos incluir los test unitarios del proyecto
</custom1>

SFC creados en el nuevo proyecto

En el scaffolding creado por Vite tenemos varios SFC:

App.vue

Es el componente raíz de la aplicación, el que contiene el layout de la página. Se trata de un SFC (Single File Component) y lo que contiene dentro de la etiqueta <template> es lo que se renderizará en el div app que hay en index.html. Si contiene algún otro componente se indica aquí dónde renderizarlo (en este caso <HelloWorld> y <TheWelcome>).

Veamos qué contiene cada sección:

Sección template
<template>
  <header>
    <img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <HelloWorld msg="You did it!" />
    </div>
  </header>

  <main>
    <TheWelcome />
  </main>
</template>

Muestra la imagen del logo de Vue (las imágenes y otros ficheros como ficheros .css se guardan en /public o dentro de /src/assets/ -como el de Vue-) y carga en su header el componente HelloWorld y en su main el componente TheWelcome.

Sección script
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script>

Lo que hace es importar los componentes que se utilizan en el template. Esta es la sintaxis de Composition API. En sintaxis de Options API sería:

<script>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'

export default {
  components: {
    HelloWorld,
    TheWelcome
  }
}
</script>

En esta sintaxis siempre se exporta un objeto con las opciones del componente, en este caso la opción components donde se registran los componentes que hemos importado.

Sección style

Aquí se definen los estilos de este componente. Como la etiqueta SÍ tiene el atributo scoped (<style scoped>) significa que los estilos aquí definidos se aplicarán SÓLO a este componente, y a sus subcomponentes.

components/HelloWorld.vue

Es el componente que muestra el texto que aparece bajo la imagen. Recibe como parámetro el título a mostrar. Veamos qué contiene cada sección:

Sección script
<script setup>
defineProps({
  msg: {
    type: String,
    required: true,
  },
})
</script>

Con defineProps declara que recibe el parámetro msg, que es de tipo String y obligatorio.

En sintaxis Options API sería:

<script>
export default {
  props: {
    msg: {
      type: String,
      required: true,
    },
  }
}
</script>
Sección template
<template>
  <div class="greetings">
    <h1 class="green">{ { msg }}</h1>
    <h3>
      You’ve successfully created a project with
      <a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
      <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
    </h3>
  </div>
</template>

Muestra el msg recibido como parámetro.

Sección style

También tiene el atributo scoped (<style scoped>) por lo que los estilos aquí definidos se aplicarán sólo a este componente.

Build and Deploy de nuestra aplicación

Normalmente trabajaremos con algún gestor de versiones como git. Para subir nuestro proyecto al repositorio lo creamos (en GitHub, GitLab o donde queramos) y ejecutamos desde la carpeta del proyecto:

git init
git add .
git remote add origin https://github.com/mi-usuario/mi-proyecto
git commit -m "Primer commit"
git push -u origin main

Cuando nuestra aplicación esté lista para subir a producción ejecutaremos el script:

npm run build

Este comando genera los JS y CSS para subir a producción dentro de la carpeta dist. El contenido de esta carpeta es lo único que debemos subir a nuestro servidor de producción.

Añadir nuevos paquetes y plugins

Si queremos usar un nuevo paquete en nuestra aplicación lo instalaremos con npm:

npm install nombre-paquete

Este comando además de instalar el paquete en node-modules lo añade a las dependencias del package.json. La opción --save o -S lo añadirá como dependencia de producción y --dev o -D como dependencia de desarrollo. Si no ponemos nada se añade como una dependencia de producción. Ej.:

npm install -S axios

Para usarlo en nuestros componentes debemos importarlo y registrarlo tal y como se indique en su documentación. Podemos hacerlo en el main.js (o en algún fichero JS que importemos en main.js como en el caso de los plugins) si queremos poder usarlo en todos los componentes.

Si el paquete que queremos instalar se encuentra como plugin el proceso es más sencillo ya que sólo es necesario usar app.use(myPlugin, { /* opciones opcionales */}) en el fichero main.js.

Bootstrap

Si queremos usarlo lo instalaremos como una dependencia de producción y después lo añadimos al fichero src/main.js:

import "bootstrap/dist/css/bootstrap.css"

Recuerda que siempre es conveniente importar Bootstrap antes de importar nuestro propio CSS (antes de la línea import './assets/main.css').

Si necesitamos algún componente de Bootstrap que utilice Javascript importaríamos también su javascript en el fichero main.js pero en este caso después de montar la aplicación vue:

// src/main.js
import { createApp } from 'vue'
import App from './App.vue'

import "bootstrap/dist/css/bootstrap.css"
import './assets/main.css'

createApp(App).mount('#app')

import "bootstrap/dist/js/bootstrap.js"

Iconos

Para usar los iconos de Bootstrap 5 podemos instalar el paquete bootstrap-icons o bien importarlos en el CSS desde su CDN, tal y como se explica en la documentación de Bootstrap. Una vez hecho ya podemos incluir los iconos en etiquetas <i>.

Por ejemplo, si importamos el CSS incluiremos en el <style> del componente App.vue:

@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css")

y donde queramos incluir el icono de la papelera, por ejemplo, incluimos:

<i class="bi bi-trash"></i>

Si queremos usar otras librerías de iconos como Font Awesome o Material Icons simplemente importaremos su CSS en el fichero App.vue y usaremos las clases que nos proporcionen. Para Material Icons sería:

@import url('https://fonts.googleapis.com/icon?family=Material+Icons');

y su uso sería:

<span class="material-icons">delete</span>

Respecto a los componentes de Bootstrap, para que funcionen sólo tenemos que usar los atributos data-bs- (recuerda que muchos de estos componentes necesitan su Javascript por lo que deberemos importarlo como se ha explicado antes). Por ejemplo para hacer un botón colapsable haremos:

<button 
  class="btn btn-primary" 
  data-bs-target="#collapseTarget" 
  data-bs-toggle="collapse">
  Bootstrap collapse
</button>
<div class="collapse py-2" id="collapseTarget">
  This is the toggle-able content!
</div>

En lugar de usar atributos data-bs- podemos envolver los componentes bootstrap en componentes Vue como se explica en muchas páginas, como Using Bootstrap 5 with Vue 3.

Crear un nuevo componente

Creamos un nuevo fichero en /src/components (o en alguna subcarpeta dentro) con extensión .vue. Donde queramos usar ese componente debemos importarlo y registrarlo como ya hemos visto:

// En sintaxis Composition API

import CompName from './CompName.vue'

// En sintaxis Options API

import CompName from './CompName.vue'

export default {
  ...
  components: {
    'comp-name': CompName
  }
  ...
}

Y ya podemos incluir el componente en el HTML:

<comp-name ...> ... </comp-name>

Depurar el código en la consola

Podemos seguir depurando nuestro código, poniendo puntos de interrupción y usando todas las herramientas que nos proporciona la consola mientras estamos en modo de depuración (si hemos abierto la aplicación con npm run dev).

Como estamos usando Vite no se está ejecutando nuestro código directamente sino que se ha convertido al código que ejecuta el navegador, por eso en el depurador aparecen varios ficheros y el que usaremos para depurar es el que pone (mapeado):

Depurar en la consola

Recordad que con las Vue DevTools tenemos una nueva pestaña en la consola desde la que podemos ver todos nuestros componentes con sus propiedades y datos:

Vue DevTools

Aplicación de ejemplo

Recordemos estamos haciendo una aplicación para gestionar una lista de tareas a realizar. Ahora vamos a hacerla en un proyecto Vite para lo que crearemos un nuevo proyecto (podemos decirle que no cree los componentes de ejemplo porque no los usaremos) y en el App.vue renderizamos nuestra aplicación.

En el template de App.vue pondremos el DIV del HTML de nuestra aplicación y en el script pondremos todo el código JS que teníamos en el bloque de directivas. En modo Options API quedaría:

<script>
export default {
  name: 'App',
  data: () => ({
      todos: [
        { id: 1, title: "Learn JavaScript", done: false }, 
        { id: 2, title: "Learn Vue", done: false }, 
        { id: 3, title: "Play around in JSFiddle", done: true }, 
        { id: 4, title: "Build something awesome", done: true }
      ],
      newTodo: '',
  }),
  methods: {
    addTodo() {
      if (this.newTodo.trim()) {
        this.todos.push({ title: this.newTodo.trim(), done: false });
        this.newTodo = '';
      }
    },
    delTodo(index) {
      this.todos.splice(index, 1);
    },
    delTodos() {
      this.todos = [];
    },
  }
}
</script>

Si usamos Composition API el código del script sería:

<script>
import { ref } from 'vue';
export default {
  name: 'App',
  setup() {
    const todos = ref([
      { id: 1, title: "Learn JavaScript", done: false }, 
      { id: 2, title: "Learn Vue", done: false }, 
      { id: 3, title: "Play around in JSFiddle", done: true }, 
      { id: 4, title: "Build something awesome", done: true }
    ]);
    const newTodo = ref('');

    const addTodo = () => {
      if (newTodo.value.trim()) {
        todos.value.push({ title: newTodo.value.trim(), done: false });
        newTodo.value = '';
      }
    };

    const delTodo = (index) => {
      todos.value.splice(index, 1);
    };

    const delTodos = () => {
      todos.value = [];
    };

    return {
      todos,
      newTodo,
      addTodo,
      delTodo,
      delTodos
    };
  }
}
</script>

y abreviado:

<script setup>
import { ref } from 'vue';
const todos = ref([
  { id: 1, title: "Learn JavaScript", done: false }, 
  { id: 2, title: "Learn Vue", done: false }, 
  { id: 3, title: "Play around in JSFiddle", done: true }, 
  { id: 4, title: "Build something awesome", done: true }
]);
const newTodo = ref('');    

const addTodo = () => {
  if (newTodo.value.trim()) {
    todos.value.push({ title: newTodo.value.trim(), done: false });
    newTodo.value = '';
  }
};

const delTodo = (index) => {
  todos.value.splice(index, 1);
};

const delTodos = () => {
  todos.value = [];
};

</script>

La principal diferencia es que en Composition API debemos usar ref() para definir las variables reactivas y acceder a su valor con .value mientras que en Options API todas las variables son reactivas y accedemos directamente a ellas con this.. Sin embargo, en el template de ambos casos accedemos a las variables directamente sin usar .value ni this..

Puedes ver la aplicación en: https://play.vuejs.org/#eNqVVdtO4zAQ/ZVRXpqKbLJcnkpAC4iVQLvsakG8EB7cZkoNjp21nVKE+lF8Az+2YychaSlI26dmbj5zfGb8HByVZTyvMBgFqZloXlowaKvyMJO8KJW28Awap7CEqVYFDCh0sJ/JTE6UNBasypWBAxcS3mQSKJrnI9iOwHIrcARZ8AOZlnDO5uzS18+CCHIlyTdlwiAsI+gSd94lXlf4acZuL+O3YE/AtKpkDpzOvPzO81z08q2u1tL3eunHFRc5GFWgnXF5B+wR3cd6eiZvh0RBTYDExyvioKFgMBjuA/06glieN/5wCAeH8OyO5lMIm8R4zkSFsdW8CIfD2g1Q5xZscZZTpie5CdSYVxMMQ/IRcnL4qj+ZncVk6swxz4cRfB3W9foVysrMwrp5f8BWd1mbMK1R7zp3FVdCCePAqQIcOcuePHIUbffcYhHRreS4WCWCIqdcF+HgmhlgMFZaMw2CgWUaWRYMYAtccuxB0seAbB1V/dZMKTiR4w+JYLvGuhmSE+3ajXwIRHBjGSXWiMzmw6neze3qgWlSTxTNEn1QC6VgFukLIM353P+hv7OdwxNl/JkzNkENaCwCe31RozQhZxNWCZh/4dODLKhPFSjv7CwLGj9FCE4RU6UppKa7YZtGwaf0Yl00G6PoGcjEZVnRTD+VSCUmM5w8jNUiC6hqoYg3MvqLcIJYqeX6wTd4H8XQyJHsuptcElG9CgmVWK1pSiapKJLw/quQy+t3mqy3mo4ra5WEbxPSywNBbkSxIlJCf+wlkCZ1eEd0Inh7K0nVFk7LFuuFoot8atQCf0kbhTLWV3J7tUd1R2wzT2+UvYPYLBIKOHp9YTlfh5WO9UepreTfOnJyIFk3yl4pRNdAynQbLE16ig2iwBo/IHfxvVGSngs/AiQTVZRcoP5VWk4DlgWjdjgIsxDq8dzb3OqMWruX1gb7vSG1+UWu0aCeu9Xb+ojNO6THw7lPLy9w4R6S1kksVm7Pf+L8Q7tcVA5jHXZMjwTB7sV5tGf+0aPtf2VOFxalaZtyQP10+/gsoIfw5JPWO7i78V67FYLlP0JiXms=

También puedes descargarte el código desde el repositorio de Github.