一、背景与概念
Vue 的编译器(compiler)负责将模板字符串(template)转换为可执行的渲染函数(render function)。
这个过程大致分为三个阶段:
- 解析(Parse) :将模板字符串解析为抽象语法树(AST)。
- 转换(Transform) :遍历并改造 AST 节点,应用各种指令转换(如
v-if、v-for)。 - 代码生成(Codegen) :将最终的 AST 生成 JavaScript 渲染函数代码。
baseCompile 就是这一整个编译流程的核心函数。它被更高层的模块(如 @vue/compiler-dom)包装,用于浏览器或 SSR 场景。
二、核心函数结构与执行流程
1. 文件导入部分
python
import type { CompilerOptions } from './options'
import { baseParse } from './parser'
import {
type DirectiveTransform,
type NodeTransform,
transform,
} from './transform'
import { type CodegenResult, generate } from './codegen'
import type { RootNode } from './ast'
import { extend, isString } from '@vue/shared'
解释:
baseParse:负责将模板解析为 AST。transform:负责节点遍历和转换。generate:根据最终的 AST 输出渲染函数代码。extend:对象合并工具函数(浅拷贝,用于合并编译选项)。
2. 转换预设 getBaseTransformPreset
ini
export type TransformPreset = [
NodeTransform[],
Record<string, DirectiveTransform>,
]
export function getBaseTransformPreset(
prefixIdentifiers?: boolean,
): TransformPreset {
return [
[
transformVBindShorthand,
transformOnce,
transformIf,
transformMemo,
transformFor,
...(__COMPAT__ ? [transformFilter] : []),
...(!__BROWSER__ && prefixIdentifiers
? [
trackVForSlotScopes,
transformExpression,
]
: __BROWSER__ && __DEV__
? [transformExpression]
: []),
transformSlotOutlet,
transformElement,
trackSlotScopes,
transformText,
],
{
on: transformOn,
bind: transformBind,
model: transformModel,
},
]
}
解释:
-
该函数定义了 默认转换插件集(preset),包括:
- 节点级转换(NodeTransform) :对整个节点结构生效的转换器。
- 指令级转换(DirectiveTransform) :只针对特定指令(如
v-on、v-bind)。
-
prefixIdentifiers参数用于控制是否在编译阶段前缀变量(如 SSR、module 模式时)。
主要节点转换:
| 转换器 | 功能 |
|---|---|
transformIf |
处理 v-if 逻辑结构 |
transformFor |
处理 v-for 列表渲染 |
transformExpression |
将表达式中的变量转化为作用域内标识符 |
transformElement |
处理普通元素节点 |
transformText |
合并相邻文本节点、插值等 |
transformModel |
处理 v-model 指令 |
transformOn |
处理 v-on 事件绑定 |
transformBind |
处理 v-bind 动态属性 |
3. 编译主函数 baseCompile
ini
export function baseCompile(
source: string | RootNode,
options: CompilerOptions = {},
): CodegenResult {
const onError = options.onError || defaultOnError
const isModuleMode = options.mode === 'module'
解释:
source:可以是模板字符串或已生成的 AST。options:编译配置项。onError:错误处理回调。isModuleMode:是否为模块模式(通常用于 SSR)。
4. 浏览器/模块模式检查
scss
if (__BROWSER__) {
if (options.prefixIdentifiers === true) {
onError(createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED))
} else if (isModuleMode) {
onError(createCompilerError(ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED))
}
}
解释:
- 浏览器模式下不支持
prefixIdentifiers和module mode。 - 在浏览器中,模板直接在运行时编译,无需模块作用域隔离。
5. 前缀与缓存处理逻辑
scss
const prefixIdentifiers =
!__BROWSER__ && (options.prefixIdentifiers === true || isModuleMode)
if (!prefixIdentifiers && options.cacheHandlers) {
onError(createCompilerError(ErrorCodes.X_CACHE_HANDLER_NOT_SUPPORTED))
}
if (options.scopeId && !isModuleMode) {
onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
}
解释:
prefixIdentifiers只有在非浏览器环境才可启用。- 当未启用
prefixIdentifiers时,如果还希望缓存事件处理函数(cacheHandlers),会报错。 scopeId通常只用于module模式(例如 SSR 时生成作用域 CSS)。
6. 解析模板为 AST
scss
const resolvedOptions = extend({}, options, {
prefixIdentifiers,
})
const ast = isString(source) ? baseParse(source, resolvedOptions) : source
解释:
- 将字符串模板解析为 AST。
- 若
source已是 AST,则跳过解析阶段。
7. 获取转换预设与 TypeScript 支持
scss
const [nodeTransforms, directiveTransforms] =
getBaseTransformPreset(prefixIdentifiers)
if (!__BROWSER__ && options.isTS) {
const { expressionPlugins } = options
if (!expressionPlugins || !expressionPlugins.includes('typescript')) {
options.expressionPlugins = [...(expressionPlugins || []), 'typescript']
}
}
解释:
- 动态引入基础转换器。
- 如果开启了
isTS选项,则自动为表达式添加 TypeScript 插件支持(比如解析 TS 语法)。
8. 执行 AST 转换
less
transform(
ast,
extend({}, resolvedOptions, {
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || []),
],
directiveTransforms: extend(
{},
directiveTransforms,
options.directiveTransforms || {},
),
}),
)
解释:
-
执行核心转换阶段:
- 将所有
nodeTransforms顺序作用于 AST。 - 同时允许用户通过
options注入自定义转换器。
- 将所有
-
例如,
transformIf会将:ini<div v-if="ok">yes</div>转换为条件表达式结构的渲染指令节点。
9. 代码生成阶段
kotlin
return generate(ast, resolvedOptions)
}
解释:
-
最终调用
generate()将优化后的 AST 转为 JavaScript 渲染函数。 -
输出的
CodegenResult通常包含:code: 渲染函数的字符串源码。ast: 最终 AST。map: 源码映射(用于调试)。
三、原理总结
| 阶段 | 函数 | 功能 |
|---|---|---|
| 解析 | baseParse |
模板字符串 → AST |
| 转换 | transform |
对 AST 应用转换规则 |
| 生成 | generate |
AST → 渲染函数源码 |
Vue 的编译器是一个典型的 编译管线(pipeline)模式,每一步都在加工 AST,直到最终生成高性能的渲染函数。
四、与 DOM 编译器的对比
| 特性 | @vue/compiler-core |
@vue/compiler-dom |
|---|---|---|
| 环境 | 通用基础层 | 浏览器专用 |
| 输出 | 平台无关的渲染函数 | 带 DOM API 的渲染函数 |
| 转换器 | v-if、v-for 等核心指令 |
DOM 事件、属性、样式相关 |
| 角色 | 被上层编译器继承和扩展 | 实际对模板进行编译输出 |
五、实践示例
输入模板:
ini
<div v-if="ok">{{ msg }}</div>
执行:
css
const { code } = baseCompile('<div v-if="ok">{{ msg }}</div>')
console.log(code)
输出(简化版):
javascript
return function render(_ctx, _cache) {
return _ctx.ok
? (_openBlock(), _createElementBlock("div", null, _toDisplayString(_ctx.msg), 1))
: _createCommentVNode("v-if", true)
}
说明:
transformIf将v-if转为条件表达式结构;transformText处理{{ msg }};generate输出完整渲染函数。
六、拓展与潜在问题
拓展方向
- 用户可自定义
nodeTransforms来实现自己的编译优化(如 AST 静态提升)。 - 可结合 TypeScript 插件系统支持更复杂的表达式解析。
潜在问题
- 编译选项之间存在依赖约束(如
prefixIdentifiers与cacheHandlers不能同时用)。 - 过多自定义转换器可能影响编译性能。
- 浏览器模式下的错误检查有限,需在构建时编译模板。
七、总结
baseCompile 是 Vue 编译系统的"心脏",其职责是将模板转化为高效的渲染函数。
整个流程体现了函数式编译管线思想:解析 → 转换 → 生成,每一步都职责清晰、可扩展性强。
本文部分内容借助 AI 辅助生成,并由作者整理审核。