The data structure of an AST node
Data Structure of AST Nodes
AST (Abstract Syntax Tree) is a tree-like representation of source code. Vue 3's template compiler converts template strings into AST before proceeding with subsequent optimizations and code generation. AST nodes are the fundamental units that constitute this tree, with each node representing a structural element in the template.
Basic Node Types
Vue 3's AST nodes are primarily divided into the following basic types:
- Root Node (RootNode): The root of the entire AST, containing all the content of the template.
- Element Node (ElementNode): Corresponds to an HTML element.
- Text Node (TextNode): Plain text content.
- Interpolation Node (InterpolationNode): Double curly brace expressions.
- Attribute Node (AttributeNode): Attributes of an element.
- Directive Node (DirectiveNode): Special attributes starting with
v-
.
Common Node Properties
All AST nodes inherit from a base node interface, which includes the following common properties:
interface BaseNode {
type: NodeType // Node type
loc: SourceLocation // Source code location information
__source?: string // Original code string (used in development mode)
}
Here, NodeType
is an enumeration defining all possible node types:
const enum NodeType {
ROOT,
ELEMENT,
TEXT,
INTERPOLATION,
ATTRIBUTE,
DIRECTIVE
}
Detailed Explanation of Element Nodes
Element nodes are the most complex node type in the AST, with the following data structure:
interface ElementNode extends BaseNode {
type: NodeType.ELEMENT
tag: string // Tag name
props: (AttributeNode | DirectiveNode)[] // Array of attributes/directives
children: TemplateChildNode[] // Child nodes
isSelfClosing: boolean // Whether it is a self-closing tag
codegenNode?: CodegenNode // Node used during the code generation phase
}
Example: For the template <div class="container"><span>hello</span></div>
, the corresponding AST structure would roughly be:
{
type: NodeType.ELEMENT,
tag: 'div',
props: [
{
type: NodeType.ATTRIBUTE,
name: 'class',
value: {
content: 'container',
loc: { /* Location information */ }
}
}
],
children: [
{
type: NodeType.ELEMENT,
tag: 'span',
props: [],
children: [
{
type: NodeType.TEXT,
content: 'hello',
loc: { /* Location information */ }
}
],
isSelfClosing: false
}
],
isSelfClosing: false
}
Attribute and Directive Nodes
Attribute nodes and directive nodes are used to describe the characteristics of elements but differ in structure:
Attribute Node:
interface AttributeNode extends BaseNode {
type: NodeType.ATTRIBUTE
name: string // Attribute name
value: {
content: string // Attribute value content
loc: SourceLocation // Value location information
} | undefined // May have no value (e.g., disabled attribute)
}
Directive Node:
interface DirectiveNode extends BaseNode {
type: NodeType.DIRECTIVE
name: string // Directive name (without the `v-` prefix)
arg: ExpressionNode | undefined // Argument (e.g., `click` in `v-on:click`)
exp: ExpressionNode | undefined // Expression (e.g., `message` in `v-model="message"`)
modifiers: string[] // Modifier array (e.g., `trim` in `v-model.trim`)
}
Example: The directive nodes in <input v-model.trim="message" @click="handleClick">
:
// v-model.trim="message"
{
type: NodeType.DIRECTIVE,
name: 'model',
arg: undefined,
exp: {
type: 'SimpleExpression',
content: 'message',
isStatic: false
},
modifiers: ['trim']
}
// @click="handleClick"
{
type: NodeType.DIRECTIVE,
name: 'on',
arg: {
type: 'SimpleExpression',
content: 'click',
isStatic: true
},
exp: {
type: 'SimpleExpression',
content: 'handleClick',
isStatic: false
},
modifiers: []
}
Expression Nodes
Expressions used in directives and interpolations are parsed into expression nodes:
interface SimpleExpressionNode extends BaseNode {
type: 'SimpleExpression'
content: string // Expression content
isStatic: boolean // Whether it is a static expression
constType: ConstantTypes // Constant type
}
interface CompoundExpressionNode extends BaseNode {
type: 'CompoundExpression'
children: (
| SimpleExpressionNode
| CompoundExpressionNode
| InterpolationNode
| TextNode
| string
)[]
}
Example: {{ count + 1 }}
would be parsed as:
{
type: NodeType.INTERPOLATION,
content: {
type: 'SimpleExpression',
content: 'count + 1',
isStatic: false,
constType: 0 // NOT_CONSTANT
}
}
Compound Expressions
Vue 3 supports more complex expression structures, such as template strings and chained calls:
// Template string
`{{ `Hello ${name}` }}`
// Corresponding AST node
{
type: 'CompoundExpression',
children: [
'`Hello ',
{
type: 'SimpleExpression',
content: 'name',
isStatic: false
},
'`'
]
}
Static Marking and Hoisting
Vue 3's AST nodes include static analysis-related markers for optimization:
interface ElementNode extends BaseNode {
// ...
tagType: ElementTypes // Element type (component/native element/slot, etc.)
isSelfClosing: boolean
codegenNode?: CodegenNode
ssrCodegenNode?: CodegenNode
// Static hoisting related
staticCount?: number
props?: (AttributeNode | DirectiveNode)[]
patchFlag?: string
dynamicProps?: string[]
// For SSR optimization
ssrTransform?: boolean
}
Example: Static nodes are marked for optimization:
<div class="static-class">Static content</div>
The corresponding AST node would include:
{
// ...
patchFlag: '1', // Static marker
staticCount: 1,
// ...
}
Node Creation in Source Code
The Vue 3 compiler internally creates nodes using factory functions:
// packages/compiler-core/src/ast.ts
export function createBaseNode(
type: NodeType,
loc: SourceLocation
): BaseNode {
return {
type,
loc,
__source: ''
}
}
export function createElementNode(
tag: string,
props: (AttributeNode | DirectiveNode)[],
children: TemplateChildNode[],
loc: SourceLocation
): ElementNode {
return {
...createBaseNode(NodeType.ELEMENT, loc),
tag,
props,
children,
isSelfClosing: false,
codegenNode: undefined
}
}
Node Transformation Process
AST nodes undergo multiple transformations during the compilation process:
- Raw AST: The basic AST generated by the
parse
function. - Transformed AST: The AST after being processed by
transform
. - Code Generation AST: The final AST containing information needed for code generation.
Example: The transformation process for the v-if
directive:
<div v-if="show">Content</div>
The transformed AST node would include a conditional expression:
{
type: NodeType.IF,
branches: [
{
condition: {
type: 'SimpleExpression',
content: 'show'
},
children: [/* Original element node */]
}
]
}
Special Node Handling
Special syntax generates unique node structures:
v-for
Directive:
interface ForNode extends BaseNode {
type: NodeType.FOR
source: ExpressionNode // Iteration object
value: ExpressionNode | undefined // Value alias
key: ExpressionNode | undefined // Key alias
index: ExpressionNode | undefined // Index alias
children: TemplateChildNode[] // Loop content
codegenNode?: ForCodegenNode
}
v-slot
Directive:
interface SlotOutletNode extends BaseNode {
type: NodeType.SLOT
props: (AttributeNode | DirectiveNode)[]
children: TemplateChildNode[]
codegenNode?: CodegenNode
}
Node Location Information
The SourceLocation
object records the precise position of a node in the source string:
interface SourceLocation {
start: Position // Start position
end: Position // End position
source: string // Source string fragment
}
interface Position {
offset: number // Character offset (starting from 0)
line: number // Line number (starting from 1)
column: number // Column number (starting from 0)
}
Example: For the template <div>hello</div>
, the div
element's location might be:
{
start: { offset: 0, line: 1, column: 0 },
end: { offset: 17, line: 1, column: 17 },
source: '<div>hello</div>'
}
Node Utility Functions
The Vue 3 compiler provides utility functions for manipulating AST nodes:
// Check node type
export function isElementNode(node: any): node is ElementNode {
return node.type === NodeType.ELEMENT
}
// Create a simple expression node
export function createSimpleExpression(
content: string,
isStatic: boolean,
loc = locStub
): SimpleExpressionNode {
return {
type: 'SimpleExpression',
loc,
content,
isStatic,
constType: isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
}
}
// Create a text node
export function createTextNode(
content: string,
loc: SourceLocation
): TextNode {
return {
type: NodeType.TEXT,
loc,
content
}
}
Node Traversal and Visiting
The compiler uses the visitor pattern to traverse AST nodes:
export function traverseNodes(
nodes: TemplateChildNode[],
visitor: NodeVisitor
) {
for (const node of nodes) {
// Call the corresponding visit method
switch (node.type) {
case NodeType.ELEMENT:
visitor.visitElement?.(node)
traverseNodes(node.children, visitor)
break
case NodeType.TEXT:
visitor.visitText?.(node)
break
// Other node types...
}
}
}
Example visitor implementation:
const visitor = {
visitElement(node: ElementNode) {
console.log(`Visiting element node: ${node.tag}`)
},
visitText(node: TextNode) {
console.log(`Visiting text node: ${node.content}`)
}
}
Nodes and Code Generation
AST nodes are ultimately transformed into code generation nodes (CodegenNode
), which contain all the information needed to generate the render function:
interface VNodeCall {
type: NodeType.ELEMENT
tag: string | symbol
props: PropsExpression | undefined
children: TemplateChildNode[] | undefined
patchFlag: string | undefined
dynamicProps: string | undefined
directives: DirectiveNode[] | undefined
isBlock: boolean
disableTracking: boolean
}
Example: A simple element node would be transformed into:
{
type: NodeType.ELEMENT,
tag: 'div',
props: {
type: 'ObjectExpression',
properties: [
{
key: { type: 'Identifier', name: 'class' },
value: { type: 'StringLiteral', value: 'container' }
}
]
},
children: [/* Child nodes */],
patchFlag: '1',
isBlock: false
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:模板解析的整体流程