阿里云主机折上折
  • 微信号
Current Site:Index > The data structure of an AST node

The data structure of an AST node

Author:Chuan Chen 阅读数:41345人阅读 分类: Vue.js

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:

  1. Root Node (RootNode): The root of the entire AST, containing all the content of the template.
  2. Element Node (ElementNode): Corresponds to an HTML element.
  3. Text Node (TextNode): Plain text content.
  4. Interpolation Node (InterpolationNode): Double curly brace expressions.
  5. Attribute Node (AttributeNode): Attributes of an element.
  6. 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:

  1. Raw AST: The basic AST generated by the parse function.
  2. Transformed AST: The AST after being processed by transform.
  3. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.