🧩 总体结构概览
这个文件的职责是将模板字符串解析为抽象语法树(AST)。
整体由三层结构组成:
| 层级 | 内容 | 作用 |
|---|---|---|
| 外层定义 | 类型声明、常量、状态变量 | 管理解析过程中的全局状态 |
| 核心函数组 | 解析、节点生成、错误处理 | 负责模板解析逻辑 |
| 辅助函数组 | 工具类,如 getLoc、condenseWhitespace |
辅助主流程的定位与优化 |
第一部分:全局状态与初始化逻辑
### 1️⃣ reset()
ini
function reset() {
tokenizer.reset()
currentOpenTag = null
currentProp = null
currentAttrValue = ''
currentAttrStartIndex = -1
currentAttrEndIndex = -1
stack.length = 0
}
📖 功能说明
清空所有解析状态,用于开始一次新的模板解析任务。
🔍 逻辑拆解
| 步骤 | 操作 | 说明 |
|---|---|---|
| ① | tokenizer.reset() |
重置状态机(清空缓冲、位置指针) |
| ② | 清空当前正在解析的标签 (currentOpenTag) |
避免上次解析的残留 |
| ③ | 清空属性状态 (currentProp、currentAttrValue) |
防止多属性串联错误 |
| ④ | 重置索引标记 | 用于属性值的精确定位 |
| ⑤ | 清空标签栈 | 重新开始解析树结构 |
💡 原理
解析器是状态机驱动 的;每次调用 baseParse() 之前必须保证状态干净。
若不清理状态,标签嵌套关系会混乱,导致 AST 错误。
### 2️⃣ baseParse(input, options?)
scss
export function baseParse(input: string, options?: ParserOptions): RootNode {
reset()
currentInput = input
currentOptions = extend({}, defaultParserOptions)
if (options) {
for (let key in options) {
if (options[key] != null) currentOptions[key] = options[key]
}
}
...
const root = (currentRoot = createRoot([], input))
tokenizer.parse(currentInput)
root.loc = getLoc(0, input.length)
root.children = condenseWhitespace(root.children)
currentRoot = null
return root
}
📖 功能说明
baseParse 是整个文件的主函数 。
负责从模板字符串生成 AST 树的根节点 (RootNode)。
🔍 逻辑拆解
| 步骤 | 描述 |
|---|---|
① 调用 reset() |
清空上一次解析状态 |
| ② 保存输入模板 | 存入 currentInput |
| ③ 合并解析选项 | 使用 extend 结合用户自定义配置与默认值 |
| ④ 初始化解析模式 | 根据 parseMode 确定 HTML / SFC / Base 模式 |
⑤ 初始化 tokenizer |
设置分隔符、命名空间、XML 模式 |
| ⑥ 创建根节点 | createRoot([], input) |
| ⑦ 启动解析 | tokenizer.parse(currentInput)(核心状态机驱动) |
| ⑧ 修正位置与空白 | 调用 getLoc、condenseWhitespace |
| ⑨ 返回完整的 AST | 含子节点、位置信息等 |
🧠 原理说明
Vue 使用 事件驱动解析模型 :
tokenizer 扫描模板时,会触发一系列回调(如 onopentagname、ontext 等),这些回调逐步组装出 AST 节点。
🧩 拓展示例
xml
baseParse('<div v-if="ok">{{ msg }}</div>')
➡ 输出的 RootNode 类似于:
css
{
"type": 0,
"children": [
{
"type": 1,
"tag": "div",
"props": [
{ "type": 7, "name": "if", "exp": { "content": "ok" } }
],
"children": [
{ "type": 5, "content": { "content": "msg" } }
]
}
]
}
### 3️⃣ emitError()
less
function emitError(code: ErrorCodes, index: number, message?: string) {
currentOptions.onError(
createCompilerError(code, getLoc(index, index), undefined, message),
)
}
📖 功能说明
统一的错误上报函数。负责将解析阶段的错误格式化为 CompilerError 对象并交给回调。
🔍 逻辑拆解
| 步骤 | 内容 |
|---|---|
| ① | 调用 createCompilerError 构造错误对象 |
| ② | 使用 getLoc() 精确标出错误发生的位置 |
| ③ | 调用配置的 onError 回调进行处理(默认:打印警告) |
💡 设计思路
通过错误码系统(ErrorCodes),Vue 可以在编译时快速定位语法错误,如:
- 缺少闭合标签;
- 不合法的插值表达式;
- 指令名拼写错误等。
### 4️⃣ getSlice(start, end)
sql
function getSlice(start: number, end: number) {
return currentInput.slice(start, end)
}
📖 功能说明
从源字符串中截取对应区间的内容。
🧩 应用场景
用于生成 AST 节点的 source 与 loc.source 信息,保证定位准确。
### 5️⃣ getLoc(start, end?)
sql
function getLoc(start: number, end?: number): SourceLocation {
return {
start: tokenizer.getPos(start),
end: end == null ? end : tokenizer.getPos(end),
source: end == null ? end : getSlice(start, end),
}
}
📖 功能说明
生成一个 SourceLocation 对象,表示源代码位置范围(用于调试与错误提示)。
💡 原理
tokenizer.getPos() 会将字符偏移量转换为:
yaml
{ offset: 10, line: 1, column: 11 }
这样,Vue 编译错误可以指明"出错的行列位置"。
### 6️⃣ setLocEnd()
arduino
function setLocEnd(loc: SourceLocation, end: number) {
loc.end = tokenizer.getPos(end)
loc.source = getSlice(loc.start.offset, end)
}
📖 功能说明
更新现有位置对象的结束坐标(用于文本合并或标签闭合时)。
### 7️⃣ cloneLoc()
arduino
export function cloneLoc(loc: SourceLocation): SourceLocation {
return getLoc(loc.start.offset, loc.end.offset)
}
📖 功能说明
复制一个 SourceLocation(防止引用同一对象造成修改污染)。
✨ 下一部分预告
上面我们讲完了基础状态管理与定位逻辑。
接下来第二章将进入 标签与文本节点解析函数组,包括:
onText()onCloseTag()endOpenTag()addNode()condenseWhitespace()- 等一系列高频调用函数。
这些函数负责 AST 的结构构建与白名单清理,是 Vue 模板语法语义识别的核心。