comprehensive

A virtual DOM requires a VNode, so where does a VNode come from?

A VNode is produced by compiling user-written templates

Vue compiles what looks like native HTML that the user wrote in the
tag

After a series of logical processing to generate the render function, also known as the render function

The Render function generates the template content to the corresponding VNode

The user in
tag to write similar to the content of native HTML to compile, find out the content of native HTML, and then find out the non-native HTML, after a series of logical processing to generate the rendering function, that is, the render function of this section of the process is called template compilation process.

Internal processes

Abstract syntax tree AST

Online Test AST

process

After parsing a bunch of string templates into an abstract syntax tree AST, we can perform various manipulations on them, and then use the AST to generate the Render function

  1. Template parsing stage: a bunch of template strings are parsed into abstract syntax trees by means of reAST;
  2. Optimization phase: traversalAST, find out the static nodes and mark them;
  3. Code generation phase: willASTConvert to render functions;

Template parser SRC/compiler/parser/index, js

Parser parser

There can’t be just one parser, it should have an HTML parser that parses regular HTML, but also a text parser that parses text and a filter parser that parses text if it contains filters

/* The main function in this file converts HTML templates into AST trees */
export function parse (template: string, options: CompilerOptions) :ASTElement | void { warn = options.warn || baseWarn platformIsPreTag = options.isPreTag || no platformMustUseProp = options.mustUseProp ||  no platformGetTagNamespace = options.getTagNamespace || noconst isReservedTag = options.isReservedTag || no
  maybeComponent = (el: ASTElement) = >!!!!! ( el.component || el.attrsMap[':is'] ||
    el.attrsMap['v-bind:is') | |! (el.attrsMap.is ? isReservedTag(el.attrsMap.is) : isReservedTag(el.tag)) ) transforms = pluckModuleFunction(options.modules,'transformNode')
  preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
  postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')

  delimiters = options.delimiters

  const stack = []
  constpreserveWhitespace = options.preserveWhitespace ! = =false
  const whitespaceOption = options.whitespace
  let root
  let currentParent
  let inVPre = false
  let inPre = false
  let warned = false

  function warnOnce (msg, range) {
    if(! warned) { warned =true
      warn(msg, range)
    }
  }

  function closeElement (element) {
    trimEndingWhitespace(element)
    if(! inVPre && ! element.processed) { element = processElement(element, options) }// tree management
    if(! stack.length && element ! == root) {// allow root elements with v-if, v-else-if and v-else
      if (root.if && (element.elseif || element.else)) {
        if(process.env.NODE_ENV ! = ='production') {
          checkRootConstraints(element)
        }
        addIfCondition(root, {
          exp: element.elseif,
          block: element
        })
      } else if(process.env.NODE_ENV ! = ='production') {
        warnOnce(
          `Component template should contain exactly one root element. ` +
          `If you are using v-if on multiple elements, ` +
          `use v-else-if to chain them instead.`,
          { start: element.start }
        )
      }
    }
    if(currentParent && ! element.forbidden) {if (element.elseif || element.else) {
        processIfConditions(element, currentParent)
      } else {
        if (element.slotScope) {
          // scoped slot
          // keep it in the children list so that v-else(-if) conditions can
          // find it as the prev node.
          const name = element.slotTarget || '"default"'; (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element } currentParent.children.push(element) element.parent = currentParent } }// final children cleanup
    // filter out scoped slots
    element.children = element.children.filter(c= >! (c: any).slotScope)// remove trailing whitespace node again
    trimEndingWhitespace(element)

    // check pre state
    if (element.pre) {
      inVPre = false
    }
    if (platformIsPreTag(element.tag)) {
      inPre = false
    }
    // apply post-transforms
    for (let i = 0; i < postTransforms.length; i++) {
      postTransforms[i](element, options)
    }
  }

  function trimEndingWhitespace (el) {
    // remove trailing whitespace node
    if(! inPre) {let lastNode
      while (
        (lastNode = el.children[el.children.length - 1]) &&
        lastNode.type === 3 &&
        lastNode.text === ' '
      ) {
        el.children.pop()
      }
    }
  }

  function checkRootConstraints (el) {
    if (el.tag === 'slot' || el.tag === 'template') {
      warnOnce(
        `Cannot use <${el.tag}> as component root element because it may ` +
        'contain multiple nodes.',
        { start: el.start }
      )
    }
    if (el.attrsMap.hasOwnProperty('v-for')) {
      warnOnce(
        'Cannot use v-for on stateful component root element because ' +
        'it renders multiple elements.',
        el.rawAttrsMap['v-for']
      )
    }
  }

  parseHTML(template, {
    warn,
    expectHTML: options.expectHTML,
    isUnaryTag: options.isUnaryTag,
    canBeLeftOpenTag: options.canBeLeftOpenTag,
    shouldDecodeNewlines: options.shouldDecodeNewlines,
    shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
    shouldKeepComment: options.comments,
    outputSourceRange: options.outputSourceRange,
    start (tag, attrs, unary, start, end) {
      // check namespace.
      // inherit parent ns if there is one
      const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)

      // handle IE svg bug
      /* istanbul ignore if */
      if (isIE && ns === 'svg') {
        attrs = guardIESVGBug(attrs)
      }

      let element: ASTElement = createASTElement(tag, attrs, currentParent)
      if (ns) {
        element.ns = ns
      }

      if(process.env.NODE_ENV ! = ='production') {
        if (options.outputSourceRange) {
          element.start = start
          element.end = end
          element.rawAttrsMap = element.attrsList.reduce((cumulated, attr) = > {
            cumulated[attr.name] = attr
            return cumulated
          }, {})
        }
        attrs.forEach(attr= > {
          if (invalidAttributeRE.test(attr.name)) {
            warn(
              `Invalid dynamic argument expression: attribute names cannot contain ` +
              `spaces, quotes, <, >, / or =.`,
              {
                start: attr.start + attr.name.indexOf(` [`),
                end: attr.start + attr.name.length
              }
            )
          }
        })
      }

      if(isForbiddenTag(element) && ! isServerRendering()) { element.forbidden =trueprocess.env.NODE_ENV ! = ='production' && warn(
          'Templates should only be responsible for mapping the state to the ' +
          'UI. Avoid placing tags with side-effects in your templates, such as ' +
          ` <${tag}> ` + ', as they will not be parsed.',
          { start: element.start }
        )
      }

      // apply pre-transforms
      for (let i = 0; i < preTransforms.length; i++) {
        element = preTransforms[i](element, options) || element
      }

      if(! inVPre) { processPre(element)if (element.pre) {
          inVPre = true}}if (platformIsPreTag(element.tag)) {
        inPre = true
      }
      if (inVPre) {
        processRawAttrs(element)
      } else if(! element.processed) {// structural directives
        processFor(element)
        processIf(element)
        processOnce(element)
      }

      if(! root) { root = elementif(process.env.NODE_ENV ! = ='production') {
          checkRootConstraints(root)
        }
      }

      if(! unary) { currentParent = element stack.push(element) }else {
        closeElement(element)
      }
    },

    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)
    },

    chars (text: string, start: number, end: number) {
      if(! currentParent) {if(process.env.NODE_ENV ! = ='production') {
          if (text === template) {
            warnOnce(
              'Component template requires a root element, rather than just text.',
              { start }
            )
          } else if ((text = text.trim())) {
            warnOnce(
              `text "${text}" outside root element will be ignored.`,
              { start }
            )
          }
        }
        return
      }
      // IE textarea placeholder bug
      /* istanbul ignore if */
      if (isIE &&
        currentParent.tag === 'textarea' &&
        currentParent.attrsMap.placeholder === text
      ) {
        return
      }
      const children = currentParent.children
      if (inPre || text.trim()) {
        text = isTextTag(currentParent) ? text : decodeHTMLCached(text)
      } else if(! children.length) {// remove the whitespace-only node right after an opening tag
        text = ' '
      } else if (whitespaceOption) {
        if (whitespaceOption === 'condense') {
          // in condense mode, remove the whitespace node if it contains
          // line break, otherwise condense to a single space
          text = lineBreakRE.test(text) ? ' ' : ' '
        } else {
          text = ' '}}else {
        text = preserveWhitespace ? ' ' : ' '
      }
      if (text) {
        if(! inPre && whitespaceOption ==='condense') {
          // condense consecutive whitespaces into single space
          text = text.replace(whitespaceRE, ' ')}let res
        letchild: ? ASTNode// If a text message is encountered, the text parser parseText function is called for text parsing
        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].text ! = =' ') {
          child = {
            type: 3,
            text
          }
        }
        if (child) {
          if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
            child.start = start
            child.end = end
          }
          children.push(child)
        }
      }
    },
    comment (text: string, start, end) {
      // adding anything as a sibling to the root node is forbidden
      // comments should still be allowed, but ignored
      if (currentParent) {
        const child: ASTText = {
          type: 3,
          text,
          isComment: true
        }
        if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
          child.start = start
          child.end = end
        }
        currentParent.children.push(child)
      }
    }
  })
  return root
}
Copy the code

Template parsing is actually based on the characteristics of the content to be parsed using regular and other ways to extract effective information parsing, according to the different parsing content is divided into HTML parser, text parser and filter parser. And text information and filter information exists in the HTML tags, so first in the parser main function parse HTML parser parseHTML function called the template parsing a string, if encountered during parsing text or filter information and calls the corresponding parser for parsing, finishing on the interpretation of the template string