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 的完整渲染鏈路可以簡化成:
你寫:
<template>
<div>Hello {{ name }}</div>
</template>Compiler 會把它轉成類似:
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 的核心可以拆成三步:
| 階段 | 作用 |
|---|---|
| Parse | template 字串轉 AST |
| Transform | 走訪 AST,處理指令、表達式與優化資訊 |
| Generate | AST / codegen node 轉 render function 程式碼 |
本地 Vue 3.5.x 的 @vue/compiler-core 裡也能看到這些核心函式:baseParse、transform、generate、baseCompile。
二、 Parse:template 轉 AST
1. AST 是什麼?
AST 是 Abstract Syntax Tree,抽象語法樹。
例如:
<div id="app">Hello {{ name }}</div>會被解析成類似這樣的結構:
Root
└─ Element div
├─ props: id="app"
├─ Text "Hello "
└─ Interpolation nameAST 的價值是:把字串變成結構化資料,後續 transform 才能知道哪些是元素、哪些是文字、哪些是插值、哪些是指令。
2. Parse 會處理哪些節點?
常見節點類型:
| 節點 | 例子 |
|---|---|
| Root | 整個 template 根節點 |
| Element | <div>、<MyComp> |
| Text | hello |
| Interpolation | 雙大括號插值,例如 message |
| Attribute | class="active" |
| Directive | v-if、v-for、:class、@click |
例如:
<button @click="count++">
{{ count }}
</button>Compiler parse 後會知道:
- 這是一個
buttonelement。 - 有一個
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 呼叫 |
例如:
<button @click="increment">
{{ count }}
</button>會轉成類似:
createElementVNode(
'button',
{ onClick: _ctx.increment },
toDisplayString(_ctx.count),
)2. 表達式處理
Template 裡的表達式:
{{ count + 1 }}Compiler 需要知道 count 是來自 render context,所以會處理成類似:
_ctx.count + 1這樣 render function 執行時才能從組件實例狀態讀到正確值。
3. 靜態提升 hoistStatic
如果一段 DOM 永遠不變,Compiler 可以把它提升到 render function 外面。
<template>
<div>
<h1>Title</h1>
<p>{{ message }}</p>
</div>
</template><h1>Title</h1> 是靜態節點,不需要每次 render 都重新建立 VNode。
Compiler 可以概念上產生:
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 的動態更新提示。
例如:
<div :class="activeClass">{{ message }}</div>Compiler 可以知道:
class是動態的。- children text 是動態的。
- 其他部分是靜態的。
它會在產生 VNode 時附上 patch flag,概念上像:
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_EVENTS | SSR hydration 事件 |
STABLE_FRAGMENT | 穩定 fragment |
KEYED_FRAGMENT | keyed list |
UNKEYED_FRAGMENT | unkeyed list |
面試不用背數字,重點是知道:Patch Flags 是 compiler 提前告訴 runtime 哪些地方會變。
5. Block Tree
Vue 3 還有 Block Tree 優化。
傳統 Virtual DOM 更新時,runtime 可能要遞迴走完整棵 VNode tree。Vue 3 則透過 compiler 找出動態節點,把它們收集到 block 的 dynamicChildren 裡。
概念上:
<div>
<h1>Static Title</h1>
<p>{{ message }}</p>
<span>Static Footer</span>
</div>真正會變的是:
<p>{{ message }}</p>Compiler 會讓 runtime 更新時更集中地處理動態節點,而不是每次都深度遍歷所有靜態節點。
一句話:Patch Flags 告訴 runtime 怎麼更新,Block Tree 告訴 runtime 更新時該關心哪些節點。
四、 Generate:產生 render function
Generate 階段會把轉換後的 AST/codegen node 變成 JavaScript 程式碼。
例如:
<div>{{ msg }}</div>可能產生概念上的 render function:
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。
<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
如果你在瀏覽器中直接寫:
createApp({
template: '<div>{{ message }}</div>',
data() {
return {
message: 'hello',
}
},
}).mount('#app')那就需要 runtime compiler 在瀏覽器中把 template 編譯成 render function。
缺點:
- bundle 較大。
- 首次執行要多一段編譯成本。
2. Build Time Compiler
現代專案通常在建置階段編譯:
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。
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-if、v-for 是 runtime 才處理嗎?
不是單純 runtime 才處理。
Compiler 會先把 v-if 轉成條件分支,把 v-for 轉成列表渲染相關呼叫。Runtime 執行 render function 時,才根據當前狀態產生具體 VNode。
八、 實作題
題目:說明這段 template 可能如何被編譯
<template>
<div>
<h1>Profile</h1>
<p>{{ user.name }}</p>
<button @click="logout">Logout</button>
</div>
</template>可以這樣回答:
- Parse 階段把 template 轉成 AST。
- Transform 階段識別:
<h1>Profile</h1>是靜態節點,可被提升。p元素內有user.name插值,需要 text patch flag。<button @click="logout">有事件綁定,需要生成onClick。
- Generate 階段產生 render function。
概念輸出:
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 能理解結構。
例如:
<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 |
| Parse | template 字串轉 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-if、v-for、事件、綁定、插值等語法,同時做靜態提升、Patch Flags、Block Tree 這類優化,最後產生 JavaScript render function。runtime 執行 render function 得到 VNode,再交給 renderer patch DOM。Vue 3 的效能優勢不只是 Diff 算法,也來自 compiler 在編譯期提供的動態節點提示。