12k
All articles

Qu'est-ce que le mode Vapor de Vue ?

Vue Vapor Mode expliqué : compilation des SFC en opérations DOM directes, changements de Vue 3.6 RC et pièges des événements et slots.

OpenReplay Team
OpenReplay Team
Qu'est-ce que le mode Vapor de Vue ?

Le mode Vapor de Vue est un mode de compilation destiné aux composants monofichiers Vue qui transforme les templates en opérations DOM directes : le rendu et les mises à jour s’effectuent donc sans création ni comparaison (diffing) d’un DOM virtuel, ce qui réduit la taille de base du bundle, le coût des mises à jour et la consommation mémoire.

Vapor est présenté en conférence depuis un certain temps déjà, mais aucune page ne lui est encore consacrée dans la documentation de Vue. Les détails se trouvent dans les notes de version du cœur de Vue.

Cet article explique ce que le mode Vapor change dans le fonctionnement de vos composants, et où il se situe dans le cycle de publication. Il aborde également deux comportements propres à Vapor qui échouent silencieusement et dans lesquels il est facile de tomber.

Points clés à retenir

  • Le mode Vapor compile les SFC Vue en opérations DOM directes plutôt qu’en création et comparaison de VNodes, ce qui explique la réduction de la taille du bundle et la rapidité accrue des mises à jour.
  • Le mode Vapor est fonctionnellement complet dans les versions candidates de Vue 3.6, mais il n’est pas stable : la branche 3.6 est toujours en préversion, avec la v3.6.0-rc.9 (18 septembre 2026) marquée Pre-release sur GitHub, tandis que l’étiquette Latest reste sur la branche 3.5 avec la v3.5.43 (17 septembre 2026).
  • Vapor s’active composant par composant via <script setup vapor>, <script vapor> ou <template vapor>, et ne fonctionne que sur les SFC ne contenant qu’un template et sur ceux utilisant script setup ; l’Options API n’est pas prise en charge.
  • createVaporApp() monte une application Vapor pure qui ne charge jamais le runtime du DOM virtuel ; installer vaporInteropPlugin pour héberger des composants VDOM réintroduit ce runtime et annule le gain de taille.
  • Les composants Vapor n’ont ni VNodes ni proxy d’instance publique : getCurrentInstance() renvoie null, app.config.globalProperties ne s’applique pas, et les refs de template pointant vers un composant n’exposent plus $el, $props, $attrs, $slots ni $refs.

Comment fonctionne le mode Vapor de Vue ?

La note de version v3.6.0-rc.1 présente Vapor comme une seconde façon de compiler les composants monofichiers, visant un bundle de départ plus léger et de meilleures performances. Rien de tout cela ne se produit par défaut : c’est vous qui l’activez, un composant à la fois, et la partie de l’API Vue couverte se comporte globalement comme vous vous y attendez déjà. Le code source que vous écrivez ne change pas. Ce qui change, c’est la sortie : au lieu d’une fonction de rendu produisant des VNodes que le runtime compare à l’arbre précédent, le compilateur génère du code qui crée les nœuds une seule fois et relie chaque dépendance réactive à la mise à jour DOM précise qu’elle contrôle.

Supprimer le DOM virtuel élimine trois coûts d’un coup. Le runtime de diffing lui-même n’a jamais besoin d’être embarqué dans une application Vapor pure, le bundle de base est donc plus léger. Les mises à jour évitent l’allocation de VNodes et la comparaison d’arbres : une modification d’une ref touche un nœud texte ou un attribut. Et comme aucun arbre fantôme n’est conservé entre les rendus, l’empreinte mémoire par composant diminue.

Le composant lui-même ressemble à du code Composition API ordinaire :

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

Seul l’attribut vapor distingue ce composant du même composant compilé en mode VDOM.

État de publication : fonctionnellement complet, mais pas stable

Le mode Vapor n’est pas encore disponible dans une version stable de Vue. Sur la liste des releases de vuejs/core, la branche 3.6 en est toujours au stade des versions candidates : la v3.6.0-rc.9, publiée le 18 septembre 2026, porte l’étiquette Pre-release, tandis que l’étiquette Latest revient à la v3.5.43, publiée le 17 septembre 2026. npm confirme : le dist-tag latest du paquet vue pointe vers la 3.5.43, et les versions candidates sont accessibles derrière le tag distinct rc. Il n’existe aucun tag stable 3.6.0.

Ce qui est vrai est plus restreint et facile à confondre avec une sortie officielle. La note de version v3.6.0-rc.1 indique que le mode Vapor est fonctionnellement complet dans la RC de Vue 3.6, ce qui explique d’ailleurs le passage de la branche 3.6 en versions candidates. « Fonctionnellement complet » décrit le périmètre, pas la stabilité. La politique de publication de Vue traite toutes les préversions de la même manière : instables, destinées à permettre aux équipes de tester l’intégration d’un build dans leur stack plutôt qu’à être exécutées en production, et libres de rompre la compatibilité d’un build à l’autre. Si vous en installez une, figez la version exacte.

Comment activer le mode Vapor ?

Vapor s’active par composant, pas par projet. Deux types de composants sont éligibles : un composant monofichier ne contenant qu’un template, et un composant écrit avec script setup. Les composants utilisant l’Options API ne peuvent tout simplement pas être compilés en Vapor. Il existe trois façons de marquer un composant éligible : la forme complète <script setup vapor>, son raccourci <script vapor>, et un marqueur vapor sur la balise template, qui compile l’ensemble du fichier en 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 conséquence pratique est une étape d’audit. Tout composant encore écrit avec data, methods ou mounted devra être converti en script setup avant que le drapeau vapor n’ait le moindre effet.

Peut-on mélanger composants Vapor et composants à DOM virtuel ?

Les composants Vapor et ceux à DOM virtuel peuvent cohabiter, et la façon dont vous montez l’application détermine ce qui se retrouve dans le bundle. Si tous les composants sont en Vapor, montez l’application avec createVaporApp() : cette voie exclut le runtime du DOM virtuel du build, d’où la forte baisse de la taille de base. Une application montée avec createApp() doit installer vaporInteropPlugin avant de pouvoir rendre un enfant Vapor. Une application Vapor peut installer ce même plugin pour héberger des enfants à DOM virtuel, mais cela réintroduit le runtime et sacrifie l’essentiel du gain de taille, comme l’explique la note de version 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 composant écrit sous forme de fonction de rendu ou en JSX est également un composant à DOM virtuel : il nécessite donc l’interopérabilité au sein d’une application Vapor. Cette interopérabilité n’est toutefois pas sans contrepartie. L’imbrication d’un mode dans l’autre gère les props, les événements et les slots ordinaires, mais pas encore tous les cas limites, et une bibliothèque de composants bâtie sur le DOM virtuel peut toujours se comporter de façon inattendue sous Vapor. La recommandation de l’équipe Vue est d’attribuer un seul mode de rendu à chaque zone de l’application et de limiter au maximum les imbrications mixtes.

À quoi renoncent les composants Vapor ?

Un composant Vapor n’a ni VNodes ni proxy d’instance publique. Chaque entrée de la liste des éléments non pris en charge dans la note de version rc.1 découle de cette seule frontière, et chaque point a une conséquence concrète :

Non pris en charge dans VaporCe qui casse réellement
Options APILes composants utilisant data, methods ou les options de cycle de vie ne peuvent tout simplement pas être compilés en Vapor.
app.config.globalPropertiesLes variables globales injectées par les plugins sont absentes dans les composants Vapor ; injectez explicitement ce dont vous avez besoin.
getCurrentInstance()Renvoie null : le code tiers qui cherche à accéder à l’instance interne échoue donc dans les composants Vapor.
Événements de cycle de vie d’élément @vue:xxxLes hooks par élément disparaissent ; utilisez une ref de template accompagnée de onMounted.
v-memoL’échappatoire de mémoïsation manuelle n’est pas disponible et doit être supprimée.
$el, $props, $attrs, $slots, $refs sur les refs de template de composantLes schémas où le parent accède à l’enfant cassent ; déplacez le contrat vers les props et les emits.

La note est également prudente quant au degré de correspondance. Vapor vise à se comporter comme le DOM virtuel, mais les deux moteurs de rendu sont construits de manière si différente que de petits écarts sont attendus dans les cas limites ; et un écart aussi minime n’est considéré comme un changement cassant que si l’ancien comportement était documenté.

Deux pièges à connaître avant de se lancer

Événements délégués et stopPropagation()

Dans la conception de la rc.1, les événements pouvant être délégués sont traités au niveau du document. L’élément conserve son gestionnaire, mais rien n’est lié à l’élément lui-même. Un unique écouteur sur le document fait tout le travail : il suit le trajet emprunté par l’événement et déclenche tout gestionnaire rencontré en chemin. Si un ancêtre appelle stopPropagation() lors de la remontée, l’événement n’atteint jamais le document, et ce gestionnaire ne s’exécute donc jamais. Trois formes contournent la délégation et attachent l’écouteur directement à l’élément : @[event]="onClick", v-bind="{ onClick }" et 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>

Ce mode de défaillance est silencieux. Aucune exception n’est levée, rien n’est journalisé, et la supervision des erreurs signale une session saine pendant que l’utilisateur clique sur un contrôle qui ne fait rien. Le session replay est la technique qui permet de le mettre au jour, car vous pouvez guetter un clic suivi d’aucune mutation du DOM — la signature d’un gestionnaire qui ne s’est jamais exécuté. Le même constat vaut aux frontières Vapor/VDOM, où les lacunes d’interopérabilité se manifestent généralement par des anomalies de rendu plutôt que par des exceptions.

L’essentiel des évolutions au fil de la branche RC concerne l’hydratation et les slots, avec seulement quelques correctifs sur la façon dont les écouteurs d’événements sont fusionnés ; figez donc une RC précise et consultez le CHANGELOG de la branche minor pour la version que vous installez, plutôt que de supposer que la description de la rc.1 s’applique toujours.

slots.default() effectue le rendu, il ne fait pas un état des lieux

Dans Vapor, appeler slots.default() n’est pas une simple consultation gratuite du slot. Cet appel exécute le code de rendu du slot, ce qui peut créer des Blocks et des nœuds DOM, mettre en place des effets réactifs et prendre possession du DOM déjà envoyé par le serveur si la page est en cours d’hydratation. L’habitude courante en VDOM d’appeler un slot pour décider s’il faut afficher un contenu de repli a donc des conséquences sous 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>

Exprimez la décision dans le template et laissez celui-ci gérer le rendu du slot :

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

Qui devrait essayer le mode Vapor dès maintenant ?

La note de version identifie deux usages à ce stade : intégrer Vapor dans une partie d’une application existante, par exemple une page où la vitesse de rendu compte, et écrire une petite application entièrement en Vapor dès le départ. Le corollaire mérite d’être énoncé clairement. Un projet soumis à une politique de dépendances stables uniquement, un écran bâti sur une bibliothèque de composants VDOM et une base de code encore sur l’Options API sont autant de mauvais premiers candidats : le premier ne peut pas installer de préversion, et les deux autres se situent précisément là où l’interopérabilité et la liste des éléments non pris en charge posent problème.

Anticiper l’arrivée du mode Vapor

Considérez le mode Vapor comme un changement de compilateur que vous pouvez évaluer dès aujourd’hui sur un seul écran, et non comme une migration à planifier. Figez une version candidate précise, convertissez une unique page riche en listes ou en animations en <script setup vapor>, et consultez la liste des releases de vuejs/core avant de planifier quoi que ce soit autour d’une version 3.6 stable.

FAQ

Le mode Vapor fonctionne-t-il avec Nuxt ?

Oui, à titre expérimental, et uniquement à partir de Vue 3.6. Nuxt conserve la racine de l'application sur le DOM virtuel et vous permet de marquer des composants ou des pages isolés comme Vapor, ce qui autorise une adoption progressive. Une application Nuxt entièrement en Vapor n'est pas encore possible, une ref de template pointant vers un composant Vapor ne vous donnera pas l'élément, et si la version de Vue installée est plus ancienne, Nuxt émet un avertissement et désactive à nouveau l'option.

Le mode Vapor est-il nécessaire pour bénéficier des améliorations de performance de la réactivité de Vue 3.6 ?

Non. La branche 3.6 reconstruit le cœur de réactivité de Vue sur la base d'alien-signals, et les gains obtenus en vitesse et en consommation mémoire s'appliquent à toute application sur cette branche, sans rien à activer. Le mode Vapor est un changement de compilation distinct, appliqué composant par composant : une application standard à DOM virtuel tournant sur la 3.6 bénéficie donc déjà des améliorations de réactivité sans activer Vapor où que ce soit.

Le mode Vapor prend-il en charge le rendu côté serveur et l'hydratation ?

L'hydratation SSR fait partie de l'ensemble fonctionnel de Vapor dans les versions candidates de Vue 3.6, après des builds alpha initiaux qui en étaient dépourvus. La justesse de l'hydratation a été l'un des domaines les plus retouchés tout au long de la branche de préversion : figez donc une version candidate précise et testez la sortie serveur, l'hydratation et la récupération après divergence sur une page réelle, plutôt que de présumer une parité avec le rendu à DOM virtuel.

Une bibliothèque de composants tierce fonctionnera-t-elle dans un composant Vapor ?

Testez-la avant de vous engager. Les composants livrés sous forme de fonctions de rendu ou de JSX restent des composants à DOM virtuel et nécessitent l'interopérabilité ; et le code d'une bibliothèque appelant getCurrentInstance ou lisant globalProperties ne trouvera rien dans un composant Vapor. Tout ce qui repose sur provide et inject plutôt que sur l'instance de composant continue généralement de fonctionner, ce qui explique pourquoi Nuxt indique que la plupart de ses propres composables et composants intégrés ne nécessitent aucune modification sous 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.