Vue Vapor Mode — Loại Bỏ Virtual DOM, Tăng Hiệu Năng Gấp 3 Lần

Posted on: 4/23/2026 7:11:03 AM

88% Giảm bundle size so với VDOM truyền thống
~6KB Baseline size của Vapor app
100K Components mount trong ~100ms
0 Virtual DOM diffing ở runtime

Mục lục

Virtual DOM — Giải pháp hay gánh nặng?

Khi React giới thiệu Virtual DOM vào năm 2013, đây được coi là bước đột phá trong lập trình UI. Ý tưởng đơn giản nhưng mạnh mẽ: thay vì thao tác trực tiếp trên DOM thật (chậm), ta xây dựng một bản sao nhẹ (Virtual DOM), so sánh (diff) bản cũ và mới, rồi chỉ cập nhật những phần thay đổi (patch).

Vue.js kế thừa mô hình này từ version 2, và Virtual DOM đã phục vụ tốt trong nhiều năm. Tuy nhiên, khi ứng dụng ngày càng phức tạp, chi phí ẩn của VDOM bắt đầu lộ rõ:

  • Memory overhead: Mỗi component tạo ra một cây VNode trong bộ nhớ — dù chỉ render text tĩnh
  • Diffing cost: Thuật toán reconciliation phải duyệt toàn bộ cây VNode mỗi lần state thay đổi
  • Runtime size: Engine diff/patch chiếm ~50KB trong baseline bundle của Vue 3
  • Hydration penalty: SSR phải tái tạo toàn bộ cây VNode phía client trước khi interactive
flowchart LR
    A[State Change] --> B[Create New VNode Tree]
    B --> C[Diff Old vs New Tree]
    C --> D[Compute Patches]
    D --> E[Apply Patches to Real DOM]
    style A fill:#e94560,stroke:#fff,color:#fff
    style B fill:#f8f9fa,stroke:#e94560,color:#2c3e50
    style C fill:#f8f9fa,stroke:#e94560,color:#2c3e50
    style D fill:#f8f9fa,stroke:#e94560,color:#2c3e50
    style E fill:#16213e,stroke:#fff,color:#fff
  
Quy trình render truyền thống với Virtual DOM — mỗi state change đi qua 4 bước

Các framework thế hệ mới như SvelteSolidJS đã chứng minh rằng Virtual DOM không phải là cách duy nhất — và thường không phải cách hiệu quả nhất — để xây dựng UI reactive. Bằng cách dịch (compile) component thành mã DOM trực tiếp tại build time, chúng loại bỏ hoàn toàn overhead runtime của VDOM.

Và đó chính là con đường Vue đang đi với Vapor Mode.

Vapor Mode là gì?

Vapor Mode là một chiến lược compilation thay thế được giới thiệu trong Vue 3.6, cho phép compile component thành mã JavaScript thao tác DOM trực tiếp — bỏ qua hoàn toàn lớp Virtual DOM.

Điểm mấu chốt

Vapor Mode không phải là một framework mới. Nó là một compilation mode bên trong Vue — bạn vẫn viết <template>, <script setup>, Composition API, ref(), computed() hoàn toàn giống cũ. Chỉ có output của compiler thay đổi.

Tên gọi "Vapor" lấy cảm hứng từ ý tưởng "Virtual DOM evaporates" — Virtual DOM bốc hơi. Thay vì tạo VNode tree rồi diff, Vapor compiler phân tích template tại build time và sinh ra mã imperative cập nhật chính xác DOM node cần thay đổi khi reactive state thay đổi.

flowchart LR
    A[State Change] --> B[Update Exact DOM Node]
    style A fill:#e94560,stroke:#fff,color:#fff
    style B fill:#4CAF50,stroke:#fff,color:#fff
  
Vapor Mode — state change cập nhật trực tiếp DOM node, không qua trung gian

Cơ chế hoạt động: Từ template đến DOM trực tiếp

Để hiểu Vapor Mode hoạt động thế nào, hãy theo dõi pipeline compilation từ đầu đến cuối:

flowchart TD
    A["SFC (.vue file)"] --> B[Vue SFC Parser]
    B --> C[Template AST]
    C --> D{Compilation Mode?}
    D -->|VDOM Mode| E[VDOM Render Function
createVNode, patch, diff] D -->|Vapor Mode| F[Vapor Compiler] F --> G[Static Analysis
Tách dynamic vs static nodes] G --> H[Code Generation
Imperative DOM operations] H --> I[Reactive Bindings
Effect đính vào từng node] style A fill:#e94560,stroke:#fff,color:#fff style D fill:#ff9800,stroke:#fff,color:#fff style E fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50 style F fill:#4CAF50,stroke:#fff,color:#fff style I fill:#16213e,stroke:#fff,color:#fff
Pipeline compilation: cùng một SFC, hai output hoàn toàn khác nhau

Bước 1: Static Analysis

Vapor compiler phân tích template và phân loại từng node thành static (không bao giờ thay đổi) và dynamic (phụ thuộc vào reactive state). Các node static được tạo một lần duy nhất và không bao giờ bị revisit.

Bước 2: Code Generation

Thay vì sinh ra createVNode(), compiler sinh ra các lệnh DOM thuần:

// VDOM mode output (truyền thống)
function render() {
  return createVNode('div', null, [
    createVNode('h1', null, title.value),
    createVNode('p', null, message.value)
  ])
}

// Vapor mode output (mới)
function render() {
  const div = document.createElement('div')
  const h1 = document.createElement('h1')
  const p = document.createElement('p')
  h1.textContent = title.value
  p.textContent = message.value
  div.append(h1, p)

  // Chỉ cập nhật node thực sự thay đổi
  watchEffect(() => { h1.textContent = title.value })
  watchEffect(() => { p.textContent = message.value })

  return div
}

Bước 3: Fine-grained Reactivity

Mỗi biểu thức dynamic được wrap trong một watchEffect riêng biệt, đính trực tiếp vào DOM node cần cập nhật. Khi title thay đổi, chỉ có h1.textContent được gọi — p hoàn toàn không bị ảnh hưởng.

So sánh với SolidJS

Cách tiếp cận này rất giống SolidJS — fine-grained reactivity với DOM trực tiếp. Sự khác biệt: Vue Vapor vẫn dùng ref()/reactive() quen thuộc thay vì createSignal() của Solid, giữ nguyên DX mà Vue developer đã quen.

So sánh compilation output: VDOM vs Vapor

Để thấy rõ sự khác biệt, hãy xem cùng một component đơn giản được compile theo hai mode:

<!-- Component gốc -->
<template>
  <button @click="count++">
    Count: {{ count }}
  </button>
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
Tiêu chí VDOM Mode Vapor Mode
Output type Render function trả về VNode Imperative DOM creation + effect bindings
Khi state thay đổi Tạo VNode mới → diff → patch Effect chạy → cập nhật trực tiếp textContent
Runtime cần thiết @vue/runtime-dom (~50KB) @vue/reactivity + vapor helpers (~6KB)
Memory per component VNode tree + component instance DOM nodes + reactive effects
Static content Hoisted nhưng vẫn qua diff pipeline Tạo một lần, không bao giờ revisit

Benchmark & Hiệu năng thực tế

Dựa trên các benchmark được công bố tại Vue.js Nation 2025 và các test cộng đồng trong 2026:

~50KB → ~6KB Baseline bundle (giảm 88%)
2-3x Faster initial render
~50% Giảm memory consumption
SolidJS-tier Performance ngang hàng
Framework Mount 100K components Baseline Bundle Update Strategy
Vue 3 (VDOM) ~300ms ~50KB VDOM diff + patch
Vue 3.6 (Vapor) ~100ms ~6KB Direct DOM + effects
SolidJS ~95ms ~7KB Fine-grained signals
Svelte 5 ~110ms ~2KB (+ per-component) Compiled reactivity
React 19 ~350ms ~42KB Fiber + concurrent mode

Lưu ý về benchmark

Các con số trên là từ synthetic benchmarks (mount/update micro-test). Hiệu năng thực tế trong ứng dụng production phụ thuộc vào kiến trúc, data flow, và cách sử dụng component. Vapor Mode cho lợi ích lớn nhất ở các ứng dụng có nhiều component nhỏupdate thường xuyên.

Tích hợp vào dự án hiện tại

Một trong những điểm mạnh nhất của Vapor Mode là chiến lược opt-in từng component. Bạn không cần rewrite toàn bộ ứng dụng — chỉ cần đánh dấu component nào muốn chạy Vapor:

Cách 1: Flag trong SFC

<!-- Component này sẽ compile ở Vapor mode -->
<script setup vapor>
import { ref } from 'vue'
const count = ref(0)
</script>

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

Cách 2: Config trong Vite

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue({
      features: {
        vaporMode: true // Toàn bộ project dùng Vapor
      }
    })
  ]
})

Mixed Component Tree

Vue 3.6 cho phép trộn lẫn VDOM component và Vapor component trong cùng một cây component. Điều này nghĩa là bạn có thể:

graph TD
    A["App.vue (VDOM)"] --> B["Header.vue (VDOM)"]
    A --> C["Dashboard.vue (Vapor ⚡)"]
    A --> D["Sidebar.vue (VDOM)"]
    C --> E["Chart.vue (Vapor ⚡)"]
    C --> F["DataTable.vue (Vapor ⚡)"]
    C --> G["FilterPanel.vue (VDOM)"]
    style C fill:#4CAF50,stroke:#fff,color:#fff
    style E fill:#4CAF50,stroke:#fff,color:#fff
    style F fill:#4CAF50,stroke:#fff,color:#fff
    style A fill:#f8f9fa,stroke:#e94560,color:#2c3e50
    style B fill:#f8f9fa,stroke:#e94560,color:#2c3e50
    style D fill:#f8f9fa,stroke:#e94560,color:#2c3e50
    style G fill:#f8f9fa,stroke:#e94560,color:#2c3e50
  
Mixed component tree — Vapor components (xanh) hoạt động cùng VDOM components (xám)

Chiến lược migration khuyến nghị

  1. Bắt đầu với các leaf components (không có children phức tạp)
  2. Ưu tiên components render thường xuyên (list items, real-time data)
  3. Kiểm tra kỹ trước khi chuyển components dùng v-html, dynamic components, hoặc $refs
  4. Đo performance trước và sau bằng Vue DevTools Profiler

Giới hạn & những điều cần biết

Vapor Mode hiện tại (Vue 3.6) vẫn đang trong giai đoạn experimental. Dưới đây là những giới hạn cần nắm:

Giới hạn Chi tiết Workaround
Bắt buộc SFC Chỉ hoạt động với Single File Components (.vue) Chuyển JSX/render function sang SFC
Không hỗ trợ v-html Dynamic HTML injection chưa được support Dùng component VDOM cho phần cần v-html
Dynamic components hạn chế <component :is="..."> chưa đầy đủ Dùng v-if thay thế khi có thể
Root-level $refs Template refs trên root element chưa ổn định Wrap trong thêm một element
Nuxt chưa hỗ trợ Nuxt integration đang trong lộ trình Chỉ dùng trong Vite-based projects
Non-deterministic code Math.random(), Date.now() trong template Đưa vào computed hoặc ref

Vapor Mode trong bức tranh lớn của frontend

Vapor Mode không chỉ là một tính năng kỹ thuật — nó phản ánh xu hướng lớn của frontend development: chuyển công việc từ runtime sang compile time.

2013
React Virtual DOM — Mở đường cho declarative UI, chấp nhận runtime overhead để đổi lấy DX
2016
Svelte ra đời — Chứng minh compiler-first approach khả thi, không cần VDOM
2020
SolidJS — Fine-grained reactivity + compiled output = hiệu năng vượt trội
2024
React Compiler — React cũng bắt đầu dùng compiler để tối ưu, auto-memoization
2025–2026
Vue Vapor Mode — Vue gia nhập "zero VDOM club", giữ nguyên API quen thuộc

Điều đáng chú ý là mỗi framework đi đến compiler-driven approach theo cách riêng:

  • Svelte: Ngôn ngữ riêng, compiler-first từ đầu
  • SolidJS: JSX + signals, không VDOM từ đầu
  • React: Giữ VDOM nhưng thêm compiler layer (React Compiler)
  • Vue Vapor: Opt-in mode, cùng tồn tại với VDOM — linh hoạt nhất

Vue Vapor = Best of Both Worlds?

Vue là framework duy nhất cho phép cùng lúc chạy cả VDOM và no-VDOM components trong một ứng dụng. Bạn không phải chọn "tất cả hoặc không" — migrate dần dần, component by component, đo đạc hiệu quả từng bước.

Kết luận

Vue Vapor Mode đánh dấu bước tiến quan trọng trong hành trình tối ưu hiệu năng của Vue.js. Bằng cách loại bỏ Virtual DOM overhead ở compile time, Vapor Mode mang lại hiệu năng ngang hàng SolidJS và Svelte mà không yêu cầu developer thay đổi cách viết code.

Với chiến lược opt-in linh hoạt, mixed component tree, và giảm bundle size từ ~50KB xuống ~6KB, Vapor Mode đặc biệt phù hợp cho:

  • Ứng dụng mobile-first cần tải nhanh trên mạng yếu
  • Dashboard với hàng trăm component cập nhật real-time
  • Micro-frontends cần footprint nhỏ gọn
  • SSR/SSG applications muốn giảm hydration cost

Dù vẫn đang experimental, Vapor Mode cho thấy tầm nhìn rõ ràng: tương lai của frontend là compiler-driven, và Vue đang ở vị trí thuận lợi nhất khi vừa giữ backward compatibility, vừa đón đầu xu hướng hiệu năng.

Nguồn tham khảo