【vue篇】Vue 模板编译原理:从 Template 到 DOM 的翻译官

184 阅读2分钟

在 Vue 项目中,你写的:

<template>
  <div class="user" v-if="loggedIn">
    Hello, {{ name }}!
  </div>
</template>

最终变成了浏览器能执行的 JavaScript 函数。
这背后,就是 Vue 模板编译器 在默默工作。

本文将深入解析 Vue 模板编译的三大核心阶段parseoptimizegenerate,带你揭开 .vue 文件如何变成可执行代码的神秘面纱。


一、为什么需要模板编译?

🎯 浏览器不认识 <template>

<!-- 你写的 -->
<template>
  <div v-if="user.loggedIn">{{ user.name }}</div>
</template>

<!-- 浏览器看到的 -->
Unknown tag: template → 忽略 or 报错

✅ 解决方案:编译成 render 函数

// 编译后生成的 render 函数
render(h) {
  return this.user.loggedIn 
    ? h('div', { class: 'user' }, `Hello, ${this.user.name}!`)
    : null;
}

💡 render 函数返回的是 虚拟 DOM (VNode),Vue 拿它来高效更新真实 DOM。


二、模板编译三部曲

Template String 
     ↓ parse
   AST (抽象语法树)
     ↓ optimize
   优化后的 AST
     ↓ generate
   Render Function

第一步:🔍 解析(Parse)—— 构建 AST

目标:将 HTML 字符串转为 AST(Abstract Syntax Tree)

示例输入:

<div id="app" class="container">
  <p v-if="show">Hello {{ name }}</p>
</div>

输出 AST 结构:

{
  "type": 1,
  "tag": "div",
  "attrsList": [...],
  "children": [
    {
      "type": 1,
      "tag": "p",
      "if": "show",           // 指令被解析
      "children": [
        {
          "type": 3,
          "text": "Hello ",
          "static": false
        },
        {
          "type": 2,
          "expression": "_s(name)",  // {{ name }} 被编译
          "text": "{{ name }}"
        }
      ]
    }
  ]
}

🛠️ 如何实现?正则 + 状态机

编译器使用多个正则表达式匹配:

匹配内容正则示例
标签开始/<([^\s>/]+)/
属性/(\w+)(?:=)(?:"([^"]*)")/
插值表达式/{{\s*([\s\S]*?)\s*}}/
指令/v-(\w+):?(\w*)/?

⚠️ 注意:Vue 的 parser 是一个递归下降解析器,比简单正则复杂得多,但原理类似。


第二步:⚡ 优化(Optimize)—— 标记静态节点

目标:提升运行时性能,跳过不必要的 diff

什么是静态节点?

  • 不包含动态绑定;
  • 内容不会改变;
  • 如:<p>纯文本</p><img src="/logo.png">

优化过程:

  1. 遍历 AST,标记静态根节点和静态子节点;
  2. 添加 static: truestaticRoot: true 标志。
{
  "tag": "p",
  "static": true,
  "staticRoot": true,
  "children": [
    { "type": 3, "text": "这是静态文本", "static": true }
  ]
}

运行时收益:

// patch 过程中
if (vnode.static && oldVnode.static) {
  // 直接复用,跳过 diff!
  vnode.componentInstance = oldVnode.componentInstance;
  return;
}

💥 对于大量静态内容(如文档页面),性能提升可达 30%+


第三步:🎯 生成(Generate)—— 输出 render 函数

目标:将优化后的 AST 转为可执行的 render 函数字符串

输入:优化后的 AST

输出:JavaScript 代码字符串

with(this) {
  return _c('div',
    { attrs: { "id": "app", "class": "container" } },
    [ (show) ?
      _c('p', [_v("Hello "+_s(name))]) :
      _e()
    ]
  )
}

🔤 代码生成规则

AST 节点生成代码
元素标签_c(tag, data, children)
文本节点_v(text)
表达式 {{ }}_s(expression)
条件渲染 v-if(condition) ? renderTrue : renderFalse
静态节点_m(index)(从 $options.staticRenderFns 中取)

💡 _c = createElement, _v = createTextVNode, _s = toString


三、完整流程图解

          Template
             │
             ▼
       [ HTML Parser ]
             │
             ▼
         AST (未优化)
             │
             ▼
      [ 静态节点检测与标记 ]
             │
             ▼
         AST (已优化)
             │
             ▼
     [ Codegen (生成器) ]
             │
             ▼
     Render Function String
             │
             ▼
     new Function(renderStr)
             │
             ▼
       可执行的 render()
             │
             ▼
        Virtual DOM
             │
             ▼
        Real DOM (渲染)

四、Vue 2 vs Vue 3 编译器对比

特性Vue 2Vue 3
编译目标render 函数render 函数
模板语法限制较多(如必须单根)更灵活(Fragment 支持多根)
静态提升✅✅ 更强的 hoist 静态节点
Patch Flag动态节点标记,diff 更快
编译时优化基础静态标记Tree-shaking 友好,死代码消除
源码位置src/compiler/@vue/compiler-dom

💥 Vue 3 的编译器更智能,生成的代码更小、更快。


五、手写一个极简模板编译器(玩具版)

function compile(template) {
  // Step 1: Parse (简化版)
  const tags = template.match(/<(\w+)[^>]*>(.*?)<\/\1>/);
  if (!tags) return;

  const tag = tags[1];
  const content = tags[2];

  // Step 2: Optimize (判断是否静态)
  const isStatic = !content.includes('{{');

  // Step 3: Generate
  const renderCode = `
    function render() {
      return ${isStatic 
        ? `_v("${content}")` 
        : `_c("${tag}", {}, [ _v( _s(${content.slice(2,-2)})) ])`
      };
    }
  `;

  return renderCode;
}

// 使用
const code = compile('<p>{{ msg }}</p>');
console.log(code);
// 输出:function render() { return _c("p", {}, [ _v( _s(msg)) ]); }

🎉 这就是一个最简化的“编译器”雏形!


💡 结语

“Vue 模板编译器,是连接声明式模板与命令式 DOM 操作的桥梁。”

阶段作用输出
Parse解析 HTML 字符串AST
Optimize标记静态节点优化后的 AST
Generate生成 JS 代码render 函数

掌握编译原理,你就能:

✅ 理解 Vue 模板的底层机制;
✅ 写出更高效的模板(减少动态绑定);
✅ 调试编译错误更得心应手;
✅ 为学习其他框架(React JSX)打下基础。