SoFunction
Updated on 2025-04-04

One article will help you understand the principles of Vue3 compilation

I have always had a sense of fear of the compilation principle. It feels too difficult to read. Open itvue3When the source code sees the compilation-related code, it is frightened away. Until I learned from Brother Da Cuimini-vue,so ga ~

Main process

Now let's analyze a simplevue3compilation principle. To summarize the function we want to implement in one sentence, that is,templateTemplate Generate What We WantrenderJust function. A simple sentence contains a lot of knowledge.

<div>hi, {{message}}</div> 

Last generated

import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, "hi, " + _toDisplayString(_ctx.message), 1 /* TEXT */))
}

firsttemplateWill be parsed through lexical analysis and grammatical analysisAST(Abstract Syntax Tree), then usetransformrightASTPerform optimization, and finally passgenerateModule generation what we wantrenderfunction.

existvue3The source code is mainly divided into 3 parts (the following is the simplified source code)

export function baseCompile(template){
  const ast = baseParse(template)
  transform(ast)
  return generate(ast)
}
  • passparseWilltemplategenerateast
  • passtransformoptimizationast
  • passgenerategeneraterenderfunction

Since these three parts involve a lot of things, our article mainly explains itparseImplementation (Friendly reminder: In order to make everyone understand it, all the codes in this article are streamlined)

Implementation of parse

Let's take a simple example

<div><p>hi</p>{{message}}</div>

It seems like a simple example, but there are actually 3 types:elementtext, interpolation. We define these three types with enumerations.

const enum NodeTypes {
  ROOT,
  INTERPOLATION,
  SIMPLE_EXPRESSION,
  ELEMENT,
  TEXT
}

ROOTType represents the root node,SIMPLE_EXPRESSIONType represents the content of the interpolation. Finally we want to passparseGenerate aast

{
    type: 
    children: [
        {
          type: ,
          tag: "div",
          children: [
            {
              type: ,
              tag: "p",
              children: [
                {
                  type: ,
                  content: "hi"
                }
              ]
            },
            {
              type: ,
              content: {
                type: NodeTypes.SIMPLE_EXPRESSION,
                content: "message"
              }
            }
          ]
        }
    ]
}

Based on the source code, we can knowastis made of functionsbaseParsegenerate. Then let's start with this function.

baseParse

export function baseParse(content: string) {
  const context = createParseContext(content)
  return createRoot(parserChildren(context, []))
}

function createParseContext(content: string) {
  return {
    source: content
  }
}

function createRoot(children) {
  return {
    children,
    type: 
  }
}

First create a global context objectcontext, and storedsourcesourceIt is the template content we passed in. Then create the root node, includingtypeandchildren. andchildrenIs it fromparseChildrencreate.

parseChildren

function parseChildren(context, ancestors) {
  const nodes: any = []

  while (!isEnd(context, ancestors)) {
    const s = 
    let node
    if (("{{")) {
      node = parseInterpolation(context)
    } else if (s[0] === "<") {
      if (/[a-z]/(s[1])) {
        node = parseElement(context, ancestors)
      }
    } else {
      node = parseText(context)
    }
    (node)
  }
  return nodes
}

parseChildrenIt is responsible for parsing child nodes and creatingastNode array.parseChildrenIt analyzes each child node from top to bottom, and the template content must be parsed from left to right. Whenever you encounter oneelementAll nodes need to be called recursivelyparseChildrenTo parse its child nodes. When I encounter{{It is believed that the interpolation node needs to be processed, when it encounters<I think what needs to be dealt with iselementThe nodes, the rest are considered to be handledtextnode. Each node will be generated after processingnodeandpusharrivenodesIn, finally returnnodesAs a fatherastNode'schildrenproperty.

Of course, there must be a condition to exit the loop when looping from left to right.isEnd

function isEnd(context, ancestors) {
  const s = 

  if (("</")) {
    for (let i = 0; i < ; i++) {
      const tag = ancestors[i]
      if (startsWithEndTagOpen(s, tag)) {
        return true
      }
    }
  }

  return !s
}
function startsWithEndTagOpen(source, tag) {
  return (
    ("</") &&
    (2, 2 + ).toLowerCase() === ()
  )
}

ancestorsexpresselementThe set of tags roughly means when you encounter the end identifier</, and end the tag ((2, 2 + ))andelementThe tag matching in the tag collection indicates the current oneelementAfter the node is processed, the loop is exited.

Let's take a look at the interpolation nodeparseInterpolationelementnodeparseElementand text nodesparseTextHow to deal with it separately

parseInterpolation

function parseInterpolation(context) {
  const openDelimiter = "{{"
  const closeDelimiter = "}}"

  const closeIndex = (
    closeDelimiter,
    
  )

  advanceBy(context, )

  const rawContentLength = closeIndex - 

  const rawContent = parseTextData(context, rawContentLength)

  const content = ()
  advanceBy(context, )

  return {
    type: ,
    content: {
      type: NodeTypes.SIMPLE_EXPRESSION,
      content
    }
  }
}

function advanceBy(context: any, length: number) {
   = (length)
}

function parseTextData(context: any, length) {
  const content = (0, length)

  advanceBy(context, )
  return content
}

We mainly want to get the interpolated content and return an interpolated object.closeIndexIndicates the location where "}}" is located.advanceByThe function of the function is to advance. For example, "{{" does not need to be processed, so just intercept it.rawContentLengthRepresents the length of the content between "{{" and "}}", byparseTextDataGets the content between "{{" and "}}" and returns. Then promote the middle content. Since our habit of writing code may leave blank space for the content before and after, we need to usetrimDo the processing. Then push the last "}}" forward and return an interpolated object.

parseElement

function parseElement(context, ancestors) {
  const element: any = parseTag(context, )
  (element)
   = parseChildren(context, ancestors)
  ()

  if (startsWithEndTagOpen(, )) {
    parseTag(context, )
  } else {
    throw new Error(`Missing end tag: ${}`)
  }

  return element
}

function parseTag(context: any, type: TagType) {
  const match: any = /^&lt;\/?([a-z]*)/()
  const tag = match[1]
  advanceBy(context, match[0].length)
  advanceBy(context, 1)

  if (type === ) return

  return {
    type: ,
    tag
  }
}

function startsWithEndTagOpen(source, tag) {
  return (
    ("&lt;/") &amp;&amp;
    (2, 2 + ).toLowerCase() === ()
  )
}

parseElementThe second parameterancestorsis an array to collect labels (used on the aboveisEndalready mentioned). passparseTagGet the tag name,parseTagGet the tag name through regularity and return a tag object, and continue to advance the processed content. If it is an end tag, do nothing. Then passparseChildrenRecursive processingelementchild nodes. Then the end tag is processed.startsWithEndTagOpenThe judgment is that the end tag exists enough, and if it does not exist, an error will be reported.

parseText

function parseText(context: any): any {
  let endIndex = 
  let endToken = ["<", "{{"]

  for (let i = 0; i < ; i++) {
    const index = (endToken[i])
    if (index !== -1 && endIndex > index) {
      endIndex = index
    }
  }

  const content = parseTextData(context, endIndex)

  return {
    type: ,
    content
  }
}

endIndexIndicates the content length (the length of the content is the length of the promoted character to the last character). for example

<div>hi,{{message}}</div> 

Able to enterparseTextThe function indicates that the start tag has been processed, soIt should be

hi,{{message}}</div>

soendIndexThe length should be the length of the above code. When we encounter "<" or "{{", we need to changeendIndexThe value of the above code should behi,, so when encountering "{{", changeendIndexThen passparseTextDataGet the text content and return a text object.

Summarize

parseThe function is totemplategenerateastObject. You need to be righttemplateFrom left to right, process it in turn, and after processing, it will be advanced.elementTags also need to be processed recursively and added toOn, finally return oneastAbstract syntax tree.

The above is a detailed content of the Vue3 compilation principle in one article. For more information about the Vue3 compilation principle, please pay attention to my other related articles!