12k
All articles

¿Qué es Vue Vapor Mode?

Vue Vapor Mode explicado: compila SFC a operaciones DOM directas, qué cambia en Vue 3.6 RC y los riesgos con eventos y slots.

OpenReplay Team
OpenReplay Team
¿Qué es Vue Vapor Mode?

Vue Vapor Mode es un modo de compilación para componentes single-file de Vue que convierte las plantillas en operaciones directas sobre el DOM, de modo que el renderizado y las actualizaciones ocurren sin crear ni comparar un DOM virtual, lo que reduce el tamaño base del bundle, el coste de las actualizaciones y el uso de memoria.

Vapor lleva ya un tiempo mostrándose en conferencias, pero todavía no existe una página sobre él en la documentación de Vue. Los detalles se encuentran en las notas de publicación del core de Vue.

Este artículo explica qué cambia Vapor Mode en la forma en que se ejecutan tus componentes y en qué punto del ciclo de publicación se encuentra. También aborda dos comportamientos de Vapor que fallan de forma silenciosa y en los que resulta fácil caer.

Puntos clave

  • Vapor Mode compila los SFC de Vue en operaciones directas sobre el DOM en lugar de crear y comparar VNodes, y de ahí provienen su bundle más pequeño y sus actualizaciones más rápidas.
  • Vapor Mode está feature-complete en los release candidates de Vue 3.6, pero no es estable: la línea 3.6 sigue en prelanzamiento, con la v3.6.0-rc.9 (18 de septiembre de 2026) marcada como Pre-release en GitHub, mientras que la etiqueta Latest permanece en la línea 3.5 con la v3.5.43 (17 de septiembre de 2026).
  • Vapor se activa de forma opcional por componente mediante <script setup vapor>, <script vapor> o <template vapor>, y solo funciona en SFC compuestos únicamente por una plantilla y en SFC que usan script setup; la Options API no está soportada.
  • createVaporApp() monta una aplicación Vapor pura que nunca carga el runtime del DOM virtual; instalar vaporInteropPlugin para alojar componentes VDOM vuelve a incorporar ese runtime y anula el beneficio de tamaño.
  • Los componentes Vapor no tienen VNodes ni proxy público de instancia, por lo que getCurrentInstance() devuelve null, app.config.globalProperties no se aplica y las template refs de componentes ya no exponen $el, $props, $attrs, $slots ni $refs.

¿Cómo funciona Vue Vapor Mode?

La nota de publicación de la v3.6.0-rc.1 presenta Vapor como una segunda forma de compilar componentes single-file, orientada a lograr un bundle inicial más pequeño y un mejor rendimiento. Nada de esto ocurre por defecto: lo activas tú, componente a componente, y la parte de la API de Vue que cubre se comporta en su mayoría tal como ya esperas. El código fuente que escribes no cambia. Lo que cambia es la salida: en lugar de una función de render que produce VNodes para que el runtime los compare con el árbol anterior, el compilador emite código que crea los nodos una sola vez y conecta cada dependencia reactiva con la actualización concreta del DOM que controla.

Eliminar el DOM virtual elimina tres costes a la vez. El propio runtime de diffing nunca necesita incluirse en una aplicación Vapor pura, por lo que el bundle base es más pequeño. Las actualizaciones evitan la asignación de VNodes y la comparación de árboles, de modo que un cambio en una ref afecta a un nodo de texto o a un atributo. Y como no se retiene ningún árbol paralelo entre renders, la huella de memoria por componente disminuye.

El componente en sí tiene el aspecto de código normal de la Composition API:

<script setup vapor>
import { ref, computed } from 'vue'

const count = ref(0)
const doubled = computed(() => count.value * 2)
</script>

<template>
  <button @click="count++">{{ count }} / {{ doubled }}</button>
</template>

Lo único que lo diferencia del mismo componente compilado en modo VDOM es el atributo vapor.

Estado de publicación: feature-complete, no estable

Vapor Mode no se ha publicado en ninguna versión estable de Vue. En la lista de releases de vuejs/core, la línea 3.6 sigue en release candidates: la v3.6.0-rc.9, publicada el 18 de septiembre de 2026, lleva la etiqueta Pre-release, mientras que la etiqueta Latest corresponde a la v3.5.43, publicada el 17 de septiembre de 2026. npm coincide: el dist-tag latest del paquete vue apunta a la 3.5.43, y los release candidates quedan detrás de la etiqueta rc independiente. No existe una etiqueta estable 3.6.0.

Lo que sí es cierto es más limitado y resulta fácil de confundir con una publicación definitiva. La nota de publicación de la v3.6.0-rc.1 indica que Vapor Mode está feature-complete en la RC de Vue 3.6, razón por la que la línea 3.6 pasó a release candidates. Feature-complete describe el alcance, no la estabilidad. La propia política de publicación de Vue trata todos los prelanzamientos igual: son inestables, existen para que los equipos prueben cómo encaja una build en su stack en lugar de ejecutarla en producción, y pueden romper la compatibilidad de una build a otra. Si instalas una, fija la versión exacta.

¿Cómo se activa Vapor Mode?

Vapor se habilita por componente, no por proyecto. Hay dos tipos de componentes que cumplen los requisitos: un componente single-file que solo contiene una plantilla y uno escrito con script setup. Los componentes basados en la Options API no pueden compilarse a Vapor en absoluto. Existen tres formas de marcar un componente que sí cumple los requisitos: la forma completa <script setup vapor>, su abreviatura <script vapor> y un marcador vapor en la etiqueta template, que compila todo el archivo como Vapor.

<!-- Form 1: the explicit form -->
<script setup vapor>
  // ...
</script>

<!-- Form 2: shorthand for <script setup vapor> -->
<script vapor>
  // ...
</script>

<!-- Form 3: marks the whole SFC as Vapor -->
<template vapor>
  <!-- ... -->
</template>

La consecuencia práctica es un paso de auditoría. Cualquier componente que siga escrito con data, methods o mounted tendrá que convertirse a script setup antes de que el flag vapor sirva de algo.

¿Se pueden mezclar componentes Vapor y de DOM virtual?

Los componentes Vapor y de DOM virtual pueden mezclarse, y la forma en que montas la aplicación determina qué acaba en el bundle. Si todos los componentes son Vapor, monta con createVaporApp(): esa vía deja fuera de la build el runtime del DOM virtual, y de ahí viene la fuerte reducción del tamaño base. Una aplicación montada con createApp() debe instalar vaporInteropPlugin antes de poder renderizar un hijo Vapor. Una aplicación Vapor puede instalar ese mismo plugin para alojar hijos de DOM virtual, pero al hacerlo vuelve a incorporar el runtime y renuncia a la mayor parte del ahorro de tamaño, tal como explica la nota de publicación de la 3.6.0-rc.1.

// Pure Vapor: the VDOM runtime is never loaded
import { createVaporApp } from 'vue'
import App from './App.vue'
createVaporApp(App).mount('#app')

// Existing VDOM app hosting Vapor components
import { createApp, vaporInteropPlugin } from 'vue'
import App from './App.vue'
createApp(App).use(vaporInteropPlugin).mount('#app')

Un componente escrito como función de render o en JSX también es un componente de DOM virtual, así que necesita interoperabilidad dentro de una aplicación Vapor. La interoperabilidad no es gratis. Anidar un modo dentro del otro maneja props, eventos y slots corrientes, pero todavía no todos los casos límite, y una librería de componentes construida sobre el DOM virtual aún puede comportarse mal bajo Vapor. El consejo del equipo de Vue es asignar a cada región de una aplicación un único modo de renderizado y reducir al mínimo el anidamiento mixto.

¿A qué renuncian los componentes Vapor?

Un componente Vapor no tiene VNodes ni proxy público de instancia. Cada entrada de la lista de elementos no soportados de la nota de publicación rc.1 se deriva de esa única frontera, y cada punto tiene una consecuencia concreta:

No soportado en VaporQué se rompe en la práctica
Options APILos componentes que usan data, methods u opciones de ciclo de vida no pueden compilarse a Vapor en absoluto.
app.config.globalPropertiesLos valores globales inyectados por plugins no están presentes dentro de los componentes Vapor; inyecta explícitamente lo que necesites.
getCurrentInstance()Devuelve null, por lo que el código de terceros que accede a la instancia interna falla dentro de los componentes Vapor.
Eventos de ciclo de vida de elemento @vue:xxxLos hooks por elemento desaparecen; usa una template ref junto con onMounted.
v-memoLa vía de escape para memoización manual no está disponible y debe eliminarse.
$el, $props, $attrs, $slots, $refs en template refs de componentesLos patrones en los que el padre accede al hijo se rompen; traslada el contrato a props y emits.

La nota también es cuidadosa respecto a cuán estrecha es esa correspondencia. Vapor aspira a comportarse como el DOM virtual, pero los dos renderizadores están construidos de forma tan distinta que cabe esperar pequeñas discrepancias en casos límite, y una discrepancia tan pequeña solo cuenta como breaking change si el comportamiento anterior estaba documentado.

Dos trampas que conviene conocer antes de empezar

Eventos delegados y stopPropagation()

En el diseño de la rc.1, los eventos que pueden delegarse se gestionan a nivel de document. El elemento conserva su handler, pero no se vincula nada al propio elemento. Un único listener en el document hace el trabajo: sigue la ruta que toma el evento y dispara cualquier handler que encuentre por el camino. Si un ancestro llama a stopPropagation() durante la propagación hacia arriba, el evento nunca llega a document, así que ese handler nunca se ejecuta. Tres formas evitan la delegación y adjuntan el listener directamente al elemento: @[event]="onClick", v-bind="{ onClick }" y v-on="{ click: onClick }".

<script setup vapor>
const onClick = () => save()
</script>

<template>
  <!-- the ancestor stops propagation, so a delegated handler never fires -->
  <div @click.stop>
    <button @click="onClick">Save</button>
  </div>

  <!-- binds directly to the element instead -->
  <div @click.stop>
    <button v-on="{ click: onClick }">Save</button>
  </div>
</template>

Este modo de fallo es silencioso. No se lanza ninguna excepción, no se registra nada y la monitorización de errores informa de una sesión sana mientras el usuario hace clic en un control que no hace nada. El session replay es la técnica que lo saca a la luz, porque puedes buscar un clic seguido de ninguna mutación del DOM, que es la firma de un handler que nunca se ejecutó. Lo mismo se aplica en las fronteras Vapor/VDOM, donde las carencias de interoperabilidad suelen manifestarse como rarezas de renderizado más que como excepciones.

La mayor parte de los cambios a lo largo de la línea RC se concentra en la hidratación y los slots, con solo un par de correcciones en la forma en que se fusionan los listeners de eventos, así que fija una RC exacta y consulta el CHANGELOG de la rama minor para la versión que instales en lugar de asumir que la descripción de la rc.1 sigue siendo válida.

slots.default() renderiza, no informa

En Vapor, llamar a slots.default() no es una consulta inocua del slot. La llamada ejecuta el código de renderizado del slot, que puede construir Blocks y nodos del DOM, configurar efectos reactivos y tomar posesión del DOM que el servidor ya envió si la página se está hidratando. Por tanto, la costumbre habitual en VDOM de llamar a un slot para decidir si renderizar un fallback tiene consecuencias en Vapor.

<script setup vapor>
import { useSlots } from 'vue'
const slots = useSlots()
// Wrong: this call renders the slot rather than inspecting it
const showFallback = !slots.default?.()
</script>

Expresa la decisión en la plantilla y deja que sea la plantilla la que se encargue de renderizar el slot:

<template>
  <slot>Fallback</slot>
</template>

Quién debería probar Vapor Mode ahora

La nota de publicación menciona dos usos en esta etapa: incorporar Vapor en una parte de una aplicación que ya tienes, como una página donde la velocidad de renderizado importa, y escribir desde el principio una pequeña aplicación nueva en Vapor. Conviene enunciar el corolario con claridad. Un proyecto con una política de dependencias que solo admite versiones estables, una pantalla construida sobre una librería de componentes VDOM y una base de código que sigue en la Options API son todos malos candidatos iniciales, porque el primero ni siquiera puede instalar un prelanzamiento y los otros dos están justo donde más golpean la interoperabilidad y la lista de elementos no soportados.

Planificar en torno a Vapor Mode

Trata Vapor Mode como un cambio de compilador que puedes evaluar hoy en una sola pantalla, no como una migración que se planifica. Fija un release candidate exacto, convierte una única página con muchas listas o mucha animación a <script setup vapor> y consulta la lista de releases de vuejs/core antes de planificar cualquier cosa en torno a una 3.6 estable.

Preguntas frecuentes

¿Funciona Vapor Mode con Nuxt?

Sí, de forma experimental, y solo en Vue 3.6 o posterior. Nuxt mantiene la raíz de la aplicación sobre el DOM virtual y te permite marcar componentes o páginas individuales como Vapor, de modo que la adopción es gradual. Todavía no es posible una aplicación Nuxt íntegramente Vapor, una template ref que apunte a un componente Vapor no te dará el elemento y, si la versión de Vue instalada es más antigua, Nuxt avisa y vuelve a desactivar la opción.

¿Necesito Vapor Mode para obtener las mejoras de rendimiento de la reactividad de Vue 3.6?

No. La línea 3.6 reconstruye el núcleo de reactividad de Vue sobre alien-signals, y las mejoras resultantes en velocidad y uso de memoria se aplican a todas las aplicaciones de esa línea de publicación, sin nada que activar. Vapor Mode es un cambio de compilación independiente y por componente, así que una aplicación estándar de DOM virtual ejecutándose sobre 3.6 ya obtiene las mejoras de reactividad sin habilitar Vapor en ningún sitio.

¿Vapor Mode admite renderizado en servidor e hidratación?

La hidratación SSR está incluida en el conjunto de funcionalidades de Vapor de los release candidates de Vue 3.6, después de que las primeras builds alfa se publicaran sin ella. La corrección de la hidratación ha sido una de las áreas con más cambios a lo largo de la línea de prelanzamiento, así que fija un release candidate exacto y prueba la salida del servidor, la hidratación y la recuperación ante discrepancias en una página real en lugar de asumir paridad con el renderizado de DOM virtual.

¿Funcionará una librería de componentes de terceros dentro de un componente Vapor?

Pruébalo antes de comprometerte. Los componentes distribuidos como funciones de render o JSX siguen siendo componentes de DOM virtual y necesitan interoperabilidad, y el código de librería que llama a getCurrentInstance o lee globalProperties no encuentra nada dentro de un componente Vapor. Todo lo construido sobre provide e inject en lugar de sobre la instancia del componente suele seguir funcionando, y por eso Nuxt afirma que la mayoría de sus propios composables y componentes integrados no requieren cambios bajo Vapor.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.