materials

Single File Components

Introducción

La utilidad de separar nuestra aplicación en componentes es que cada uno de ellos puede 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 Component (SFC).

Recordatorio de cómo separar componentes en ficheros

Como vimos en la unidad anterior, en el fichero en que definimos el componente exportamos un objeto con las opciones del componente (el segundo parámetro del app.component()):

export default{
    props: ['todo'],
    template: 
      `<li @dblclick="delTodo">
        <label>
          <input type="checkbox" v-model="todo.done">
          <del v-if="todo.done">
            
          </del>
          <span v-else>
            
          </span>
        </label>
      </li>`,
    methods: {
      delTodo() {
        alert('Quiero borrar "' + this.todo.title + '"');
      }
    }
  }

Y donde queramos usarlo (puede ser en otro componente o en la instancia raíz de Vue) debemos:

El fichero main.js de nuestra aplicación de la Lista de tareas quedaba:

import TodoList from './TodoList.js'
import TodoAdd from './TodoAdd.js'
import TodoDellAll from './TodoDellAll.js'

var myApp=Vue.createApp({
    components:  {
        TodoList,
        TodoAdd,
        TodoDellAll,
    }
})

Recuerda que para que el navegador entienda la sentencia import debemos indicar que el script que lo contiene es de tipo module:

  <script type="module" src="main.js"></script>

Solución del ejemplo

Podéis ver aquí cómo quedará nuestra aplicación de ejemplo con los componentes separados en ficheros:

Crear un proyecto Vue

Aunque puede usarse Vue como hemos visto, enlazándolo directamente en el index.html lo más habitual es crear un nuevo proyecto para la aplicación que vamos a desarrollar usando npm y Vite. Esto:

Creación de un nuevo proyecto

Para crear un nuevo proyecto ejecutamos:

npm init vue@latest

Al ejecutar este comando se nos pregunta el nombre del proyecto a crear y si queremos usar o no determinadas herramientas (más adelante veremos qué es cada una, de momento decimos que No) y se creará el directorio para el mismo con el package.json del proyecto en su interior con su configuración.

Nuevo Proyecto

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 vismo 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. La página generada es:

Proyecto de plantilla simple

El proyecto creado usa Vite, que es un bundler más eficiente que webpack (que es el que se usaba en versiones anteriores de Vue) a la hora de gestionar nuestro código tanto en desarrollo como en producción.

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:

Estructura de nuestra aplicación

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

Fichero main.js:

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

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.

Fichero 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 y ).

En el siguiente apartado explicaremos qué es un SFC y qué partes lo forman. De momento veamos qué contiene cada 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 (las imágenes y otros ficheros como ficheros .css se guardan dentro de /src/assets/) y los subcomponentes HelloWorld y TheWelcome.

script

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

Importa y registra el componente HelloWorld que se muestra en el template. Está en forma de Composition API. En forma de Options API sería:

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

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

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, no a sus subcomponentes.

Fichero 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:

template

<template>
  <div class="greetings">
    <h1 class="green"></h1>
    <h3>
      You’ve successfully created a project with
      <a href="https://vitejs.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 y varios apartados con listas.

script

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

Recibe el parámetro msg que es de tipo String. En sintaxis Options API sería:

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: {
      type: String,
      required: true
    }
  }
}
</script>

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.

El fichero SFC (Single File Component)

Guardar los componentes en ficheros .js como hicimos en el tema anterior genera varios problemas:

Por tanto eso puede ser adecuado para proyectos muy pequeños pero no lo es cuando estos empiezan a crecer.

La solución es guardar cada componente en un único fichero (SFC), que tendrá 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 Volar 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.

Secciones de un Single File Component

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).

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">
...

<script>

Aquí definimos y exportamos el componente, que será un objeto con diferentes propiedades. Si utiliza subcomponentes hay que importarlos antes de definir el objeto y registrarlos dentro de este.

Entre las propiedades que puede tener el objeto están:

<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.

La forma más común de asignar estilos a elementos es usando clases. Para conseguir que su estilo cambie fácilmente podemos asignar al elemento clases dinámicas que hagan referencia a variables del componente. Ej.:

<template>
  <p :class="[decoration, {weight: isBold}]">Hi!</p>
</template>

<script>
export default {
  data() {
    return {
      decoration: 'underline',
      isBold: true
    }
  }
}
</script>

<style lang="css">
  .underline { text-decoration: underline; }
  .weight { font-weight: bold; }
</style>

El párrafo tendrá la clase indicada en la variable decoration (en este caso underline) y además como el valor de isBold es verdadero tendrá la clase weight. Hacer que cambien las clases del elemento es tan sencillo como cambiar el valor de las variables.

Podemos ver las diferentes maneras de asignar clases a los elementos HTML en la documentación de Vue.

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 la documentación del proyecto
</custom1>

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. Lo normal es 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 poderlo usar 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

Podemos utilizar Bootstrap 5 directamente en Vue ya que esta versión no necesita de la librería jQuery.

Para usarlo simplemente 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"

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.7.2/font/bootstrap-icons.css");

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

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

Respecto a los componentes de Bootstrap, para que funcionen sólo tenemos que usar los atributos data-bs- (recuerda que muchos de estos componenetes 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:

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).

Si estamos usando webpack no podemos ver nuestro código directamente sino que nuestros fichero se localizan dentro del apartado webpack:

Depurar en la consola

Recordad que si hemos instalado 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 que la aplicación que estamos desarrollando tiene los componentes:

Para transformar esto en SFC simplemente crearemos un fichero para cada uno de estos componentes. Nuestro anterior index.html será el <template> del componente principal App.vue, que en un sección <script> deberá importar y registrar cada uno de los componentes usados en el template (todo-list, todo-add y todo-del-all).

Solución: