从遍历到转换
有了 AST 之后,下一步是transform(转换)。转换的前提是能够遍历整棵 AST 树,对每个节点执行操作。
traverseNode:深度优先遍历
function traverseNode(ast: ASTNode, context: TransformContext) {
const currentNode = ast
const children = currentNode.children
// 将上下文中注册的所有转换函数应用到当前节点
const transforms = context.nodeTransforms
for (let i = 0; i < transforms.length; i++) {
transforms[i](currentNode, context)
}
// 递归遍历子节点
if (children && children.length) {
for (let i = 0; i < children.length; i++) {
traverseNode(children[i], context)
}
}
}
typescript
插件化架构:nodeTransforms
直接在 traverseNode 里写转换逻辑会导致函数越来越臃肿。Vue 采用的解决方案是控制反转(IoC)——将转换逻辑提取为独立的插件函数,通过上下文的 nodeTransforms 数组注入。
function transform(ast: ASTNode) {
const context = {
nodeTransforms: [
transformElement, // 转换元素节点
transformText, // 转换文本节点
// 可以无限扩展...
],
}
traverseNode(ast, context)
}
typescript
转换函数示例
// 将所有 P 标签转换为 H1 标签
function transformElement(node: ASTNode) {
if (node.type === 'Element' && node.tag === 'p') {
node.tag = 'h1'
}
}
// 将所有文本内容大写
function transformText(node: ASTNode) {
if (node.type === 'Text') {
node.content = node.content!.toUpperCase()
}
}
// 将文本内容重复两次
function transformRepeatText(node: ASTNode) {
if (node.type === 'Text') {
node.content = node.content!.repeat(2)
}
}
typescript
插件化扩展
用户只需要在 nodeTransforms 数组中添加新的转换函数,无需修改 traverseNode 的核心逻辑:
const context = {
nodeTransforms: [
transformElement,
transformText,
transformRepeatText, // 新增插件
// 继续扩展...
],
}
typescript
dump 函数:AST 可视化
用于验证转换结果的辅助函数,以树形缩进格式打印 AST:
function dump(node: ASTNode, indent = 0): void {
const type = node.type
const desc = type === 'Root'
? ''
: type === 'Element'
? `<${node.tag}>`
: node.content
console.log(`${'—'.repeat(indent)}${desc}`)
if (node.children) {
node.children.forEach(child => dump(child, indent + 1))
}
}
typescript
设计模式分析
控制反转(IoC)
transform 函数不直接决定如何转换节点,而是将这个控制权交给 nodeTransforms 中的插件函数。这是典型的控制反转模式。
策略模式
每个转换函数都是一个独立的策略,可以在不修改核心遍历逻辑的情况下自由组合和替换。
开放封闭原则
系统对扩展开放(新增转换插件),对修改封闭(traverseNode 不需要改动)。
执行流程
transform(ast)
└── 创建 context(包含 nodeTransforms 数组)
└── traverseNode(root, context)
├── 对当前节点执行所有 nodeTransforms
│ ├── transformElement(node) → 修改标签
│ ├── transformText(node) → 修改文本
│ └── transformRepeatText(node) → 重复文本
└── 递归遍历所有子节点
└── 对每个子节点重复上述过程
text
本节要点
- traverseNode 实现深度优先遍历,对每个节点执行转换操作
- nodeTransforms 是插件数组,将转换逻辑与遍历逻辑解耦
- 控制反转:
traverseNode不关心具体如何转换,只负责遍历和调用插件 - 每个转换函数接收节点作为参数,直接修改节点的属性
- 通过在
nodeTransforms中添加函数即可无限扩展转换能力
↑