In the process of using Vue for actual development, most of the time use template to create HTML, template function is powerful and simple and intuitive, the final template will be compiled into a rendering function, this article mainly introduces the specific process of template compilation.

1. Compiler entry

Vue is divided into two versions in terms of whether it can handle the template option: runtime + compiler, and run-time only. The runtime + compiler version is also referred to as the full version. The included run-time version is about 30% smaller than the full version, and using the included run-time version requires compiling templates with tools like Vue-Loader or Vueify. This article explores the template compilation process of the complete Vue version from the compilation portal of web platform. Compile the template into rendering functions via the compileToFunctions method in the $mount method under SRC /platforms/web/entry-runtime-with-compiler.js. The compilation method generation process is as follows:

First of all, tocreateCompilerCreator()Function is introduced tobaseCompile()Function, which returns a value ofcreateCompiler()Function.



baseCompile


function baseCompile (template, options){
  const ast = parse(template.trim(), options)
  if(options.optimize ! = =false) {
    optimize(ast, options)
  }
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
}
Copy the code

The first step is to parse the template into an AST (abstract syntax tree) using the parse function. The second step is to optimize the AST. The third step is to generate objects containing render function strings from the optimized abstract syntax tree. Next, we pass the base configuration object baseOptions to createCompiler(), which returns an object containing the function properties compile and compileToFunctions. The compile function takes two parameters: a template string and a compile option. There are also closures that reference the basic compilation function baseCompile passed in earlier and the basic compilation configuration object baseOptions. The function has three main functions:

1. Merge the base configuration options with the compile options passed in to generate finalOptions. 2. Collect errors during compilation. 3. Call the basic compiler function baseCompile.

CompileToFunctions function was introduced into createCompileToFunctionFn will compile function as a parameter () function to generate a return value. CreateCompileToFunctionFn function defines a cache variable cache, and then return compileToFunctions function. Compiling template strings can be time-consuming, and the cache variable cache is used to prevent double compilation and improve performance. The function compileToFunctions takes three arguments: template strings, compile options, and Vue instances. The main functions of this function are as follows:

1. Cache compilation results to prevent repeated compilation. Check the content security policy to ensure that new Function() can be used. 4. Call createFunction to convert the render function string into a real render function. 5.

Finally, the template string to compile, compile options, and Vue instance objects are passed to compileToFunctions, which return objects containing the Render and staticRenderFns properties. Render is the final generated render function string, staticRenderFns is the static root node render function string. These Function strings are used to generate the final render Function through new Function(). Vue uses the technique of function Curryization to generate compilation templates, which makes people feel very cumbersome when reading the code for the first time, but is actually very clever in design. The reason for this design is that Vue can run on different platforms, such as SSR on the server side, as well as weeX. Each platform will have its own compilation process, depending on the base compilation option baseOptions. Vue takes the basic compilation process out of the way and allows you to add compiler options in multiple places, then combine the added compiler options with the basic compilation options to create flexible compilations on different platforms.

Second, generate AST

Refer to the following Wikipedia description for AST concepts:

In computer science, Abstract Syntax Tree (AST) is an Abstract representation of the syntactic structure of source code. It represents the syntactic structure of a programming language as a tree, with each node in the tree representing a structure in the source code. The syntax is “abstract” because it does not represent every detail that occurs in real grammar. During source code translation and compilation, the parser creates an analysis tree and then generates an AST from the analysis tree. Once the AST is created, information is added during subsequent processing, such as the semantic analysis phase.

The first step at the heart of the Vue compilation process is to call the parse method to parse the template string into an AST.

const ast = parse(template.trim(), options)
Copy the code

The AST generation process is divided into two steps: lexical analysis and syntactic analysis. Parse implements syntax analysis, which is performed by parseHTML functions that parse calls internally. We first analyze the lexical analysis of template strings.

1. The lexical analysis function parseHTML

The code for the parseHTML function, which omits the details, looks like this:

export function parseHTML (html, options) {
  const stack = []
  let last, lastTag
  /* omit... * /
  while (html) {
    last = html
    if(! lastTag || ! isPlainTextElement(lastTag)) {let textEnd = html.indexOf('<')

      if (textEnd === 0) {/* omit the implementation */}

      let text, rest, next

      if (textEnd >= 0) {/* omit the implementation */}
      if (textEnd < 0) { text = html }

      if (text) { advance(text.length) }

      if (options.chars && text) {
        options.chars(text, index - text.length, index)
      }
    } else {
      /* omit the implementation */
    }

    if (html === last) {/* omit the implementation */}
  }

  parseEndTag()

  function advance (n) {
    index += n
    html = html.substring(n)
  }

  function parseStartTag () {/* omit the implementation */}

  function handleStartTag (match) {/* omit the implementation */}

  function parseEndTag (tagName, start, end) {/* omit the implementation */}}Copy the code

(I) Overall process analysis

The parseHTML function is as follows:

parseHTML
while
advance



How do you tell if a non-unary tag lacks a closing tag


<div><span></div>
Copy the code

The parseHTML function takes advantage of the stack’s data structure: when parsing the start tag, it pushes the start tag onto the array stack, with the variable lastTag always pointing to the top element of the stack. When the end tag is parsed, it matches the start element at the top of the stack. If it is a pair of non-unary tags, the start tag at the top of the stack is pushed out of the stack while parsing forward. If the match fails or there is still a start label in the stack after parsing, it indicates that the non-unary label is not closed. As shown in the example above,

is pushed onto the stack first, and then
is pushed onto the stack as well. At this point, the top tag of the stack is < SPAN >. When the end tag

is parsed, it will be compared to the top tag of the stack. The template string is missing the closing tag.

1, if the comment tag
, the comment content is processed using the comment() method passed in. 2, if conditional annotation tag
, do not do any processing, directly skip. 3, if the document type declaration
, do not do any processing, directly skip. 4. If the tag ends, the parseEndTag() function is called. 5. If it is a start tag, call parseStartTag() and handleStartTag().

In short, the parseHTML function parses to text and calls the chars() method, parses to comment tags and calls the comment() method, parses to conditional comment tags and document type declarations without processing. The chars() and comment() methods passed in will be explained when we explain the parse() method. The opening and closing tags are a bit more cumbersome, and the parseHTML function does some processing on them before calling the function that handles the opening and closing tags.

(2) Processing of the start label

Parsing the start tag first calls the parseStartTag() function and then passes the return value of the function as an argument to the handleStartTag() function for processing. The parseStartTag() function uses regular expressions to parse the start tag, and the results are treated as attributes of the match object.

match = {
  tagName: ' '.// Label name of the start label
  attrs: [], // An array of attributes in the tag
  start: startIndex, // Label start subscript
  unarySlash: undefined || '/'.// Check whether the tag is unary
  end: endIndex // Label end subscript
}
Copy the code

The handleStartTag() function takes a match object as an argument. It mainly has the following five functions:

ParseEndTag () is used to close

when the top tag of the stack is

and the start tag of the current parse is a paragraph content model. 2. The parseEndTag() method is called to close the current parseEndTag and give a warning if the end tag is omitted and is the same as the top tag. Attrs is an array of objects. Each object has two attributes: name(attribute name) and value(decoded attribute value). 4. Push the current parse tag information into the stack and change the value of the variable lastTag to the name of the top tag on the stack. 5. Call the start function passed in with information about the current parse tag.

(3) Processing of the end label

Parsing the closing tag calls the parseEndTag() function. This function has the following four functions:

1. Check whether closing labels are missing. 2. Handle the remaining labels in the stack. 3. Handle

and
tags. 4. Call the passed end() method to process the closing tag.

The handleStartTag() function describes the case where

calls parseEndTag(). Here is an introduction to the

tag MDN:

The start tag is required, and the end tag can be omitted in the following cases.<p>Element immediately after<address>.<article>.<aside>.<blockquote>.<div>.<dl>.<fieldset>.<footer>.<form>.<h1>.<h2>.<h3>.<h4>.<h5>.<h6>.<header>.<hr>.<menu>.<nav>.<ol>.<pre>.<section>.<table>.<ul>Or another<p>Elements; Or there is nothing else in the parent element, and the parent element is not<a>Elements.Copy the code

If

is followed by the above elements, the parseEndTag() function emulates the browser’s behavior and completes the

tag automatically. As follows:

<p><h5></h5></p>
Copy the code

The above HTML code is parsed into the following code:

<p></p><h5></h5><p></p>
Copy the code

The parseEndTag() method is called if the current parsed tag can omit the end tag and is identical to the top of the stack. ParseEndTag () closes the second tag and warns if the first tag is not closed.

<li>123<li>456
Copy the code

The above HTML code is parsed into the following code with a warning that the first tag is not closed.

<li>123<li></li>456
Copy the code

In addition, just write down the closing tag < / p > < / br >, the browser will be converted to < / p > < p > < / p >, will be converted < / br > < br >. Vue converts template strings in line with the browser, converting the two closed tags in the handleStartTag() function.

2. Parsing function parse

Syntactic analysis function to parse the code in/SRC/compiler/parser/index in js. The parse function code that omits the details looks like this:

export function parse (template,options){
  const stack = []
  let root
  let currentParent
  /* omit... * /

  parseHTML(template, {
    // Omit some arguments
    start (tag, attrs, unary, start, end) {/* omit the implementation */},
    end (tag, start, end) {/* omit the implementation */},
    chars (text, start, end) {/* omit the implementation */},
    comment (text, start, end) {/* omit the implementation */}})return root
}
Copy the code

(1) Overall analysis of syntactic analysis function

The variable root is the return value of the parseHTML function, which is the AST that is ultimately generated. Vue divides the nodes in the template into four types: label node, text node containing literal expression, ordinary text node, and comment node. Ordinary text node and comment node are both plain text nodes, which are counted as the same type. There are three types of node description objects in the AST: label node description object, expression text node description object, and plain text node description object. The basic properties of different types of objects are as follows:

// Label node types describe basic object properties
element = {
  type: 1.// Label Node type identifier
  tag: ' '.// Label name
  attrsList: [], // An array of objects that store the names and values of the tag attributes
  attrsMap: {}, // Label attributes object, which stores label attributes as key-value pairs
  rawAttrsMap: {} // Convert attrsList to an object whose attribute is the tag attribute name
  parent: {}, // Parent label node
  children: [], // Array of child nodes
  start: Number.// The position of the first character of the start tag in the HTML string
  end: Number // Place the last character of the closing tag in the HTML string
}

// Expression text nodes describe basic object properties
expression = {
  type: 2.// Expression text node type identifier
  expression: ' '.// Expression text string, variable wrapped around _s()
  tokens: [] // Token for storing text. There are two types: text and expression
  text: ' '.// A text string
  start: Number.// The position of the first character of the expression text in the HTML string
  end: Number // The position of the last character of the expression text in the HTML string
}

// Plain text nodes describe basic object properties
text = {
  type: 3.// Plain text node type identifier
  text: ' '.// A text string
  start: Number.// The position of the first character in the HTML string in plain text
  end: Number // The position of the last character in the HTML string in plain text
}
Copy the code

The AST is a tree-structured object that describes the parent and children of the object through label nodes. The parent attribute points to the parent node element descriptor, and the children attribute stores the element descriptor for all the children of the node. The parent property of the root node is undefined. The stack variable is used in conjunction with currentParent to correctly add children to the children property of the parent. A stack is the data structure of a stack that stores the parent and ancestor nodes of the currently parsed node. CurrentParent points to the parent node of the currently parsed content. In the process of lexical analysis, the corresponding functions are called when nodes are parsed for processing, as described below.

(2) Start label processing function start

In the start() function, the createASTElement() function is first called, passing in the tag name, tag attributes, and tag parent as parameters to generate a tag node type description object.

let element = createASTElement(tag, attrs, currentParent)
Copy the code

The label node object is as follows:

element = {
  type: 1,
  tag,
  attrsList: attrs,
  attrsMap: makeAttrsMap(attrs),
  rawAttrsMap: {},
  parent,
  children: []}Copy the code

If the start tag is SVG or Math, an additional NS attribute is added with the same value as the tag name. Then add the start and end attributes to the Element object, and use the attrsList attribute to format the rawAttrsMap attribute. Each function in the preTransforms function array is then called to process the Element object, along with a series of functions starting with Process. Many process* functions are declared in the same file as the parse functions, such as processFor, processIf, processOnce, and so on. These functions work the same way as the functions in the preTransforms function array, and are used to further process the current element description object. For platformization purposes, this set of functions is placed in separate folders. The Process family of functions is generic, while the preTransforms function array varies by platform. The process of processing elements with different attributes is quite complex. The main purpose of this article is to describe the compilation of template strings into rendering functions, which will be explained in more detail in future articles on the corresponding instructions. Finally, check whether the start tag is unary. If it is, call the closeElement method. The details of the closeElement method are described in the next section. If not, assign the Element object to the variable currentParent as the parent for subsequent parsing and push the Element object onto the stack.

(3), end tag processing function end

The end tag handler function has relatively simple logic and looks like this:

end (tag, start, end) {
  const element = stack[stack.length - 1]
  // pop stack
  stack.length -= 1
  currentParent = stack[stack.length - 1]
  if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
    element.end = end
  }
  closeElement(element)
}
Copy the code

First remove the top node and assign it to the Element variable, then remove the top node from the stack and point the currentParent variable to the top node. This is done because the current node is handled as the parent node and the scope is returned to the upper node. The end method is then added to the node where the closing tag is located, and the Element variable is passed to the closeElement function. In addition to calling the functions in the postTransforms array to process the node, the closeElement function also calls the corresponding Process * for further processing, depending on the case. The other main function of this function is to push the current node into the children property of the parent node and add the parent node to point to the parent node.

currentParent.children.push(element)
element.parent = currentParent
Copy the code

(4) Text processing function chars

The core code for the chars function is as follows:

let res
let child
if(! inVPre && text ! = =' ' && (res = parseText(text, delimiters))) {
  child = {
    type: 2.expression: res.expression,
    tokens: res.tokens,
    text
  }
} else if(text ! = =' '| |! children.length || children[children.length -1].t! = =' ') {
  child = {
    type: 3,
    text
  }
}
if (child) {
  if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
    child.start = start
    child.end = end
  }
  children.push(child)
}
Copy the code

The chars function calls parseText to handle text strings. ParseText parses the text containing literal expressions. If there are no literal expressions in the text, return empty values. A node description object of type 2 is generated if the text contains a literal expression, and a node description object of type 3 is generated if the text is plain. We then add the start and end characters to the node object, and finally push the node description object to the children array property of the parent node. For example, where title is variable data:

<div>Title: {{title}}.</div>
<div>456<div>
Copy the code

The text containing the literal expression under the first

tag is parsed by parseText and returns the following object:
{
  expression: "Title:"+_s(title)+"。".tokens: [ "Title:", { @binding: "title" }, "。"]}Copy the code

The final node description object generated by the text under the first

tag is:
{
  type: 2.expression: "Title:"+_s(title)+"。".tokens: [ "Title:", { @binding: "title" }, "。"].text: "Title: {{title}}".start: Number.end: Number
}
Copy the code

The final node description object generated by the text under the second

tag is:
{
  type: 3.text: "456".start: Number.end: Number
}
Copy the code

(5) Comment text processing function

The logic of comment text processing is similar to that of chars, except that the generated node description object of type 3 has an additional attribute: isComment, which has a value of true and is the identifier of the comment text description node. The comment code of the processing function is as follows:

comment (text, start, end) {
  if (currentParent) {
    const child = {
      type: 3,
      text,
      isComment: true
    }
    if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
      child.start = start
      child.end = end
    }
    currentParent.children.push(child)
  }
}
Copy the code

Third, optimize AST

The optimization approach of the AST is primarily to detect pure static subtrees of the DOM that do not need to be changed, which has two benefits:

1. Upgrade the purely static node description object to a constant, which does not need to be regenerated during re-rendering. Skip this section in the Virtual DOM patching process.

AST optimization is done with the optimize function as follows:

export function optimize (root, options) {
  if(! root)return
  isStaticKey = genStaticKeysCached(options.staticKeys || ' ')
  isPlatformReservedTag = options.isReservedTag || no
  
  markStatic(root)
  
  markStaticRoots(root, false)}Copy the code

AST optimization logic is relatively simple, divided into two steps:

1. Use the markStatic function to markStatic nodes. 2. Mark the static root node with the markStaticRoots method.

1. Mark the static node

The code to mark the static node function markStatic looks like this:

function markStatic (node) {
  node.static = isStatic(node)
  if (node.type === 1) {
    /* omit some code */
    }
    for (let i = 0, l = node.children.length; i < l; i++) {
      const child = node.children[i]
      markStatic(child)
      if(! child.static) { node.static =false}}/* omit to handle if else instructions, etc. */}}Copy the code

The markStatic function first calls isStatic to determine whether the node isStatic, and adds a Boolean variable static to the node description object to identify whether the node isStatic. If it is an element node and has child nodes, the markStatic function is recursively called to process each child node. If one of the child nodes is not a static node, the element node is not a static node, i.e. the static property value is false. The markStatic function to determine whether a node is static looks like this:

function isStatic (node) {
  if (node.type === 2) { return false }
  if (node.type === 3) { return true }
  return!!!!! (node.pre || ( ! node.hasBindings &&// no dynamic bindings! node.if && ! node.for &&// not v-if or v-for or v-else! isBuiltInTag(node.tag) &&// not a built-in
    isPlatformReservedTag(node.tag) && // not a component! isDirectChildOfTemplateFor(node) &&Object.keys(node).every(isStaticKey)
  ))
}
Copy the code

There are four rules to determine whether a node is static:

1. Text nodes containing literal expressions are non-static nodes. 2. Plain text nodes are static nodes. 3. Object Description The object with pre attribute (label with V-Pre attribute) is a static node. 4. If a label node meets the following conditions at the same time, it is a static node: No v-if, v-for, no instructions other than V-once, no platform-reserved labels, no component, no direct child of the TEMPLATE tag with v-for, and all attributes of the node satisfy static keys.

2. Mark the static root node

The function markStaticRoots that marks the static root node looks like this:

function markStaticRoots (node, isInFor) {
  if (node.type === 1) {
    if (node.static || node.once) {
      node.staticInFor = isInFor
    }
    if(node.static && node.children.length && ! ( node.children.length ===1 &&
      node.children[0].type === 3
    )) {
      node.staticRoot = true
      return
    } else {
      node.staticRoot = false
    }
    if (node.children) {
      for (let i = 0, l = node.children.length; i < l; i++) { markStaticRoots(node.children[i], isInFor || !! node.for) } }/* omit to handle if else instructions, etc. */}}Copy the code

The staticRoot attribute is used to mark whether a node is a staticRoot node. Only a tag node can be a staticRoot node. The criteria for judging a staticRoot node are as follows:

1. If static is true, the node is static. 2. The label node has child nodes. 3. Tag nodes do not have only one plain text node.

The reason for requiring a label node to have more than one plain text node is that it is less profitable to mark such a node as a static root node, and it is best to keep it fresh all the time. After determining whether the current node is a static root node, the markStaticRoots function is recursively called to process each child node of the node. After the AST optimization function optimize, each node’s description object is added with a Boolean static attribute that identifies it as a static node. StaticRoot is added to the label node description object of type 1 to identify whether it is a staticRoot node.

4. Generate the rendering function

Converting the optimized AST into a render function string is done in the generate function as follows:

export function generate (ast, options){
  const state = new CodegenState(options)
  const code = ast ? genElement(ast, state) : '_c("div")'
  return {
    render: `with(this){return ${code}} `.staticRenderFns: state.staticRenderFns
  }
}
Copy the code

The code for generate is deceptively simple, but the logic involved is complex because you have to deal with a wide variety of situations. This article uses a simple example to describe the process of AST generating render function strings. Other instructions such as V-for and V-if will be introduced in the following specific articles.

  <div id="app" class="home" @click="showTitle">
    <div class="title">Title: {{title}}.</div>
    <div class="content">
      <span>456</span>
    </div>
  </div>
Copy the code

The template strings are parsed into an AST by the parse function, and the AST looks like this after the optimize function:

ast = {
  tag: "div".type: 1.attrs: [{dynamic: undefined.end: 13.name: "id".start: 5.value: ""app""}].attrsList: [{end: 13.name: "id".start: 5.value: "app" },
    { end: 45.name: "@click".start: 27.value: "showTitle"}].attrsMap: {id: "app".class: "home", @click: "showTitle"},
  end: 178.events: {click: {dynamic: false.end: 45.start: 27.value: "showTitle"}},
  hasBindings: true.parent: undefined.plain: false.rawAttrsMap: {
    @click: {end: 45.name: "@click".start: 27.value: "showTitle"},
    class: {end: 26.name: "class".start: 14.value: "home"},
    id: {end: 13.name: "id".start: 5.value: "app"}},start: 0.static: false.staticClass: ""home"".staticRoot: false.children: [{tag: "div".type: 1.attrsList: [].attrsMap: {class: "title"},
      children: [{
        text: "Title: {{title}}".type: 2.end: 87.expression: ""Title:"+_s(title)+"."".start: 74.static: false.tokens: (3) ["Title:", {@binding: "title"}, "。"]}],end: 93.parent: {/* The introduction of the parent node description object */},
      plain: false.rawAttrsMap: {
        class: {end: 73.name: "class".start: 60.value: "title"}},start: 55.static: false.staticClass: ""title"".staticRoot: false
    },
    {
      text: "".type: 3.end: 102.start: 93.static: true
    },
    {
      tag: "div".type: 1.attrsList: [].attrsMap: {class: "content"},
      children: [{tag: "span".type: 1.attrsList: [].attrsMap: {},
          children: [{text: "456".type: 3.end: 145.start: 142.static: true}].end: 152.parent: {/* The introduction of the parent node description object */},
          plain: true.rawAttrsMap: {},
          start: 136.static: true}].end: 167.parent: {/* The introduction of the parent node description object */},
      plain: false.rawAttrsMap: {class: {end: 122.name: "class".start: 107.value: "content"}},
      start: 102.static: true.staticClass: ""content"".staticInFor: false.staticRoot: true}}]Copy the code

The

node description object with id app has three objects in the children property array, because its two

child nodes have Spaces between them and count as a plain text node. The generate function first instantiates the CodegenState object based on the configuration parameter object options passed in. The code for the class CodegenState looks like this:

export class CodegenState {
  constructor (options) {
    this.options = options
    this.warn = options.warn || baseWarn
    this.transforms = pluckModuleFunction(options.modules, 'transformCode')
    this.dataGenFns = pluckModuleFunction(options.modules, 'genData')
    this.directives = extend(extend({}, baseDirectives), options.directives)
    const isReservedTag = options.isReservedTag || no
    this.maybeComponent = (el) = >!!!!! el.component || ! isReservedTag(el.tag)this.onceId = 0
    this.staticRenderFns = []
    this.pre = false}}Copy the code

Here we focus on the dataGenFns and staticRenderFns properties on this object. The staticRenderFns property is an array that stores the render function string of the static root node and is one of the return object properties of generate. The dataGenFns array stores the genData function in modules, which handles the class and :class attributes, and the style and :style attributes of the tag description object, respectively.

dataGenFns = [
  function genData (el) {
    var data = ' ';
    if (el.staticClass) {
      data += "staticClass:" + (el.staticClass) + ",";
    }
    if (el.classBinding) {
      data += "class:" + (el.classBinding) + ",";
    }
    return data
  },
  function genData(el) {
    var data = ' ';
    if (el.staticStyle) {
      data += "staticStyle:" + (el.staticStyle) + ",";
    }
    if (el.styleBinding) {
      data += "style:(" + (el.styleBinding) + "),";
    }
    return data
  }
]
Copy the code

After generating CodegenState’s instantiation object state, the generate function passes the AST and state to the genElement function, ultimately generating the render function string. The code for the genElement function with respect to the sample HTML is as follows:

export function genElement (el, state) {
  if(el.staticRoot && ! el.staticProcessed) {return genStatic(el, state)
  } 
  /* omit some judgment conditions */
  else {
    let code
    /* omit to label component */
    let data
    if(! el.plain || (el.pre && state.maybeComponent(el))) { data = genData(el, state) }const children = el.inlineTemplate ? null : genChildren(el, state, true)
    code = `_c('${el.tag}'${
      data ? `,${data}` : ' ' // data
    }${
      children ? `,${children}` : ' ' // children
    }) `
    /* omit some code */
    return code
  }
}
Copy the code

In the case of the ast generated by the example, the genElement function is called first:

data = genData(el, state)
Copy the code

The genData function mainly processes the attributes in the tag and returns them as strings. When a tag has a class or style attribute, it loops through the function in the state. DataGenFns array described earlier. The data value of the current tag description object after genData function processing is:

"{staticClass:"home",attrs:{"id":"app"},on:{"click":showTitle}}"
Copy the code

The genChildren function is then called to process the objects in the children property array of the current tag description object, that is, to process the child node description object.

const children = genChildren(el, state, true)
Copy the code

The genChildren function code is as follows:

function genChildren (el,state,checkSkip,altGenElement,altGenNode) {
  const children = el.children
  if (children.length) {
    const el = children[0]
    /* omit some code */
    const gen = altGenNode || genNode
    return ` [${children.map(c => gen(c, state)).join(', ')}]${
      normalizationType ? `,${normalizationType}` : ' '
    }`}}Copy the code

The main logic of this function is to use the genNode function to process the individual node description objects in the children property of the object separately.

function genNode (node, state) {
  if (node.type === 1) {
    return genElement(node, state)
  } else if (node.type === 3 && node.isComment) {
    return genComment(node)
  } else {
    return genText(node)
  }
}
Copy the code

The genNode function calls different functions for processing according to different node types, using genElement function to process tag nodes, genComment function to process comment nodes, and genText function to process text nodes. The annotation node is handled in a simpler way, wrapping the text attribute of the annotation node directly in the string form of the _e() function.

function genComment(comment) {
  return `_e(The ${JSON.stringify(comment.text)}) `
}
Copy the code

GenText, the processing function of the text node, uses the string form of _v() function to wrap the text content. The pure text node content is the value of the text attribute of the node description object, and the text content containing the literal expression is the value of the expression attribute of the node description object.

function genText (text) {
  return `_v(${text.type === 2
    ? text.expression // no need for () because already wrapped in _s()
    : transformSpecialNewlines(JSON.stringify(text.text))
  }) `
}
Copy the code

The genElement function, after taking the function string of the child node, uses a comma to concatenate the tag name, tag attribute string, child node function string, and finally wraps it in the string form of _v() function. Use new Function to create the following code:

_c(tag,data,children)
Copy the code

, whose class is content, is the static root node, which is handled by calling the genStatic function in genElement.
function genStatic (el, state) {
  /* omit some code */
  state.staticRenderFns.push(`with(this){return ${genElement(el, state)}} `)
  / *... * /
  return `_m(${
    state.staticRenderFns.length - 1
  }${
    el.staticInFor ? ',true' : ' '
  }) `
}
Copy the code

The processed function string is pushed into the array state.staticrenderfns. The static root function string looks like this:

"with(this){return _c('div',{staticClass:"content"},[_c('span',[_v("456")]])}"
Copy the code

In summary, the function generate returns:

{
  render: "with(this){return _c('div',{staticClass:"home",attrs:{"id":"app"},on:{"click":showTitle}},[_c('div',{staticClass:"title"},[_v("Title:"+_s(title)+".")]),_v(" "),_m(0)])}".staticRenderFns: ["with(this){return _c('div',{staticClass:"content"},[_c('span',[_v("456")]])}"]}Copy the code

The Function string generated by the compiled example code and the static root node Function string processed by new Function look like this:

render = function() {
  with(this) {return _c(
      'div',
      {
        staticClass:"home".attrs: {"id":"app"},
        on: {"click":showTitle}
      },
      [
        _c(
          'div',
          {staticClass:"title"},
          [_v("Title:"+_s(title)+"。")]
        ),
        _v(""),
        _m(0)])}}Copy the code

_c functions defined in SRC/core/instance/render. Js, used to create the VNode. Other internal methods for compiling render helpers are defined in the SRC /core/instance/render-helpers/index.js installRenderHelpers function. The _v function is used to create text vNodes; The _s function is used to handle literal expressions returning result strings; The _m function handles the static root node. These processes for generating vNodes from render functions will be explained in more detail when we cover the Virtual DOM.

Five, the summary

Vue uses the technique of function currization to realize the compilation function of different platforms. The core compilation process is divided into three steps: generate AST from template string, optimize AST, and generate render function from AST. The AST generation process is divided into two steps: lexical analysis and syntax analysis. In lexical analysis, parsing an HTML string character by character. First determine whether the beginning of the string to be parsed is an element tag or a text tag, which can be divided into: start tag, end tag, comment tag, document type declaration tag and conditional comment tag, and then do corresponding processing according to the type of the string to be parsed. The parsing function parse generates three types of node description objects based on the results of lexical parsing: label node description object, literal expression text node description object, and plain text node description object. The AST relies on the parent attribute of the tag node pointing to the parent node and the children attribute containing the children node to construct the tree structure. AST optimization is divided into two steps: marking static nodes and marking static root nodes. The main approach to optimization is to mark static root nodes that do not need to be repeatedly compiled and whose DOM does not change, and to ignore such nodes while doing the relevant processing. Render Function strings are generated by splicing various nodes into strings wrapped in different functions according to the AST, and finally converting Function strings into real render functions through new Function. Welcome to pay attention to the public number: front-end Taohuayuan, mutual exchange and learning!