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()devuelvenull, y no existeapp.config.globalProperties.v-memoy 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 ( | 25,1 kB | 21,9 kB |
Solo Vapor ( | 17,8 kB | 17,8 kB |
Vapor dentro de app Virtual DOM ( | 45,7 kB | 42,7 kB |
Hay tres lecturas:
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.
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.
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 unKeepAliveo props de custom elements que se aplican juntas. Si pruebas la interop con un RC anterior, repite las pruebas conrc.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:
Activa
vaporInteropPluginy confirma que la aplicación arranca y navega sin errores en consola.Migra un componente hoja y verifica sus props, eventos y slots con los casos reales de tu app.
Ejecuta
npm run buildynpm run preview, y compara el tamaño del bundle con la versión anterior.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.15tocan 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
Una idea lleva a otra.
- Medir sin engañarse: benchmark de Virtual DOM frente a Vapor en Vue 3.6Mide Virtual DOM frente a Vapor sin engañarse: apps aisladas, calentamiento, orden alternado, mediana y pruebas en producción.
- Vapor Mode en Vue 3.6: Vue sin Virtual DOM, explicado y medidoActiva Vapor Mode en Vue 3.6 con una sola palabra, mira qué compila y mide cuánto mejora frente al Virtual DOM, con cifras reales.
- Dentro del compilador de Vapor: v-if, v-for, v-model y slots en Vue 3.6Compara el código que genera Vapor y Virtual DOM para v-if, v-for, v-model y slots, con el compilador de Vue 3.6.