跳至主要內容
Skip to content

Vue Compiler 的實現原理

Vue Compiler 是把你寫的 template 轉成 render function 的工具。很多人以為 Vue 的效能只靠 Virtual DOM Diff,但 Vue 3 的重點其實是:compiler 會在編譯階段分析 template,提前標記哪些地方是動態的,讓 runtime 更新時少做很多猜測。

一句話回答:Vue Compiler 的核心流程是 parse -> transform -> generate。它先把 template 解析成 AST,再對 AST 做指令轉換、靜態分析與優化標記,最後產生 render function。runtime 執行 render function 得到 VNode,再交給 renderer patch DOM。


一、 必考觀念

1. Compiler 在 Vue 渲染流程裡的位置

Vue 的完整渲染鏈路可以簡化成:

你寫:

vue
<template>
  <div>Hello {{ name }}</div>
</template>

Compiler 會把它轉成類似:

typescript
function render(_ctx) {
  return createElementVNode(
    'div',
    null,
    'Hello ' + toDisplayString(_ctx.name),
  )
}

實際產物會比這更複雜,但心智模型就是:template 不是直接執行,它會先被編譯成 render function。

2. Vue 有 runtime-only 和 runtime-compiler 版本

Vue 可以分成兩種使用方式:

版本特點
runtime-only不包含 template compiler,體積較小
runtime-compiler瀏覽器中也能即時編譯 template,體積較大

在 Vite / Vue CLI 這類現代建置工具裡,.vue 單檔組件通常會在 build time 被 @vue/compiler-sfc 編譯。所以瀏覽器拿到的是 JavaScript render function,而不是原始 template。

這也是為什麼正式專案通常使用 runtime-only 版本即可。

3. Compiler 三階段

Vue Compiler 的核心可以拆成三步:

階段作用
Parsetemplate 字串轉 AST
Transform走訪 AST,處理指令、表達式與優化資訊
GenerateAST / codegen node 轉 render function 程式碼

本地 Vue 3.5.x 的 @vue/compiler-core 裡也能看到這些核心函式:baseParsetransformgeneratebaseCompile


二、 Parse:template 轉 AST

1. AST 是什麼?

AST 是 Abstract Syntax Tree,抽象語法樹。

例如:

vue
<div id="app">Hello {{ name }}</div>

會被解析成類似這樣的結構:

text
Root
└─ Element div
   ├─ props: id="app"
   ├─ Text "Hello "
   └─ Interpolation name

AST 的價值是:把字串變成結構化資料,後續 transform 才能知道哪些是元素、哪些是文字、哪些是插值、哪些是指令。

2. Parse 會處理哪些節點?

常見節點類型:

節點例子
Root整個 template 根節點
Element<div><MyComp>
Texthello
Interpolation雙大括號插值,例如 message
Attributeclass="active"
Directivev-ifv-for:class@click

例如:

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

Compiler parse 後會知道:

  • 這是一個 button element。
  • 有一個 v-on 指令,事件是 click
  • children 裡有一個 interpolation,表達式是 count

三、 Transform:轉換與優化

Transform 是 Vue Compiler 最有看頭的階段。

Parse 只把 template 變成 AST;Transform 會進一步分析 AST,並產生 codegen 需要的資訊。

1. 指令轉換

Vue 指令不是原封不動留到 runtime 處理。Compiler 會把很多指令轉成 render function 需要的結構。

Template編譯概念
v-if轉成條件表達式
v-for轉成 renderList
@click轉成 onClick prop
:class轉成動態 prop
v-model轉成 prop + onUpdate:*
<slot>轉成 slot render 呼叫

例如:

vue
<button @click="increment">
  {{ count }}
</button>

會轉成類似:

typescript
createElementVNode(
  'button',
  { onClick: _ctx.increment },
  toDisplayString(_ctx.count),
)

2. 表達式處理

Template 裡的表達式:

vue
{{ count + 1 }}

Compiler 需要知道 count 是來自 render context,所以會處理成類似:

typescript
_ctx.count + 1

這樣 render function 執行時才能從組件實例狀態讀到正確值。

3. 靜態提升 hoistStatic

如果一段 DOM 永遠不變,Compiler 可以把它提升到 render function 外面。

vue
<template>
  <div>
    <h1>Title</h1>
    <p>{{ message }}</p>
  </div>
</template>

<h1>Title</h1> 是靜態節點,不需要每次 render 都重新建立 VNode。

Compiler 可以概念上產生:

typescript
const _hoisted_1 = createElementVNode('h1', null, 'Title')

function render(_ctx) {
  return createElementVNode('div', null, [
    _hoisted_1,
    createElementVNode('p', null, _ctx.message),
  ])
}

這就是靜態提升。它能減少重複建立 VNode 的成本,也讓 runtime 更容易跳過靜態內容。

4. Patch Flags

Patch Flags 是 Vue 3 Compiler 給 runtime 的動態更新提示。

例如:

vue
<div :class="activeClass">{{ message }}</div>

Compiler 可以知道:

  • class 是動態的。
  • children text 是動態的。
  • 其他部分是靜態的。

它會在產生 VNode 時附上 patch flag,概念上像:

typescript
createElementVNode(
  'div',
  { class: _ctx.activeClass },
  toDisplayString(_ctx.message),
  PatchFlags.TEXT | PatchFlags.CLASS,
)

這樣 runtime patch 時不用完整 diff props 和 children,而是直接知道要更新 text 和 class。

常見 Patch Flags:

Flag意義
TEXT動態文字
CLASS動態 class
STYLE動態 style
PROPS動態 props
FULL_PROPS需要完整 props diff
HYDRATE_EVENTSSSR hydration 事件
STABLE_FRAGMENT穩定 fragment
KEYED_FRAGMENTkeyed list
UNKEYED_FRAGMENTunkeyed list

面試不用背數字,重點是知道:Patch Flags 是 compiler 提前告訴 runtime 哪些地方會變。

5. Block Tree

Vue 3 還有 Block Tree 優化。

傳統 Virtual DOM 更新時,runtime 可能要遞迴走完整棵 VNode tree。Vue 3 則透過 compiler 找出動態節點,把它們收集到 block 的 dynamicChildren 裡。

概念上:

vue
<div>
  <h1>Static Title</h1>
  <p>{{ message }}</p>
  <span>Static Footer</span>
</div>

真正會變的是:

vue
<p>{{ message }}</p>

Compiler 會讓 runtime 更新時更集中地處理動態節點,而不是每次都深度遍歷所有靜態節點。

一句話:Patch Flags 告訴 runtime 怎麼更新,Block Tree 告訴 runtime 更新時該關心哪些節點。


四、 Generate:產生 render function

Generate 階段會把轉換後的 AST/codegen node 變成 JavaScript 程式碼。

例如:

vue
<div>{{ msg }}</div>

可能產生概念上的 render function:

typescript
import { toDisplayString, openBlock, createElementBlock } from 'vue'

function render(_ctx, _cache) {
  return openBlock(), createElementBlock(
    'div',
    null,
    toDisplayString(_ctx.msg),
    1,
  )
}

其中:

  • openBlock() / createElementBlock() 和 Block Tree 有關。
  • toDisplayString() 負責把值轉成顯示文字。
  • 最後的 1 可以理解成 patch flag,例如 TEXT

真實輸出會根據模式、優化選項、SSR、scopeId 等條件不同而變化。


五、 SFC 編譯流程

.vue 單檔組件不是只有 template。

vue
<template>
  <div>{{ message }}</div>
</template>

<script setup lang="ts">
const message = 'hello'
</script>

<style scoped>
div {
  color: red;
}
</style>

SFC 編譯通常包含:

區塊編譯器處理
<template>@vue/compiler-dom 編譯成 render function
<script setup>@vue/compiler-sfc 編譯成 setup 函數
<style scoped>加上 scope id,改寫 selector
CSS vars轉成 runtime 可注入的 CSS 變數

所以面試談 Vue Compiler 時,要分清楚:

  • compiler-core:平台無關的核心編譯邏輯。
  • compiler-dom:處理 DOM 平台相關轉換。
  • compiler-sfc:處理 .vue 單檔組件。
  • compiler-ssr:處理 SSR render 輸出。

六、 Runtime Compiler vs Build Time Compiler

1. Runtime Compiler

如果你在瀏覽器中直接寫:

javascript
createApp({
  template: '<div>{{ message }}</div>',
  data() {
    return {
      message: 'hello',
    }
  },
}).mount('#app')

那就需要 runtime compiler 在瀏覽器中把 template 編譯成 render function。

缺點:

  • bundle 較大。
  • 首次執行要多一段編譯成本。

2. Build Time Compiler

現代專案通常在建置階段編譯:

text
App.vue
-> Vite plugin vue
-> compiler-sfc
-> render function JS
-> browser 執行已編譯產物

優點:

  • 瀏覽器不需要帶 compiler。
  • bundle 較小。
  • 編譯期可以做更多靜態分析與錯誤提示。

七、 追問題庫

Q1:Vue Compiler 和 Renderer 差在哪?

Compiler 負責把 template 轉成 render function。

Renderer 負責執行 render function 得到 VNode,並把 VNode patch 到真實 DOM。

text
Compiler: template -> render function
Renderer: render function -> VNode -> DOM

可以延伸讀:Vue 2.x 與 Vue 3.x 渲染器 Diff 算法

Q2:為什麼 Vue 3 編譯器能幫 runtime 加速?

因為 compiler 可以在 build time 看見整個 template 結構。

它能提前知道:

  • 哪些節點是靜態的。
  • 哪些 props 是動態的。
  • 哪些 children 是動態文字。
  • 哪些 list 是 keyed fragment。
  • 哪些節點需要被收集進 dynamicChildren。

Runtime 拿到這些提示後,就不用每次更新都完整猜測。

Q3:Patch Flags 是什麼?

Patch Flags 是 compiler 產生在 VNode 上的更新提示,用來告訴 runtime 這個 VNode 哪些部分是動態的。

例如只有 text 動態,就只更新 text;只有 class 動態,就只比對 class。

Q4:靜態提升有什麼用?

靜態提升會把不會變的 VNode 提到 render function 外面,避免每次 render 都重新建立。

它也讓 runtime 更容易跳過靜態子樹。

Q5:Vue template 是不是比 JSX 慢?

不能簡化成這樣。

Vue template 因為語法受限,compiler 更容易做靜態分析與優化,例如 Patch Flags、Block Tree、靜態提升。

JSX 更接近 JavaScript 表達能力,靈活但較難做同等程度的靜態分析。

Q6:v-ifv-for 是 runtime 才處理嗎?

不是單純 runtime 才處理。

Compiler 會先把 v-if 轉成條件分支,把 v-for 轉成列表渲染相關呼叫。Runtime 執行 render function 時,才根據當前狀態產生具體 VNode。


八、 實作題

題目:說明這段 template 可能如何被編譯

vue
<template>
  <div>
    <h1>Profile</h1>
    <p>{{ user.name }}</p>
    <button @click="logout">Logout</button>
  </div>
</template>

可以這樣回答:

  1. Parse 階段把 template 轉成 AST。
  2. Transform 階段識別:
    • <h1>Profile</h1> 是靜態節點,可被提升。
    • p 元素內有 user.name 插值,需要 text patch flag。
    • <button @click="logout"> 有事件綁定,需要生成 onClick
  3. Generate 階段產生 render function。

概念輸出:

typescript
const _hoisted_1 = createElementVNode('h1', null, 'Profile')

function render(_ctx) {
  return openBlock(), createElementBlock('div', null, [
    _hoisted_1,
    createElementVNode(
      'p',
      null,
      toDisplayString(_ctx.user.name),
      PatchFlags.TEXT,
    ),
    createElementVNode('button', {
      onClick: _ctx.logout,
    }, 'Logout'),
  ])
}

面試時不需要寫出完全正確的 Vue 產物,但要能講出 compiler 在每個階段做了什麼。


九、 資深視角

1. Vue 3 的效能不是只靠 Diff

Vue 3 的效能思路是 compiler + runtime 合作:

  • Compiler:提前分析靜態與動態資訊。
  • Runtime:根據 flags 和 block tree 精準 patch。

所以回答 Vue 渲染原理時,不要只講 Virtual DOM,也要講 compiler 如何讓 Virtual DOM 變得更可優化。

2. Template 的限制換來可分析性

Vue template 不像任意 JavaScript 那麼自由,但它的好處是 compiler 能理解結構。

例如:

vue
<div :class="activeClass">{{ message }}</div>

Compiler 很容易知道 class 和 text 是動態的。

如果同樣邏輯藏在一大段任意 JS 裡,靜態分析就更困難。

3. 讀 compiler 原理有助於寫出更友善的模板

理解 compiler 後,你會更知道:

  • 為什麼穩定 key 很重要。
  • 為什麼靜態內容可以放心留在 template。
  • 為什麼複雜邏輯不要塞進模板表達式。
  • 為什麼 v-if / v-for / slots 會影響編譯結果。
  • 為什麼 Vue 3 能比傳統 VDOM 做更精準更新。

總結

問題面試回答重點
Compiler 做什麼template 轉 render function
核心流程parse -> transform -> generate
Parsetemplate 字串轉 AST
Transform指令轉換、表達式處理、靜態分析、優化標記
Generate產生 render function 程式碼
Vue 3 優化Patch Flags、Block Tree、靜態提升
和 runtime 關係compiler 提供提示,runtime 精準 patch

一句完整的面試回答可以是:

Vue Compiler 的作用是把 template 編譯成 render function。它的核心流程是 parse、transform、generate:先把 template 字串解析成 AST,再走訪 AST 處理 v-ifv-for、事件、綁定、插值等語法,同時做靜態提升、Patch Flags、Block Tree 這類優化,最後產生 JavaScript render function。runtime 執行 render function 得到 VNode,再交給 renderer patch DOM。Vue 3 的效能優勢不只是 Diff 算法,也來自 compiler 在編譯期提供的動態節點提示。

延伸閱讀