When I was sorting out this article, I felt confused. What I was confused was that I didn’t know how to use a better way to make this part of the tedious content easy for you to understand. I also hope that this article can be used as a guide to your reading, so that you can look at the guide and the source code together.

How do we find the final approach we need to focus on

Remember the previous “hand in hand to take you through the vUE part of the source”? From there, we already know that SRC /platform/ Web/entry-runtime-with-Compiler. js converted the template to render function when it overrode the prototype’s $mount method, so we’ll use that as an entry point.

  const { render, staticRenderFns } = compileToFunctions(template, {
    shouldDecodeNewlines,
    shouldDecodeNewlinesForHref,
    delimiters: options.delimiters,
    comments: options.comments
  }, this)
Copy the code

Where do compileToFunctions come from? Seriously the problem to see the source code is quite round, when first quoted on platforms/web/compiler/index, js, and found that is invoked the SRC/compiler/index js createCompiler method, CreateCompiler calls SRC /compiler/ create-Compiler. js createCompilerCreator, and compileToFunctions. Is by calling the SRC/compiler/to – function. The js createCompileToFunctionFn to create, so here, in order to easy to remember, we temporarily ignore all of the steps in the middle. Let’s start with the last step.

createCompileToFunctionFn(src/compiler/to-function.js)

Code a little long not here not all posted, I said, you watch.

try {
    new Function('return 1')}catch (e) {
	if (e.toString().match(/unsafe-eval|CSP/)) {
	  warn(
	    'It seems you are using the standalone build of Vue.js in an ' +
	    'environment with Content Security Policy that prohibits unsafe-eval. ' +
	    'The template compiler cannot work in this environment. Consider ' +
	    'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
	    'templates into render functions.')}}Copy the code

What does this code do? Eval (‘function fn() {return 1} ‘) {return 1} Why are we doing tests here? Good question, remember, keep reading and you’ll get the answer for yourself.

const key = options.delimiters
  ? String(options.delimiters) + template
  : template
if (cache[key]) {
  return cache[key]
}
Copy the code

Another piece of code for efficiency checks directly from the cache to see if there are already compiled results, and returns them directly.

const compiled = compile(template, options)
Copy the code

CreateCompilerCreator (SRC /compiler/create-compiler.js); createCompilerCreator (SRC /compiler/create-compiler.js);

compile(src/compiler/create-compiler.js)

In the compile method, there are three main things that are done

  1. Pass the passed CompilerOptions and mount it to finalOptions

    Where finalOptions will eventually become:

  1. willtemplate,finalOptionsThe incomingsrc/compiler/index.jsOf the filebaseCompileIn the.
  2. collectastError message during conversion.

baseCompile(src/compiler/index.js)

Here we go into baseCompile and see what baseCompile does.

parse
// Convert the incoming HTML to an AST syntax tree
const ast = parse(template.trim(), options)
Copy the code

Here we go. YesparseMethod converts the contents of the template we pass into the AST syntax tree

Together under the SRC/compiler/parser/index. The parse method in js file.

function parse (template, options){ warn = options.warn || baseWarn platformIsPreTag = options.isPreTag || no platformMustUseProp = options.mustUseProp ||  no platformGetTagNamespace = options.getTagNamespace || no// pluckModuleFunction: Find out options.mudules where each property contains the key method
  transforms = pluckModuleFunction(options.modules, 'transformNode')
  preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
  postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')

  delimiters = options.delimiters
  / / store astNode
  const stack = []
  constpreserveWhitespace = options.preserveWhitespace ! = =false
  // Define the root node
  let root
  // The parent of the current processing node
  let currentParent
  // Indicates whether there is a v-pre in the attribute
  / / what is v - pre, see https://cn.vuejs.org/v2/api/#v-pre
  let inVPre = false
  // Indicates whether the label is pre
  let inPre = false
  // Indicates whether WARN has been triggered
  let warned = false

  function warnOnce (msg) {}

  function closeElement (element) {}

  // Parsing incoming HTML in a loop
  parseHTML(params)
  /** * handle v-pre * @param {*} el */
  function processPre() {}
  
  /** * Handles HTML native attributes * @param {*} el */
  function processRawAttrs (el) {}
  return root
}
Copy the code

As you can see from the above section, it is the parseHTML method that does the actual conversion. We omit the parseHTML parameter above, because the parseHTML method uses the start, end, chars, and comment methods. Here I’ll break it down at the end of the article and provide a special comment for each method so you can read it.

SRC/Compiler /parser/html-parser.js to look at the parseHTML method.

function parseHTML (html, options) {
  const stack = []
  const expectHTML = options.expectHTML
  const isUnaryTag = options.isUnaryTag || no
  const canBeLeftOpenTag = options.canBeLeftOpenTag || no
  // Declare index, identifying the index currently processing the incoming HTML
  let index = 0
  let last, lastTag
  // Loop through the HTML
  while(html) {... }// Clean up any remaining tags
  parseEndTag()
  /** * modifies the current processing tag index and truncates the HTML processed portion * @param {*} n digits */
  function advance (n) {}

  /** * process the start tag and place the attribute in attrs */
  function parseStartTag () {}

  /** * Process the result of the parseStartTag process and generate the ast node * @param {*} match result of the parseStartTag process */
  function handleStartTag (match) {}

  * @param {*} tagName tagName * @param {*} start start position in HTML * @param {*} end position in HTML */
  function parseEndTag (tagName, start, end) {}}Copy the code

Here we’ve kept some of the snippets, the full comments, which I’ll put at the end of the article.

With the parse method, we get the entire abstract syntax tree.

optimize

Optimizing the current abstract syntax tree to identify static nodes will be covered in our next vNode article.

generate(scr/compiler/codegen/index.js)

This will convert our abstract syntax tree into a string for the corresponding Render method. If you are interested, read it for yourself and you will be a little clearer about the purpose of the method of mounting various _ letters for the prototype in Vue Instance

With (this){with(this){… }, so why does it check to see if eval is allowed when compiling template? .

summary

Convert template to render with parse, optimize, and generate in Compile.

The last

If you like, I will continue to bring you render time, virtual DOM related articles.

Appendix – Source code + notes

options.start

start (tag, attrs, unary) {
  // check namespace.
  // inherit parent ns if there is one
  const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)

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

  // Create an AST NODE
  let element: ASTElement = createASTElement(tag, attrs, currentParent)
  if (ns) {
    element.ns = ns
  }

  // Warning if the current node is 
  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.')}// 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
  }
  // Skip compilation if v-pre is included
  if (inVPre) {
    processRawAttrs(element)
  } else if(! element.processed) {// structural directives
    / / v - for processing
    processFor(element)
    // handle v-if v-else-if v-else
    processIf(element)
    / / processing v - once
    processOnce(element)
    // element-scope stuff
    // Handle ast node nodes, key, ref, slot, Component, attrs
    processElement(element, options)
  }

  // root Node constraint check
  function checkRootConstraints (el) {
    if(process.env.NODE_ENV ! = ='production') {
      // Slot and template cannot be root
      if (el.tag === 'slot' || el.tag === 'template') {
        warnOnce(
          `Cannot use <${el.tag}> as component root element because it may ` +
          'contain multiple nodes.')}// The root node cannot contain v-for
      if (el.attrsMap.hasOwnProperty('v-for')) {
        warnOnce(
          'Cannot use v-for on stateful component root element because ' +
          'it renders multiple elements.')}}}// tree management
  if(! root) { root = element checkRootConstraints(root) }else if(! stack.length) {// allow root elements with v-if, v-else-if and v-else
    if (root.if && (element.elseif || element.else)) {
      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.`)}}if(currentParent && ! element.forbidden) {if (element.elseif || element.else) {
      processIfConditions(element, currentParent)
    } else if (element.slotScope) { // scoped slot
      currentParent.plain = false
      const name = element.slotTarget || '"default"'; (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element }else {
      currentParent.children.push(element)
      element.parent = currentParent
    }
  }
  if(! unary) { currentParent = element stack.push(element) }else {
    closeElement(element)
  }
}
Copy the code

options.end

end () {
  // remove trailing whitespace
  // Fetch the last AST node in the stack
  const element = stack[stack.length - 1]
  // Find the most recently processed node
  const lastNode = element.children[element.children.length - 1]

  if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
    element.children.pop()
  }
  // pop stack
  // Remove element from the stack
  stack.length -= 1
  currentParent = stack[stack.length - 1]
  closeElement(element)
}
Copy the code

options.chars

chars (text: string) {
  // Text has no parent node processing
  if(! currentParent) {if(process.env.NODE_ENV ! = ='production') {
      if (text === template) {
        warnOnce(
          'Component template requires a root element, rather than just text.')}else if ((text = text.trim())) {
        warnOnce(
          `text "${text}" outside root element will be ignored.`)}}return
  }
  // IE textarea placeholder bug
  /* istanbul ignore if */
  if (isIE &&
    currentParent.tag === 'textarea' &&
    currentParent.attrsMap.placeholder === text
  ) {
    return
  }
  const children = currentParent.children
  // Format text
  text = inPre || text.trim()
    ? isTextTag(currentParent) ? text : decodeHTMLCached(text)
    // only preserve whitespace if its not right after a starting tag
    : preserveWhitespace && children.length ? ' ' : ' '
  if (text) {
    // Process the {{text}} part and convert {{text}} to
    // {expression: '_s(text)', token: [{'@binding': 'text'}]}
    let res
    if(! inVPre && text ! = =' ' && (res = parseText(text, delimiters))) {
      children.push({
        type: 2.expression: res.expression,
        tokens: res.tokens,
        text
      })
    } else if(text ! = =' '| |! children.length || children[children.length -1].text ! = =' ') {
      children.push({
        type: 3,
        text
      })
    }
  }
}
Copy the code

parseHTML

function parseHTML (html, options) {
  const stack = []
  const expectHTML = options.expectHTML
  const isUnaryTag = options.isUnaryTag || no
  const canBeLeftOpenTag = options.canBeLeftOpenTag || no
  // Declare index, identifying the index currently processing the incoming HTML
  let index = 0
  let last, lastTag
  while (html) {
    last = html
    // Make sure we're not in a plaintext content element like script/style
    if(! lastTag || ! isPlainTextElement(lastTag)) {let textEnd = html.indexOf('<')
      // Whether to start with <
      if (textEnd === 0) {
        // Comment:
        // Check if <! -- opening comment
        if (comment.test(html)) {
          const commentEnd = html.indexOf('-->')

          if (commentEnd >= 0) {
            if (options.shouldKeepComment) {
              options.comment(html.substring(4, commentEnd))
            }
            advance(commentEnd + 3)
            continue}}// Check for compatibility comments with 
      
        / / <! --[if IE 6]>
        // Special instructions for IE 6 here
        / / 
      
        // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
        if (conditionalComment.test(html)) {
          const conditionalEnd = html.indexOf('] > ')

          if (conditionalEnd >= 0) {
            advance(conditionalEnd + 2)
            continue}}// Check if 
      
        // Doctype:
        const doctypeMatch = html.match(doctype)
        if (doctypeMatch) {
          advance(doctypeMatch[0].length)
          continue
        }

        // Check whether it is an end label
        // End tag:
        const endTagMatch = html.match(endTag)
        if (endTagMatch) {
          const curIndex = index
          advance(endTagMatch[0].length)
          parseEndTag(endTagMatch[1], curIndex, index)
          continue
        }

        // After passing parseStartTag, HTML = '
      
...
'
// handle the tag as HTML = '...
'
// Return match = { // start: 0, // start index // end: 15, // end index // tagName: 'div' // attrs: [] // The array here matches the attributes of the tag for the re // } // Start tag: const startTagMatch = parseStartTag() if (startTagMatch) { handleStartTag(startTagMatch) if (shouldIgnoreFirstNewline(lastTag, html)) { advance(1)}continue}}let text, rest, next // Because our HTML code may have TAB position newlines and other operations that do not need parsing // Here we remove the garbage and continue looping through the HTML if (textEnd >= 0) { rest = html.slice(textEnd) while(! endTag.test(rest) && ! startTagOpen.test(rest) && ! comment.test(rest) && ! conditionalComment.test(rest) ) {// < in plain text, be forgiving and treat it as text next = rest.indexOf('<'.1) if (next < 0) break textEnd += next rest = html.slice(textEnd) } text = html.substring(0, textEnd) advance(textEnd) } if (textEnd < 0) { text = html html = ' ' } // Process characters if (options.chars && text) { options.chars(text) } } else { let endTagLength = 0 const stackedTag = lastTag.toLowerCase() const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?) (< / ' + stackedTag + '[^ >] * >)'.'i')) const rest = html.replace(reStackedTag, function (all, text, endTag) { endTagLength = endTag.length if(! isPlainTextElement(stackedTag) && stackedTag ! = ='noscript') { text = text .replace(/ /g.'$1') / / # 7298 .replace(/ /g.'$1')}if (shouldIgnoreFirstNewline(stackedTag, text)) { text = text.slice(1)}if (options.chars) { options.chars(text) } return ' ' }) index += html.length - rest.length html = rest parseEndTag(stackedTag, index - endTagLength, index) } if (html === last) { options.chars && options.chars(html) if(process.env.NODE_ENV ! = ='production' && !stack.length && options.warn) { options.warn(`Mal-formatted tag at end of template: "${html}"`)}break}}// Clean up any remaining tags parseEndTag() /** * modifies the current processing tag index and truncates the HTML processed portion * @param {*} n digits */ function advance (n) { index += n html = html.substring(n) } /** * process the start tag and place the attribute in attrs */ function parseStartTag () { const start = html.match(startTagOpen) if (start) { const match = { tagName: start[1].attrs: [].start: index } // After processing the header, remove the header advance(start[0].length) let end, attr [>]. If there is no tail, add the attribute of the current re to attrs. while(! (end = html.match(startTagClose)) && (attr = html.match(attribute))) { advance(attr[0].length) match.attrs.push(attr) } // Remove the tail [>] from the HTML to record the currently processed index if (end) { match.unarySlash = end[1] advance(end[0].length) match.end = index return match } } } /** * Process the result of the parseStartTag process and generate the ast node * @param {*} match result of the parseStartTag process */ function handleStartTag (match) { const tagName = match.tagName const unarySlash = match.unarySlash if (expectHTML) { if (lastTag === 'p' && isNonPhrasingTag(tagName)) { parseEndTag(lastTag) } if (canBeLeftOpenTag(tagName) && lastTag === tagName) { parseEndTag(tagName) } } constunary = isUnaryTag(tagName) || !! unarySlashconst l = match.attrs.length const attrs = new Array(l) for (let i = 0; i < l; i++) { const args = match.attrs[i] // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778 if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('" "') = = =- 1) { if (args[3= = =' ') { delete args[3]}if (args[4= = =' ') { delete args[4]}if (args[5= = =' ') { delete args[5]}}const value = args[3] || args[4] || args[5] | |' ' const shouldDecodeNewlines = tagName === 'a' && args[1= = ='href' ? options.shouldDecodeNewlinesForHref : options.shouldDecodeNewlines attrs[i] = { name: args[1].value: decodeAttr(value, shouldDecodeNewlines) } } if(! unary) { stack.push({tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs }) lastTag = tagName } if (options.start) { options.start(tagName, attrs, unary, match.start, match.end) } } * @param {*} tagName tagName * @param {*} start start position in HTML * @param {*} end position in HTML */ function parseEndTag (tagName, start, end) { let pos, lowerCasedTagName if (start == null) start = index if (end == null) end = index if (tagName) { lowerCasedTagName = tagName.toLowerCase() } // Find the closest opened tag of the same type // Find the node in the stack that matches the current tag, using reverse order to match the nearest one if (tagName) { for (pos = stack.length - 1; pos >= 0; pos--) { if (stack[pos].lowerCasedTag === lowerCasedTagName) { break}}}else { // If no tag name is provided, clean shop pos = 0 } if (pos >= 0) { // Close all the open elements, up the stack for (let i = stack.length - 1; i >= pos; i--) { if(process.env.NODE_ENV ! = ='production'&& (i > pos || ! tagName) && options.warn ) { options.warn(`tag <${stack[i].tag}> has no matching end tag.`)}if (options.end) { options.end(stack[i].tag, start, end) } } // Remove the open elements from the stack stack.length = pos lastTag = pos && stack[pos - 1].tag Br / / processing } else if (lowerCasedTagName === 'br') { if (options.start) { options.start(tagName, [], true, start, end) } // Process the p tag } else if (lowerCasedTagName === 'p') { if (options.start) { options.start(tagName, [], false, start, end) } if (options.end) { options.end(tagName, start, end) } } } } Copy the code