Todos los artículos

RendimientoIntermedio6 min de lectura

Adoptar Vapor en una app Vue real: interop, componentes y bundle

Cómo migrar una app Vue a Vapor sin reescribirla: activar la interop, migrar componentes hoja, medir y entender el peso del bundle.

Luis Aldair Quispe Rios

Objetivo y requisitos

Hasta aquí vimos qué es Vapor Mode, cómo se mide y qué código genera el compilador. La pregunta que sigue en un proyecto real es otra: ¿cómo lo adopto sin reescribir la aplicación, qué parte migro primero y cuánto peso agrego en el proceso?

Necesitas una aplicación Vue 3 con Vite y Vue Router, y la versión candidata 3.6.0-rc.15 (la última RC al momento de escribir esto). Para el peso medido en este artículo usé 3.6.0-rc.14, indicado en la tabla correspondiente.

npm install vue@3.6.0-rc.15

Como vimos en el primer post, Vue Router 5 declara peer vue ^3.5.34, y npm no acepta una RC dentro de ese rango. Si tu proyecto lo usa, añade un overrides en package.json:

{
  "overrides": {
    "vue": "$vue",
    "@vue/compiler-sfc": "$vue"
  }
}

Primero, decide qué no migrar

Vapor no soporta todo lo que hace Vue. Antes de tocar un componente, revisa si usa alguna de estas piezas. Si la usa, ese componente se queda en Virtual DOM por ahora:

  • Options API. Solo <script setup> o un SFC con solo template.

  • getCurrentInstance() devuelve null, y no existe app.config.globalProperties.

  • v-memo y eventos @vue:xxx.

  • Render functions y JSX. Son componentes Virtual DOM y necesitan interop.

  • Directivas personalizadas. Cambian de comportamiento en Vapor, así que revísalas una por una.

Esta lista sale de las notas de lanzamiento de Vue 3.6. Para tu caso concreto, lo que cuenta es lo que tu código usa.

Paso 1: activa la interop en la app existente

La aplicación sigue montada con createApp, y le agregas vaporInteropPlugin:

import { createApp, vaporInteropPlugin } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(vaporInteropPlugin).use(router).mount('#app')

Con esto, cualquier componente Vapor puede vivir dentro de tu árbol Virtual DOM, y al revés. No cambias ninguna ruta ni ningún store.

Yo lo probé en un laboratorio con Vue Router: el shell de la aplicación es Virtual DOM, con rutas y navegación, y dentro de él un contador <script setup vapor> funciona igual que cualquier componente. Al pulsar sus botones, el estado se actualiza y el resto del shell sigue igual.

Paso 2: migra un componente hoja

El primer candidato es un componente hoja, sin muchos hijos y sin lógica compleja: una celda de tabla, un badge, un item de lista. Migrarlo es cambiar una palabra:

<script setup vapor lang="ts">
import { ref } from 'vue'

const props = defineProps<{ label: string }>()
const emit = defineEmits<{ select: [id: string] }>()
const isOpen = ref(false)
</script>

<template>
  <button type="button" @click="isOpen = !isOpen" :aria-expanded="isOpen">
    {{ props.label }}
  </button>
</template>

La API se mantiene: defineProps, defineEmits, ref. Los componentes Virtual DOM que lo usan no necesitan saber que cambió.

Las notas de lanzamiento documentan que props, eventos y slots pasan entre los dos mundos. Eso no reemplaza tu prueba: antes de migrar un componente, verifica en tu app los casos que realmente usas, como un v-model sobre un componente o un slot con scope.

Paso 3: mide antes de seguir

Migrar no garantiza una mejora. Antes de seguir con más componentes, mide la pantalla concreta con la misma metodología del benchmark: build de producción, varias corridas, mediana, y la métrica que refleje lo que el usuario nota.

Si la mejora no aparece, deja el componente en Virtual DOM. Migrar sin medir agrega complejidad de interop sin beneficio.

Regiones separadas, no anidamiento mixto

Una regla que sale de las recomendaciones del equipo: mantén las regiones Vapor separadas de las regiones Virtual DOM, en vez de alternarlas capa por capa. Un componente Vapor dentro de un componente Virtual DOM dentro de un componente Vapor funciona en teoría, pero es el caso donde más bordes aparecen.

En la práctica, esto significa migrar una tabla completa, un panel o un micro-frontend entero, no cada componente suelto.

El peso: la trampa del modo mixto

Este es el dato más importante para una aplicación existente. Medí un contador mínimo, compilado con Vite y comprimido con gzip, en tres variantes (3.6.0-rc.14):

Variante

Flags por defecto

Sin Options API

Virtual DOM (createApp)

25,1 kB

21,9 kB

Solo Vapor (createVaporApp)

17,8 kB

17,8 kB

Vapor dentro de app Virtual DOM (vaporInteropPlugin)

45,7 kB

42,7 kB

Hay tres lecturas:

  1. Una aplicación 100 % Vapor pesa menos. Un 29 % menos que Virtual DOM con las flags por defecto, y un 19 % menos si desactivas la Options API en Virtual DOM. Esa es la comparación justa, porque Vapor no soporta Options API.

  2. Con interop, el bundle casi se duplica. Un 82 % más que Virtual DOM solo, y un 2,6 veces más que Vapor puro, porque se cargan los dos runtimes.

  3. En una app existente, el peso sube antes de bajar. La ganancia de tamaño llega cuando eliminas el runtime de Virtual DOM, es decir, cuando toda la aplicación (o micro-frontend) es Vapor. Mientras convives con los dos, pagas los dos.

Por eso, la adopción gradual gana en rendimiento de las partes migradas, pero no en peso. Si el peso inicial es tu problema, la migración completa de un micro-frontend tiene más sentido que migrar componentes sueltos.

Estas cifras son de rc.14 y son mías: el equipo no publica un tamaño base oficial. Las puedes repetir con el script measure:bundle del laboratorio.

Lo que hay que vigilar

  • DevTools y Vue Test Utils. En el roadmap de Vapor siguen como pendientes. Revisa si tu flujo de pruebas depende de ellos antes de migrar.

  • Transition, KeepAlive y Teleport. Tienen versión Vapor, pero prueba los casos que usas: animaciones de salida, componentes cacheados y destinos de Teleport.

  • Correcciones entre RC. Solo en rc.15 (12 de noviembre) hay decenas de correcciones de Vapor, incluidas varias sobre interop: por ejemplo, componentes interop que se mueven dentro de un KeepAlive o props de custom elements que se aplican juntas. Si pruebas la interop con un RC anterior, repite las pruebas con rc.15.

  • Suspense. No tiene versión propia de Vapor. Un componente Vapor puede vivir dentro de un <Suspense> de Virtual DOM.

Comprueba el resultado

En tu aplicación:

  1. Activa vaporInteropPlugin y confirma que la aplicación arranca y navega sin errores en consola.

  2. Migra un componente hoja y verifica sus props, eventos y slots con los casos reales de tu app.

  3. Ejecuta npm run build y npm run preview, y compara el tamaño del bundle con la versión anterior.

  4. Si la migración afecta a una pantalla crítica, repite las mediciones de rendimiento de esa pantalla.

Errores frecuentes

  • Migrar componentes sueltos en todas partes. Multiplicas las fronteras entre runtimes y los casos que verificar, sin ganar peso.

  • Asumir que la interop es gratis. Mezclar runtimes sube el bundle, y las fronteras son donde aparecen los errores.

  • Migrar sin medir. Si la pantalla no mejora, la complejidad extra no se justifica.

  • Usar un RC sin repetir las pruebas de interop. Las correcciones de rc.15 tocan justo ese punto.

Cuándo adoptarlo hoy

  • Para una pantalla con muchas actualizaciones. Tablas grandes, dashboards, editores: donde el trabajo del framework es el cuello de botella.

  • Para un micro-frontend nuevo o pequeño. Donde toda la aplicación puede ser Vapor y el peso también mejora.

  • No para migrar todo de una vez. Es una RC, y la adopción gradual pide medir cada paso.

Si quieres el detalle del compilador que hay detrás de estos componentes, el post sobre v-if, v-for, v-model y slots muestra la salida real de cada patrón.

Sigue leyendo