Skip to content

Parsing Templates and AST

Next, we'll look at the parser and AST implemented in compiler-core.
The SFC parser uses this.

AST

AST stands for Abstract Syntax Tree.

It is probably the most complex intermediate object that the Vue.js compiler has.
Information such as directives, mustaches, slots, etc., are represented as ASTs here.

The implementation is in ast.ts of compiler-core.

packages/compiler-core/src/ast.ts

Let's read through the overall structure.

Looking at the types of Nodes, we can see that there are several categories.

29export enum NodeTypes {
30  ROOT,
31  ELEMENT,
32  TEXT,
33  COMMENT,
34  SIMPLE_EXPRESSION,
35  INTERPOLATION,
36  ATTRIBUTE,
37  DIRECTIVE,
38  // containers
39  COMPOUND_EXPRESSION,
40  IF,
41  IF_BRANCH,
42  FOR,
43  TEXT_CALL,
44  // codegen
45  VNODE_CALL,
46  JS_CALL_EXPRESSION,
47  JS_OBJECT_EXPRESSION,
48  JS_PROPERTY,
49  JS_ARRAY_EXPRESSION,
50  JS_FUNCTION_EXPRESSION,
51  JS_CONDITIONAL_EXPRESSION,
52  JS_CACHE_EXPRESSION,
53
54  // ssr codegen
55  JS_BLOCK_STATEMENT,
56  JS_TEMPLATE_LITERAL,
57  JS_IF_STATEMENT,
58  JS_ASSIGNMENT_EXPRESSION,
59  JS_SEQUENCE_EXPRESSION,
60  JS_RETURN_STATEMENT,
61}
  • Plain
  • Containers
  • Codegen
  • SSR Codegen

To conclude, codegen and ssr codegen are not related to the Vapor compiler.
This relates to concepts we'll explain later, such as IR and transform, but in Vapor Mode, information for codegen is aggregated in IR.
However, in traditional Vue.js (non-Vapor Mode), there is no concept of IR, and even the output code was represented as AST.
In Vapor Mode, the transformer converts the AST into IR, but in Vue.js (non-Vapor Mode), the AST (Plain, Containers) is converted into AST (Codegen, SSR Codegen) and passed to the codegen.

This time, to explain the design of the Vapor Mode compiler, we won't touch on codegen and ssr codegen.
Let's look at the others!

Plain

First, the basic AST Node types without any specific category.

29export enum NodeTypes {
30  ROOT,
31  ELEMENT,
32  TEXT,
33  COMMENT,
34  SIMPLE_EXPRESSION,
35  INTERPOLATION,
36  ATTRIBUTE,
37  DIRECTIVE,

Root

As the name suggests, Root represents the root of the template.
It has Nodes in its children.

111export interface RootNode extends Node {
112  type: NodeTypes.ROOT
113  source: string
114  children: TemplateChildNode[]
115  helpers: Set<symbol>
116  components: string[]
117  directives: string[]
118  hoists: (JSChildNode | null)[]
119  imports: ImportItem[]
120  cached: (CacheExpression | null)[]
121  temps: number
122  ssrHelpers?: symbol[]
123  codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
124  transformed?: boolean
125
126  // v2 compat only
127  filters?: string[]
128}

Element

Element is a Node representing an element.
Elements like <p> or <div> correspond to this.
Components and slots also correspond to this.

These also have Nodes in their children.
They also have attribute information and directive information.

136export interface BaseElementNode extends Node {
137  type: NodeTypes.ELEMENT
138  ns: Namespace
139  tag: string
140  tagType: ElementTypes
141  props: Array<AttributeNode | DirectiveNode>
142  children: TemplateChildNode[]
143  isSelfClosing?: boolean
144  innerLoc?: SourceLocation // only for SFC root level elements
145}
146
147export interface PlainElementNode extends BaseElementNode {
148  tagType: ElementTypes.ELEMENT
149  codegenNode:
150    | VNodeCall
151    | SimpleExpressionNode // when hoisted
152    | CacheExpression // when cached by v-once
153    | MemoExpression // when cached by v-memo
154    | undefined
155  ssrCodegenNode?: TemplateLiteral
156}
157
158export interface ComponentNode extends BaseElementNode {
159  tagType: ElementTypes.COMPONENT
160  codegenNode:
161    | VNodeCall
162    | CacheExpression // when cached by v-once
163    | MemoExpression // when cached by v-memo
164    | undefined
165  ssrCodegenNode?: CallExpression
166}
167
168export interface SlotOutletNode extends BaseElementNode {
169  tagType: ElementTypes.SLOT
170  codegenNode:
171    | RenderSlotCall
172    | CacheExpression // when cached by v-once
173    | undefined
174  ssrCodegenNode?: CallExpression
175}

Text

Text is, as the name suggests, Text.
In <p>hello</p>, hello corresponds to this.

183export interface TextNode extends Node {
184  type: NodeTypes.TEXT
185  content: string
186}

Comment

Comment is a comment.
<!-- comment --> corresponds to this.

188export interface CommentNode extends Node {
189  type: NodeTypes.COMMENT
190  content: string
191}

SimpleExpression

SimpleExpression is a simple expression that appears in the template. It's a bit difficult to explain what is simple and what is not, but for example, a and o.a are simple, while (() => 42)() is not simple.

The foo in {{ foo }} and the handlers.onClick in <button @click="handlers.onClick"> correspond to this.

232export interface SimpleExpressionNode extends Node {
233  type: NodeTypes.SIMPLE_EXPRESSION
234  content: string
235  isStatic: boolean
236  constType: ConstantTypes
237  /**
238   * - `null` means the expression is a simple identifier that doesn't need
239   *    parsing
240   * - `false` means there was a parsing error
241   */
242  ast?: BabelNode | null | false
243  /**
244   * Indicates this is an identifier for a hoist vnode call and points to the
245   * hoisted node.
246   */
247  hoisted?: JSChildNode
248  /**
249   * an expression parsed as the params of a function will track
250   * the identifiers declared inside the function body.
251   */
252  identifiers?: string[]
253  isHandlerKey?: boolean
254}

Interpolation

This is a mustache.
{{ foo }} corresponds to this.

256export interface InterpolationNode extends Node {
257  type: NodeTypes.INTERPOLATION
258  content: ExpressionNode
259}

Attribute

This corresponds to attributes (not directives).
In <div id="app">, id="app" corresponds to this.

193export interface AttributeNode extends Node {
194  type: NodeTypes.ATTRIBUTE
195  name: string
196  nameLoc: SourceLocation
197  value: TextNode | undefined
198}

Directive

This is a directive.

v-on:click="handler" and v-for="item in items" correspond to this.
Of course, shorthand notations like @click="handler" and #head are also included.

200export interface DirectiveNode extends Node {
201  type: NodeTypes.DIRECTIVE
202  /**
203   * the normalized name without prefix or shorthands, e.g. "bind", "on"
204   */
205  name: string
206  /**
207   * the raw attribute name, preserving shorthand, and including arg & modifiers
208   * this is only used during parse.
209   */
210  rawName?: string
211  exp: ExpressionNode | undefined
212  arg: ExpressionNode | undefined
213  modifiers: string[]
214  /**
215   * optional property to cache the expression parse result for v-for
216   */
217  forParseResult?: ForParseResult
218}

Containers

Containers are Nodes with specific structures.

39  COMPOUND_EXPRESSION,
40  IF,
41  IF_BRANCH,
42  FOR,
43  TEXT_CALL,

The order might be a bit out of sequence, but let's look at the easier ones first.

If, IfBranch

If and IfBranch are Nodes represented by v-if, v-else-if, and v-else.

286export interface IfNode extends Node {
287  type: NodeTypes.IF
288  branches: IfBranchNode[]
289  codegenNode?: IfConditionalExpression | CacheExpression // <div v-if v-once>
290}
291
292export interface IfBranchNode extends Node {
293  type: NodeTypes.IF_BRANCH
294  condition: ExpressionNode | undefined // else
295  children: TemplateChildNode[]
296  userKey?: AttributeNode | DirectiveNode
297  isTemplateIf?: boolean
298}

Structurally, an IfNode has multiple IfBranchNodes, and an IfBranchNode has a condition (condition) and children (Nodes when that condition is met).
In the case of v-else, condition becomes undefined.

For

This is the Node represented by v-for.

300export interface ForNode extends Node {
301  type: NodeTypes.FOR
302  source: ExpressionNode
303  valueAlias: ExpressionNode | undefined
304  keyAlias: ExpressionNode | undefined
305  objectIndexAlias: ExpressionNode | undefined
306  parseResult: ForParseResult
307  children: TemplateChildNode[]
308  codegenNode?: ForCodegenNode
309}
311export interface ForParseResult {
312  source: ExpressionNode
313  value: ExpressionNode | undefined
314  key: ExpressionNode | undefined
315  index: ExpressionNode | undefined
316  finalized: boolean
317}

In <div v-for="it in list">, source becomes list, and value becomes it.

CompoundExpression

This is a somewhat hard-to-understand concept.

compound means "composite", and this Node is composed of multiple Nodes.

261export interface CompoundExpressionNode extends Node {
262  type: NodeTypes.COMPOUND_EXPRESSION
263  /**
264   * - `null` means the expression is a simple identifier that doesn't need
265   *    parsing
266   * - `false` means there was a parsing error
267   */
268  ast?: BabelNode | null | false
269  children: (
270    | SimpleExpressionNode
271    | CompoundExpressionNode
272    | InterpolationNode
273    | TextNode
274    | string
275    | symbol
276  )[]
277
278  /**
279   * an expression parsed as the params of a function will track
280   * the identifiers declared inside the function body.
281   */
282  identifiers?: string[]
283  isHandlerKey?: boolean
284}

Examples like {{ foo }} + {{ bar }} correspond to this.

Intuitively, this might seem like a structure of Interpolation + Text + Interpolation, but
the Vue.js compiler treats these together as a CompoundExpression.

The noteworthy point is that types like string and Symbol can be seen in children.

269  children: (
270    | SimpleExpressionNode
271    | CompoundExpressionNode
272    | InterpolationNode
273    | TextNode
274    | string
275    | symbol

This is a mechanism to treat partial strings not as some AST Node but as literals for simplicity.

Between {{ foo }} + {{ bar }}, the + part of the string is more efficient to treat as a literal " + " rather than expressing it as a Text Node.

It's like an AST as follows:

json
{
  "type": "CompoundExpression",
  "children": [
    { "type": "Interpolation", "content": "foo" },
    " + ",
    { "type": "Interpolation", "content": "bar" }
  ]
}

TextCall

319export interface TextCallNode extends Node {
320  type: NodeTypes.TEXT_CALL
321  content: TextNode | InterpolationNode | CompoundExpressionNode
322  codegenNode: CallExpression | SimpleExpressionNode // when hoisted
323}

This is a Node used when expressing Text as a function call createText.
For now, you don't need to worry too much about it.


So far, we've looked at the necessary AST Nodes.
From here, let's look at the implementation of the parser that generates these ASTs!