Interpretation of Vue source code -- Analysis of compiler

Posted by robotman321 on Wed, 02 Mar 2022 01:59:35 +0100

When learning becomes a habit, knowledge becomes common sense. Thank you for your attention, likes, collections and comments.

The new video and articles will be sent to WeChat official account for the first time. Li Yongning

The article has been included in github warehouse liyongning/blog , welcome to Watch and Star.

Special instructions

Due to the limited length of the article, Vue source code interpretation (8) - compiler parsing is divided into two parts, so please open it at the same time when reading this article Interpretation of Vue source code (8) -- Analysis of compiler (Part 2) Read together.

preface

Interpretation of Vue source code (4) -- asynchronous update Finally, when it comes to refreshing the watcher queue, execute each watcher Run method, by watcher Run calls watcher Get to execute the watcher Getter method to enter the actual update phase. If you are not familiar with this process, I suggest you read this article again.

When updating a render watcher, the updateComponent method is executed:

// /src/core/instance/lifecycle.js
const updateComponent = () => {
  // Execute VM_ Render() function, get the virtual DOM and pass vnode to_ update method, and then it's time to go to the patch stage
  vm._update(vm._render(), hydrating)
}

You can see that you need to execute VM before each update_ Render() method, VM_ Render is the render function we often hear, which is obtained in two ways:

  • Provided by users themselves. When writing components, use the render option instead of the template

  • The compiler compiles the component template to generate the render option

Today, let's go deep into the compiler to see how it compiles the class html template we usually write into the render function.

The core of the compiler consists of three parts:

  • Parse and convert the class html template into AST object

  • Optimization, also known as static marking, traverses AST objects, marks whether each node is a static node, and marks the static root node

  • Generate rendering function, and generate AST object into rendering function

Due to the large amount of code in the compiler, this part of knowledge is divided into three parts. The first part is parsing.

target

Deeply understand the parsing process of Vue compiler and how to convert class html template string into AST object.

Source code interpretation

Next, we go to the source code to find the answer.

Reading suggestions

Due to the huge amount of code in the parsing process, it is recommended that you grasp the main line: "parse HTML string template and generate AST object", and this AST object is the final result we want to get. Therefore, in the process of reading, you should record this AST object, which is helpful for understanding and makes you less likely to get lost.

You can also read the help section of the next article first to have an advance preparation and psychological expectation.

Entry - $mount

The entry location of the compiler is / SRC / platforms / Web / entry runtime with compiler JS, there are two ways to find this entry

/src/platforms/web/entry-runtime-with-compiler.js

/**
 * Compiler entry
 * Vue at runtime JS package does not have this part of the code. It is precompiled through the packer combined with Vue loader + Vue compiler utils to compile the template into render function
 * 
 * Just do one thing, get the rendering function of the component and set it to this$ Options
 */
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  // Mount point
  el = el && query(el)

  // The mount point cannot be body or html
  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  // Configuration item
  const options = this.$options
  // resolve template/el and convert to render function
  /**
   * If the user provides the render configuration item, the compilation phase will be skipped directly. Otherwise, the compilation phase will be entered
   *   Parse template and el and convert them to render function
   *   Priority: render > template > el
   */
  if (!options.render) {
    let template = options.template
    if (template) {
      // Processing template options
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          // {template: '#app'}. If template is an id selector, get the innerHtml of the element as the template
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        // Template is a normal element. Get its innerHtml as a template
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      // Set the el option to obtain the outerHtml of the el selector as the template
      template = getOuterHTML(el)
    }
    if (template) {
      // The template is ready and enters the compilation stage
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      // Static rendering function and dynamic rendering function
      const { render, staticRenderFns } = compileToFunctions(template, {
        // In a non production environment, compile time records the position index of the label attribute at the beginning and end of the template string
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        // Delimiter, default {}
        delimiters: options.delimiters,
        // Keep comments
        comments: options.comments
      }, this)
      // Put two rendering functions into this$ Options
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  // Perform mount
  return mount.call(this, el, hydrating)
}

compileToFunctions

/src/compiler/to-function.js

/**
 * 1,Execute the compilation function to get the compilation result - > compiled
 * 2,Process the error and tip generated during compilation and output them to the console respectively
 * 3,Convert the compiled string code into an executable function through new Function(codeStr)
 * 4,Cache compilation results
 * @param { string } template String template
 * @param { CompilerOptions } options Compile options
 * @param { Component } vm Component instance
 * @return { render, staticRenderFns }
 */
return function compileToFunctions(
  template: string,
  options?: CompilerOptions,
  vm?: Component
): CompiledFunctionResult {
  // Compilation options passed in
  options = extend({}, options)
  // journal
  const warn = options.warn || baseWarn
  delete options.warn

  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production') {
    // Detect possible CSP limitations
    try {
      new Function('return 1')
    } catch (e) {
      if (e.toString().match(/unsafe-eval|CSP/)) {
        // It seems that you are using the full version of Vue in a CSP insecure environment JS, the template compiler cannot work in such an environment.
        // Consider relaxing policy restrictions or precompiling your template as the render function
        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.'
        )
      }
    }
  }

  // If there is a cache, skip the compilation and get the results of the last compilation directly from the cache
  const key = options.delimiters
    ? String(options.delimiters) + template
    : template
  if (cache[key]) {
    return cache[key]
  }

  // Execute the compilation function to get the compilation result
  const compiled = compile(template, options)

  // Check the error and tip generated during compilation and output them to the console respectively
  if (process.env.NODE_ENV !== 'production') {
    if (compiled.errors && compiled.errors.length) {
      if (options.outputSourceRange) {
        compiled.errors.forEach(e => {
          warn(
            `Error compiling template:\n\n${e.msg}\n\n` +
            generateCodeFrame(template, e.start, e.end),
            vm
          )
        })
      } else {
        warn(
          `Error compiling template:\n\n${template}\n\n` +
          compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
          vm
        )
      }
    }
    if (compiled.tips && compiled.tips.length) {
      if (options.outputSourceRange) {
        compiled.tips.forEach(e => tip(e.msg, vm))
      } else {
        compiled.tips.forEach(msg => tip(msg, vm))
      }
    }
  }

  // Convert the compiled string code into a function, which is realized through new Function(code)
  // turn code into functions
  const res = {}
  const fnGenErrors = []
  res.render = createFunction(compiled.render, fnGenErrors)
  res.staticRenderFns = compiled.staticRenderFns.map(code => {
    return createFunction(code, fnGenErrors)
  })

  // When dealing with the errors in the above code conversion process, this step generally will not report an error unless the compiler itself makes an error
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production') {
    if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
      warn(
        `Failed to generate render function:\n\n` +
        fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'),
        vm
      )
    }
  }

  // Cache compilation results
  return (cache[key] = res)
}

compile

/src/compiler/create-compiler.js

/**
 * To compile functions, you do two things:
 *   1,Option merging: merge the options configuration item into final options (base options) to get the final compiled configuration object
 *   2,Call the core compiler baseCompile to get the compilation result
 *   3,Mount the error and tip generated during compilation to the compilation result and return the compilation result
 * @param {*} template Template
 * @param {*} options Configuration item
 * @returns 
 */
function compile(
  template: string,
  options?: CompilerOptions
): CompiledResult {
  // Create a compilation option object based on the platform specific compilation configuration
  const finalOptions = Object.create(baseOptions)
  const errors = []
  const tips = []

  // Log, responsible for recording error and tip
  let warn = (msg, range, tip) => {
    (tip ? tips : errors).push(msg)
  }

  // If compilation options exist, merge options and baseOptions
  if (options) {
    // Development environment
    if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
      // $flow-disable-line
      const leadingSpaceLength = template.match(/^\s*/)[0].length

      // Enhanced logging method
      warn = (msg, range, tip) => {
        const data: WarningMessage = { msg }
        if (range) {
          if (range.start != null) {
            data.start = range.start + leadingSpaceLength
          }
          if (range.end != null) {
            data.end = range.end + leadingSpaceLength
          }
        }
        (tip ? tips : errors).push(data)
      }
    }

    /**
     * Merge the configuration items in options into finalOptions
     */

    // Merge custom module s
    if (options.modules) {
      finalOptions.modules =
        (baseOptions.modules || []).concat(options.modules)
    }
    // Merge custom directives
    if (options.directives) {
      finalOptions.directives = extend(
        Object.create(baseOptions.directives || null),
        options.directives
      )
    }
    // Copy other configuration items
    for (const key in options) {
      if (key !== 'modules' && key !== 'directives') {
        finalOptions[key] = options[key]
      }
    }
  }

  // journal
  finalOptions.warn = warn

  // So far, the key point is finally reached. Call the core compilation function, pass the template string and the final compilation options, and get the compilation result
  // All of the above is to build the final compilation options of the platform
  const compiled = baseCompile(template.trim(), finalOptions)
  if (process.env.NODE_ENV !== 'production') {
    detectErrors(compiled.ast, warn)
  }
  // Mount the errors and prompts generated during compilation to the compilation results
  compiled.errors = errors
  compiled.tips = tips
  return compiled
}

baseOptions

/src/platforms/web/compiler/options.js

export const baseOptions: CompilerOptions = {
  expectHTML: true,
  // Handle class, style, v-model
  modules,
  // Processing instruction
  // Is it a pre tag
  isPreTag,
  // Is it a self closing label
  isUnaryTag,
  // Specifies some properties that should be bound using props
  mustUseProp,
  // You can only write the label of the start label, and the end label browser will complete it automatically
  canBeLeftOpenTag,
  // Is it a reserved tag (html + svg)
  isReservedTag,
  // Gets the namespace of the tag
  getTagNamespace,
  staticKeys: genStaticKeys(modules)
}

baseCompile

/src/compiler/index.js

/**
 * All the things we have done before have only one purpose, which is to build platform specific compilation options, such as web platform
 * 
 * 1,Parse html template into ast
 * 2,Static marking of ast tree
 * 3,Generate ast render function
 *    Put the static rendering function into code Staticrenderfns array
 *    code.render Is a dynamic rendering function
 *    Execute the rendering function to get vnode when rendering in the future
 */
function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  // The template is parsed into ast, and all the information of the element is set on the ast object of each node, such as label information, attribute information, slot information, parent node, child node, etc.
  // For the specific properties, check the start and end methods to process the start and end tags
  const ast = parse(template.trim(), options)
  // Optimize, traverse AST and make static marks for each node
  // Mark whether each node is a static node, and then further mark the static root node
  // In this way, these static nodes can be skipped in subsequent updates
  // Mark the static root, which is used to generate the render function stage and generate the render function of the static root node
  if (options.optimize !== false) {
    optimize(ast, options)
  }
  // Generate rendering function from AST and generate code like this, such as code render = "_c('div',{attrs:{"id":"app"}},_l((arr),function(item){return _c('div',{key:item},[_v(_s(item))])}),0)"
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
}

parse

Note: due to the large amount of code in this part, some adjustments have been made to the structure of the code to facilitate reading and understanding.

/src/compiler/parser/index.js

/**
 * 
 * Convert HTML string to AST
 * @param {*} template HTML Template
 * @param {*} options Platform specific compilation options
 * @returns root
 */
export function parse(
  template: s tring,
  options: CompilerOptions
): ASTElement | void {
  // journal
  warn = options.warn || baseWarn

  // Is it a pre tag
  platformIsPreTag = options.isPreTag || no
  // Properties that must be bound using props
  platformMustUseProp = options.mustUseProp || no
  // Gets the namespace of the tag
  platformGetTagNamespace = options.getTagNamespace || no
  // Is it a reserved tag (html + svg)
  const isReservedTag = options.isReservedTag || no
  // Judge whether an element is a component
  maybeComponent = (el: ASTElement) => !!el.component || !isReservedTag(el.tag)

  // Get options Methods of transformNode, preTransformNode and postTransformNode in the three modules of class, model and style under modules
  // Responsible for handling class, style and v-model on the element node
  transforms = pluckModuleFunction(options.modules, 'transformNode')
  preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
  postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')

  // Delimiter, such as: {}}
  delimiters = options.delimiters

  const stack = []
  // Space options
  const preserveWhitespace = options.preserveWhitespace !== false
  const whitespaceOption = options.whitespace
  // The root node takes root as the root, and the processed nodes will be attached to root according to the hierarchy. The last return is root, an ast syntax tree
  let root
  // Parent element of the current element
  let currentParent
  let inVPre = false
  let inPre = false
  let warned = false
  
  // Parse html template string and handle all tags and attributes on tags
  // parseHTMLOptions here will be used in later processing and will be further analyzed
  // If you analyze it in advance, it's easy for you to diverge
  parseHTML(template, parseHtmlOptions)
  
  // Returns the generated ast object
  return root

parseHTML

/src/compiler/parser/html-parser.js

/**
 * Loop through the html template string to process each tag and the attributes on the tag in turn
 * @param {*} html html Template
 * @param {*} options Configuration item
 */
export function parseHTML(html, options) {
  const stack = []
  const expectHTML = options.expectHTML
  // Is it a self closing label
  const isUnaryTag = options.isUnaryTag || no
  // Can I have only the start tag
  const canBeLeftOpenTag = options.canBeLeftOpenTag || no
  // Record the current starting position in the original html string
  let index = 0
  let last, lastTag
  while (html) {
    last = html
    // Make sure it's not in plain text elements like script, style, textarea
    if (!lastTag || !isPlainTextElement(lastTag)) {
      // Find the first < character
      let textEnd = html.indexOf('<')
      // textEnd === 0 means found at the beginning
      // Handle possible annotation labels, conditional annotation labels, Doctype, start labels and end labels respectively
      // After each case is processed, the loop will be truncated, the html string will be reset, the processed tags will be cut off, and the remaining html string templates will be processed in the next loop
      if (textEnd === 0) {
        // Handling annotation labels <-- xx -->
        if (comment.test(html)) {
          // End index of annotation label
          const commentEnd = html.indexOf('-->')

          if (commentEnd >= 0) {
            // Should comments be retained
            if (options.shouldKeepComment) {
              // Get: annotation content, start index and end index of annotation
              options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3)
            }
            // Adjust html and index variables
            advance(commentEnd + 3)
            continue
          }
        }

        // Processing condition comment label: <-- [if IE]>
        // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
        if (conditionalComment.test(html)) {
          // Find end position
          const conditionalEnd = html.indexOf(']>')

          if (conditionalEnd >= 0) {
            // Adjust html and index variables
            advance(conditionalEnd + 2)
            continue
          }
        }

        // Processing Doctype, <! Doctype html>
        const doctypeMatch = html.match(doctype)
        if (doctypeMatch) {
          advance(doctypeMatch[0].length)
          continue
        }

        /**
         * Dealing with the start tag and end tag is the karyotype part of the whole function. Don't worry about others
         * These two parts are constructing element ast
         */

        // Process end tag, such as < / div >
        const endTagMatch = html.match(endTag)
        if (endTagMatch) {
          const curIndex = index
          advance(endTagMatch[0].length)
          // Processing end tag
          parseEndTag(endTagMatch[1], curIndex, index)
          continue
        }

        // Process the start tag, such as < div id = "app" >, starttagmatch = {tagName: 'div', attrs: [[XX],...], start: index}
        const startTagMatch = parseStartTag()
        if (startTagMatch) {
          // Further processing the previous step, and finally calling options.start method
          // The real parsing work is done in this start method
          handleStartTag(startTagMatch)
          if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
            advance(1)
          }
          continue
        }
      }

      let text, rest, next
      if (textEnd >= 0) {
        // If you can come here, it means that although < XX is matched in html, it does not belong to the above situations,
        // It's just an ordinary paragraph of text: < I'm the text
        // So find the next < < from the html until < XX is the label of the above situations, and then it ends,
        // Throughout this process, we have been adjusting the value of textEnd as the starting position of the next valid tag in html

        // Intercept the content after textEnd in the html template string, rest = < XX
        rest = html.slice(textEnd)
        // This while loop is used to process plain text after < XX
        // Intercept the text content and find the start position (textEnd) of a valid label
        while (
          !endTag.test(rest) &&
          !startTagOpen.test(rest) &&
          !comment.test(rest) &&
          !conditionalComment.test(rest)
        ) {
          // It is considered that the content after < is plain text, and then find it again in these plain text<
          next = rest.indexOf('<', 1)
          // If < < is not found, the loop ends directly
          if (next < 0) break
          // Coming here means that < < is found in the subsequent string, and the index position is textEnd
          textEnd += next
          // Intercept the content of the html string template textEnd and assign it to rest, and continue to judge whether there is a label in the subsequent string
          rest = html.slice(textEnd)
        }
        // Go here to explain the end of the traversal. There are two cases: one is < followed by a piece of plain text, or find a valid label and intercept the text
        text = html.substring(0, textEnd)
      }

      // If textend < 0, it means that < < is not found in html, which means that html is a piece of text
      if (textEnd < 0) {
        text = html
      }

      // Intercept the text content from the html template string
      if (text) {
        advance(text.length)
      }

      // Processing text
      // Generate an ast object based on text, and then put the ast into its parent element,
      // I.e. currentparent In the children array
      if (options.chars && text) {
        options.chars(text, index - text.length, index)
      }
    } else {
      // Handle closed tags of script, style, textarea Tags
      let endTagLength = 0
      // Lowercase form of start label
      const stackedTag = lastTag.toLowerCase()
      const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'))
      // Match and process all text between the start tag and the end tag, such as < script > XX < / script >
      const rest = html.replace(reStackedTag, function (all, text, endTag) {
        endTagLength = endTag.length
        if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
          text = text
            .replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298
            .replace(/<!\[CDATA\[([\s\S]*?)]]>/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)
    }

    // The processing ends here. If there are still contents in the stack array, it indicates that there are labels that have not been closed, and a prompt message is given
    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}"`, { start: index + html.length })
      }
      break
    }
  }

  // Clean up any remaining tags
  parseEndTag()
}

advance

/src/compiler/parser/html-parser.js

/**
 * Reset html, html = all characters backward from the index n position
 * index It is the starting index of html in the original template string, and it is also the starting position of the character to be processed next time
 * @param {*} n Indexes
 */
function advance(n) {
  index += n
  html = html.substring(n)
}

parseStartTag

/src/compiler/parser/html-parser.js

/**
 * Parse the start tag, for example: < div id = "app" >
 * @returns { tagName: 'div', attrs: [[xx], ...], start: index }
 */
function parseStartTag() {
  const start = html.match(startTagOpen)
  if (start) {
    // Processing results
    const match = {
      // Tag name
      tagName: start[1],
      // Properties, placeholders
      attrs: [],
      // Start position of label
      start: index
    }
    /**
     * Adjust html and index, such as:
     *   html = ' id="app">'
     *   index = Index at this time
     *   start[0] = '<div'
     */
    advance(start[0].length)
    let end, attr
    // Process the attributes in the start tag and put them into match In attrs array
    while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) {
      attr.start = index
      advance(attr[0].length)
      attr.end = index
      match.attrs.push(attr)
    }
    // End of start tag, end = > 'or end =' / > '
    if (end) {
      match.unarySlash = end[1]
      advance(end[0].length)
      match.end = index
      return match
    }
  }
}

handleStartTag

/src/compiler/parser/html-parser.js

/**
 * Further process the parsing result of the start tag -- the match object
 *  Process attribute match Attrs, if it is not a self closing label, put the label information into the stack array, and pop it up when it is processed to its closed label in the future, indicating that the label has been processed, and all the information of the label is on the element ast object
 *  Next, call options The start method processes the tag and generates an element ast according to the tag information,
 *  And process the attributes and instructions on the start tag, and finally put the element ast into the stack array
 * 
 * @param {*} match { tagName: 'div', attrs: [[xx], ...], start: index }
 */
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)
    }
  }

  // Unary tags, such as < HR / >
  const unary = isUnaryTag(tagName) || !!unarySlash

  // Process match Attrs, get attrs = [{Name: attrname, value: attrval, start: XX, end: XX},...]
  // For example, attrs = [{Name: 'ID', value: 'app', start: XX, end: XX},...]
  const l = match.attrs.length
  const attrs = new Array(l)
  for (let i = 0; i < l; i++) {
    const args = match.attrs[i]
    // For example: args [3] = > 'ID', args [4] = > '=', args [5] = > 'app'
    const value = args[3] || args[4] || args[5] || ''
    const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href'
      ? options.shouldDecodeNewlinesForHref
      : options.shouldDecodeNewlines
    // attrs[i] = { id: 'app' }
    attrs[i] = {
      name: args[1],
      value: decodeAttr(value, shouldDecodeNewlines)
    }
    // Non production environment, start and end indexes of record attributes
    if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
      attrs[i].start = args.start + args[0].match(/^\s*/).length
      attrs[i].end = args.end
    }
  }

  // If it is not a self closing label, put the label information into the stack array and pop it up when its closed label is processed in the future
  // If it is a self closing label, the label information does not need to enter the stack. Directly process many attributes and set them to the element ast object, so there is no step to process the end label. This step is carried out in the process of processing the start label
  if (!unary) {
    // Put the tag information into the stack array, {tag, lowerCasedTag, attrs, start, end}
    stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end })
    // The end tag that identifies the current tag is tagName
    lastTag = tagName
  }

  /**
   * Calling the start method mainly does the following six things:
   *   1,Create AST object
   *   2,Handle the input tag with v-model instruction, and handle the cases where the input is checkbox, radio and others respectively
   *   3,Handle many instructions on labels, such as v-pre, v-for, v-if and v-once
   *   4,If the root node does not exist, set the current element as the root node
   *   5,If the current element is a non self closing label, it will push itself into the stack array and record the currentParent, which will be used to tell the child element who its parent node is when processing the child element next
   *   6,If the current element is a self closing label, it means that the label is finished to be processed, so that it can have a relationship with the parent element and set its own child element
   */
  if (options.start) {
    options.start(tagName, attrs, unary, match.start, match.end)
  }
}

parseEndTag

/src/compiler/parser/html-parser.js

/**
 * Parse end tag, for example: < / div >
 * The most important thing is:
 *   1,The stack array is processed, and the start tag of the current end tag is found from the stack array, and then the options. is called. End method
 *   2,After processing the tag, adjust the stack array to ensure that the last element in the stack array is the starting tag corresponding to the next tag.
 *   3,Handle some exceptions, such as the last element of the stack array is not the start tag corresponding to the current end tag, or
 *      br And p labels are handled separately
 * @param {*} tagName Tag name, such as div
 * @param {*} start Start index of end tag
 * @param {*} end End index of end tag
 */
function parseEndTag(tagName, start, end) {
  let pos, lowerCasedTagName
  if (start == null) start = index
  if (end == null) end = index

  // Traverse the stack array in reverse order and find the first tag that is the same as the current end tag. This tag is the description object of the start tag corresponding to the end tag
  // Theoretically, there is no exception. The last element in the stack array is the description object of the start tag of the current end tag
  // Find the closest opened tag of the same type
  if (tagName) {
    lowerCasedTagName = tagName.toLowerCase()
    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 the same tag name is not found in the stack, pos will be < 0 and the subsequent else branch will be carried out

  if (pos >= 0) {
    // This for loop is responsible for closing all tags with index > = POS in the stack array
    // Why use a loop? As mentioned above, under normal circumstances, the last element of the stack array is the start tag we are looking for,
    // However, there are some exceptions, that is, some elements do not provide an end tag, such as:
    // Stack = ['span ','div','span ','h1'], end tag of current processing tagName = div
    // If div and pos = 1 are matched, the two tags (span and h1) with indexes 2 and 3 do not provide the end tag
    // The for loop is responsible for closing the div, span and h1 tags,
    // And in the development environment, give a "prompt that the end tag does not match" for span and h1 tags
    // 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.`,
          { start: stack[i].start, end: stack[i].end }
        )
      }
      if (options.end) {
        // Go here to explain that the above exceptions have been handled, and call options End handles the normal end tag
        options.end(stack[i].tag, start, end)
      }
    }

    // Remove the tags just processed from the array to ensure that the last element of the array is the start tag corresponding to the next end tag
    // Remove the open elements from the stack
    stack.length = pos
    // lastTag records the last unprocessed start tag in the stack array
    lastTag = pos && stack[pos - 1].tag
  } else if (lowerCasedTagName === 'br') {
    // The currently processed tags are < br / > tags
    if (options.start) {
      options.start(tagName, [], true, start, end)
    }
  } else if (lowerCasedTagName === 'p') {
    // p tag
    if (options.start) {
      // Process < p > tags
      options.start(tagName, [], false, start, end)
    }
    if (options.end) {
      // Process < / P > tags
      options.end(tagName, start, end)
    }
  }
}

parseHtmlOptions

src/compiler/parser/index.js

Defines how to handle start tags, end tags, text nodes, and comment nodes.

start

/**
 * We have mainly done the following six things:
 *   1,Create AST object
 *   2,Handle the input tag with v-model instruction, and handle the cases where the input is checkbox, radio and others respectively
 *   3,Handle many instructions on labels, such as v-pre, v-for, v-if and v-once
 *   4,If the root node does not exist, set the current element as the root node
 *   5,If the current element is a non self closing label, it will push itself into the stack array and record the currentParent, which will be used to tell the child element who its parent node is when processing the child element next
 *   6,If the current element is a self closing label, it means that the label is finished to be processed, so that it can have a relationship with the parent element and set its own child element
 * @param {*} tag Tag name
 * @param {*} attrs [{ name: attrName, value: attrVal, start, end }, ...] Attribute array in form
 * @param {*} unary Self closing label
 * @param {*} start Start index of tag in html string
 * @param {*} end End index of tag in html string
 */
function start(tag, attrs, unary, start, end) {
  // Check the namespace, and if it exists, inherit the parent namespace
  // 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)
  }

  // Creates an AST object for the current label
  let element: ASTElement = createASTElement(tag, attrs, currentParent)
  // Set namespace
  if (ns) {
    element.ns = ns
  }

  // This paragraph will go in the non production environment. Add some attributes on the ast object, such as start and end
  if (process.env.NODE_ENV !== 'production') {
    if (options.outputSourceRange) {
      element.start = start
      element.end = end
      // Parse the attribute array into {attrname: {Name: attrname, value: attrval, start, end},...} Formal object
      element.rawAttrsMap = element.attrsList.reduce((cumulated, attr) => {
        cumulated[attr.name] = attr
        return cumulated
      }, {})
    }
    // Verify whether the attribute is valid. For example, the attribute name cannot contain: spaces, quotes, <, >, / or =
    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
          }
        )
      }
    })
  }

  // In the case of non server rendering, style and script labels should not appear in the template
  if (isForbiddenTag(element) && !isServerRendering()) {
    element.forbidden = true
    process.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 }
    )
  }

  /**
   * Execute the preTransforms methods in the class, style and model modules respectively for the element object
   * However, only the model module of the web platform has the preTransforms method
   * Used to handle the input tag with v-model, but not the v-model attribute
   * Handle the cases where the input is checkbox, radio and others respectively
   * input The specific situation is determined by el Conditions in ifconditions
   * <input v-mode="test" :type="checkbox or radio or other(Text) "/ >
   */
  // apply pre-transforms
  for (let i = 0; i < preTransforms.length; i++) {
    element = preTransforms[i](element, options) || element
  }

  if (!inVPre) {
    // Indicates whether there is a v-pre instruction for element. If yes, set element pre = true
    processPre(element)
    if (element.pre) {
      // If there is a v-pre instruction, set inVPre to true
      inVPre = true
    }
  }
  // If the pre tag, set inPre to true
  if (platformIsPreTag(element.tag)) {
    inPre = true
  }

  if (inVPre) {
    // It indicates that there is a v-pre instruction on the label. Such a node will only be rendered once. Set the attributes on the node to El In attrs array object, as a static attribute, this part will not be rendered during data update
    // Set el Attrs array object. Each element is an attribute object {name: attrName, value: attrVal, start, end}
    processRawAttrs(element)
  } else if (!element.processed) {
    // structural directives
    // Process the v-for attribute to get element For = iteratable object element Alias = alias
    processFor(element)
    /**
     * Handle v-if, v-else-if, v-else
     * Get element if = "exp",element.elseif = exp, element.else = true
     * v-if Attribute will be added in element Add {exp, block} object to ifconditions array
     */
    processIf(element)
    // Process the v-once instruction to get element once = true 
    processOnce(element)
  }

  // If the root does not exist, it means that the currently processed element is the first element, that is, the root element of the component
  if (!root) {
    root = element
    if (process.env.NODE_ENV !== 'production') {
      // Check the root element. There are some restrictions on the root element. For example, slot and template cannot be used as the root element, and v-for instructions cannot be used on the root element of stateful components
      checkRootConstraints(root)
    }
  }

  if (!unary) {
    // The non self closing tag records the current element through currentParent. When the next element is processed, it will know who its parent element is
    currentParent = element
    // Then push the element into the stack array and take it out when processing the closed label of the current element in the future
    // push the ast object of the current tag into the stack array. Note that when calling options Start method
    // There was also a push operation before, which brought in a basic configuration information of the current tag
    stack.push(element)
  } else {
    /**
     * Note that the current element is a self closing label, which mainly does three things:
     *   1,If the element has not been processed, that is, El If processed is false, the processElement method is called to process many attributes on the node
     *   2,Let yourself have a relationship with the parent element, put yourself in the children array of the parent element, and set your parent attribute to currentParent
     *   3,Set your own child elements and put all your non slot child elements into your own children array
     */
    closeElement(element)
  }
}

end

/**
 * Processing end tag
 * @param {*} tag The name of the end tag
 * @param {*} start Start index of end tag
 * @param {*} end End index of end tag
 */
function end(tag, start, end) {
  // The ast object of the start tag corresponding to the end tag
  const element = stack[stack.length - 1]
  // pop stack
  stack.length -= 1
  // It's a little difficult to understand here, because the previous element may be the sibling node of the current element
  currentParent = stack[stack.length - 1]
  if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
    element.end = end
  }
  /**
   * Three main things have been done:
   *   1,If the element has not been processed, that is, El If processed is false, the processElement method is called to process many attributes on the node
   *   2,Let yourself have a relationship with the parent element, put yourself in the children array of the parent element, and set your parent attribute to currentParent
   *   3,Set your own child elements and put all your non slot child elements into your own children array
   */
  closeElement(element)
}

chars

/**
 * Process the text, generate an ast object based on the text, and then put the ast into its parent element, currentparent In the children array 
 */
function chars(text: string, start: number, end: number) {
  // Exception handling: the absence of currentParent indicates that this text has no parent element
  if (!currentParent) {
    if (process.env.NODE_ENV !== 'production') {
      if (text === template) { // Text cannot be the root element of a component
        warnOnce(
          'Component template requires a root element, rather than just text.',
          { start }
        )
      } else if ((text = text.trim())) { // Text placed outside the root element is ignored
        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
  }
  // All child nodes of the current parent element
  const children = currentParent.children
  // Carry out a series of processing on text, such as deleting white space characters, or if there is whitespace options option, text will be directly set to null or space
  if (inPre || text.trim()) {
    // Text in pre tag or text Trim() is not empty
    text = isTextTag(currentParent) ? text : decodeHTMLCached(text)
  } else if (!children.length) {
    // The description text is not in the pre tag and text Trim() is empty, and the current parent element has no child node,
    // Set text to empty
    // remove the whitespace-only node right after an opening tag
    text = ''
  } else if (whitespaceOption) {
    // Compression processing
    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 still exists after processing
  if (text) {
    if (!inPre && whitespaceOption === 'condense') {
      // If it is not in the pre node and there is a compression option in the configuration option, multiple consecutive spaces are compressed into a single space
      // condense consecutive whitespaces into single space
      text = text.replace(whitespaceRE, ' ')
    }
    let res
    // Generate AST object based on text
    let child: ?ASTNode
    if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
      // There is an expression in the text (i.e. there is a delimiter)
      child = {
        type: 2,
        // expression
        expression: res.expression,
        tokens: res.tokens,
        // text
        text
      }
    } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
      // Plain text node
      child = {
        type: 3,
        text
      }
    }
    // If the child exists, put the child into the belly of the parent element, that is, currentparent In the children array
    if (child) {
      if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
        child.start = start
        child.end = end
      }
      children.push(child)
    }
  }
},

comment

/**
 * Processing annotation nodes
 */
function comment(text: string, start, end) {
  // adding anything as a sibling to the root node is forbidden
  // comments should still be allowed, but ignored
  // It is forbidden to add any content as the peer of the root node. Comments should be allowed, but will be ignored
  // If the currentParent does not exist, it means that the comment and root are at the same level and are ignored
  if (currentParent) {
    // ast of annotation node
    const child: ASTText = {
      // Node type
      type: 3,
      // Note Content 
      text,
      // Is it a comment
      isComment: true
    }
    if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
      // Record the start index and end index of the node
      child.start = start
      child.end = end
    }
    // Place the current annotation node in the children attribute of the parent element
    currentParent.children.push(child)
  }
}

createASTElement

/src/compiler/parser/index.js

/**
 * Creates an AST object for the specified element
 * @param {*} tag Tag name
 * @param {*} attrs Attribute array, [{name: attrName, value: attrVal, start, end},...]
 * @param {*} parent Parent element
 * @returns { type: 1, tag, attrsList, attrsMap: makeAttrsMap(attrs), rawAttrsMap: {}, parent, children: []}
 */
export function createASTElement(
  tag: string,
  attrs: Array<ASTAttr>,
  parent: ASTElement | void
): ASTElement {
  return {
    // Node type
    type: 1,
    // Tag name
    tag,
    // Attribute array of tag
    attrsList: attrs,
    // Attribute object of tag {attrName: attrVal,...}
    attrsMap: makeAttrsMap(attrs),
    // Original attribute object
    rawAttrsMap: {},
    // Parent node
    parent,
    // Child node
    children: []
  }
}

preTransformNode

/src/platforms/web/compiler/modules/model.js

/**
 * The input tag with v-model is processed, but the v-model attribute is not processed
 * Handle the cases where the input is checkbox, radio and others respectively
 * input The specific situation is determined by el Conditions in ifconditions
 * <input v-mode="test" :type="checkbox or radio or other(Text) "/ >
 * @param {*} el 
 * @param {*} options 
 * @returns branch0
 */
function preTransformNode (el: ASTElement, options: CompilerOptions) {
  if (el.tag === 'input') {
    const map = el.attrsMap
    // No v-model attribute, end directly
    if (!map['v-model']) {
      return
    }

    // Get the value of: type
    let typeBinding
    if (map[':type'] || map['v-bind:type']) {
      typeBinding = getBindingAttr(el, 'type')
    }
    if (!map.type && !typeBinding && map['v-bind']) {
      typeBinding = `(${map['v-bind']}).type`
    }

    // If the type attribute exists
    if (typeBinding) {
      // Get the value of v-if, for example: < input V-model = "test": type = "checkbox" v-if = "test" / >
      const ifCondition = getAndRemoveAttr(el, 'v-if', true)
      // &&test
      const ifConditionExtra = ifCondition ? `&&(${ifCondition})` : ``
      // Whether there is a v-else attribute, < input v-else / >
      const hasElse = getAndRemoveAttr(el, 'v-else', true) != null
      // Get the value of v-else-if attribute < input v-else-if = "test" / >
      const elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true)
      // Clone a new el object and handle the input as chekbox, radio or other situations respectively
      // In which case, through El Ifconditions condition
      // 1. checkbox
      const branch0 = cloneASTElement(el)
      // process for on the main node
      // <input v-for="item in arr" :key="item" />
      // Process the v-for expression to get branch0 for = arr, branch0. alias = item
      processFor(branch0)
      // At branch 0 Attrsmap and branch0 Add the type attribute to the attrslist object
      addRawAttr(branch0, 'type', 'checkbox')
      // Handle the key, ref, slot, closed slot tag, dynamic component, class, style, v-bind, v-on, other instructions and some native attributes of the element node respectively 
      processElement(branch0, options)
      // Mark that the current object has been processed
      branch0.processed = true // prevent it from double-processed
      // Get true & & test or false & & test, and mark whether the current input is a checkbox
      branch0.if = `(${typeBinding})==='checkbox'` + ifConditionExtra
      // At branch 0 Put {exp, block} objects into ifconfigurations array
      addIfCondition(branch0, {
        exp: branch0.if,
        block: branch0
      })
      // Clone a new ast object
      // 2. add radio else-if condition
      const branch1 = cloneASTElement(el)
      // Get v-for attribute value
      getAndRemoveAttr(branch1, 'v-for', true)
      // In branch1 Attrsmap and branch1 Add the type attribute to the attrslist object
      addRawAttr(branch1, 'type', 'radio')
      // Handle the key, ref, slot, closed slot tag, dynamic component, class, style, v-bind, v-on, other instructions and some native attributes of the element node respectively 
      processElement(branch1, options)
      // At branch 0 Put {exp, block} objects into ifconfigurations array
      addIfCondition(branch0, {
        // Mark whether the current input is radio
        exp: `(${typeBinding})==='radio'` + ifConditionExtra,
        block: branch1
      })
      // 3. other, input is other
      const branch2 = cloneASTElement(el)
      getAndRemoveAttr(branch2, 'v-for', true)
      addRawAttr(branch2, ':type', typeBinding)
      processElement(branch2, options)
      addIfCondition(branch0, {
        exp: ifCondition,
        block: branch2
      })

      // Set else or else if condition for branch0
      if (hasElse) {
        branch0.else = true
      } else if (elseIfCondition) {
        branch0.elseif = elseIfCondition
      }

      return branch0
    }
  }
}

getBindingAttr

/src/compiler/helpers.js

/**
 * Gets the value of the execution property name on the el object 
 */
export function getBindingAttr (
  el: ASTElement,
  name: string,
  getStatic?: boolean
): ?string {
  // Gets the value of the specified property
  const dynamicValue =
    getAndRemoveAttr(el, ':' + name) ||
    getAndRemoveAttr(el, 'v-bind:' + name)
  if (dynamicValue != null) {
    return parseFilters(dynamicValue)
  } else if (getStatic !== false) {
    const staticValue = getAndRemoveAttr(el, name)
    if (staticValue != null) {
      return JSON.stringify(staticValue)
    }
  }
}

getAndRemoveAttr

/src/compiler/helpers.js

/**
 * From El Delete the specified attribute name from attrslist
 * If removeFromMap is true, then el. Is also deleted The attribute in the attrsmap object,
 *   For example, v-if, v-else-if, v-else and other attributes will be removed,
 *   However, the properties on this object are not normally deleted because it is also needed during code generation from ast
 * Returns the value of the specified property
 */
// note: this only removes the attr from the Array (attrsList) so that it
// doesn't get processed by processAttrs.
// By default it does NOT remove it from the map (attrsMap) because the map is
// needed during codegen.
export function getAndRemoveAttr (
  el: ASTElement,
  name: string,
  removeFromMap?: boolean
): ?string {
  let val
  // Remove the execution attribute name from El Remove from attrslist
  if ((val = el.attrsMap[name]) != null) {
    const list = el.attrsList
    for (let i = 0, l = list.length; i < l; i++) {
      if (list[i].name === name) {
        list.splice(i, 1)
        break
      }
    }
  }
  // If removeFromMap is true, from El Remove the specified attribute name from attrsmap
  // However, El is generally not removed Data in attsmap, because this object is also needed during code generation from ast
  if (removeFromMap) {
    delete el.attrsMap[name]
  }
  // Returns the value of the execution property
  return val
}

processFor

/src/compiler/parser/index.js

/**
 * Process v-for, set the result to el object, and get:
 *   el.for = Iteratable objects, such as arr
 *   el.alias = Alias, such as item
 * @param {*} el ast object of element
 */
export function processFor(el: ASTElement) {
  let exp
  // Gets the value of the v-for attribute on el
  if ((exp = getAndRemoveAttr(el, 'v-for'))) {
    // Parse the expression of v-for to get {for: iteratable object, alias: alias}, such as {for: arr, alias: item}
    const res = parseFor(exp)
    if (res) {
      // Copy the attributes on the res object to the el object
      extend(el, res)
    } else if (process.env.NODE_ENV !== 'production') {
      warn(
        `Invalid v-for expression: ${exp}`,
        el.rawAttrsMap['v-for']
      )
    }
  }
}

addRawAttr

/src/compiler/helpers.js

// In El Attrsmap and el Add the specified attribute name to attrslist
// add a raw attr (use this in preTransforms)
export function addRawAttr (el: ASTElement, name: string, value: any, range?: Range) {
  el.attrsMap[name] = value
  el.attrsList.push(rangeSetItem({ name, value }, range))
}

processElement

/src/compiler/parser/index.js

/**
 * Handle the key, ref, slot, closed slot tag, dynamic component, class, style, v-bind, v-on, other instructions and some native attributes of the element node respectively 
 * Then add the following attributes on the el object:
 * el.key,ref,refInFor,scopedSlot,slotName,component,inlineTemplate,staticClass
 * el.bindingClass,staticStyle,bindingStyle,attrs
 * @param {*} element ast object of the element being processed
 * @param {*} options Configuration item
 * @returns 
 */
export function processElement(
  element: ASTElement,
  options: CompilerOptions
) {
  // el.key = val
  processKey(element)

  // Determines whether element is a normal element
  // determine whether this is a plain element after
  // removing structural attributes
  element.plain = (
    !element.key &&
    !element.scopedSlots &&
    !element.attrsList.length
  )

  // el.ref = val, el.refInFor = boolean
  processRef(element)
  // Process the content passed to the component as a slot, and get the slot name, whether it is a dynamic slot, the value of the scope slot, and all child elements in the slot. The child elements are placed in the children attribute of the slot object
  processSlotContent(element)
  // Process the self closing slot tag and get the slot name = > el slotName = xx
  processSlotOutlet(element)
  // When processing dynamic components, < component: is = "componame" > < / component > gets el component = compName,
  // And mark whether there is an inline template, El inlineTemplate = true of false
  processComponent(element)
  // Execute the transformNode methods in the class, style and model modules respectively for the element object
  // However, only the class and style modules in the web platform have the transformNode method, which is used to process the class attribute and style attribute respectively
  // Get el staticStyle, el.styleBinding,el.staticClass,el.classBinding
  // Store the value of static style attribute, dynamic style attribute, static class attribute and dynamic class attribute respectively
  for (let i = 0; i < transforms.length; i++) {
    element = transforms[i](element, options) || element
  }
  /**
   * Process all attributes on the element:
   * v-bind The command becomes: El Attrs or EL dynamicAttrs = [{ name, value, start, end, dynamic }, ...],
   *                Or the attribute of props must be used, which becomes el props = [{ name, value, start, end, dynamic }, ...]
   * v-on The command becomes: El Events or EL nativeEvents = { name: [{ value, start, end, modifiers, dynamic }, ...] }
   * Other instructions: El directives = [{name, rawName, value, arg, isDynamicArg, modifier, start, end }, ...]
   * Native attribute: El Attrs = [{name, value, start, end}], or some attributes that must use props, become:
   *         el.props = [{ name, value: true, start, end, dynamic }]
   */
  processAttrs(element)
  return element
}

processKey

/src/compiler/parser/index.js

/**
 * Process the key attribute on the element and set el key = val
 * @param {*} el 
 */
function processKey(el) {
  // Get the attribute value of the key
  const exp = getBindingAttr(el, 'key')
  if (exp) {
    // Exception handling on key usage
    if (process.env.NODE_ENV !== 'production') {
      // template tag does not allow setting key
      if (el.tag === 'template') {
        warn(
          `<template> cannot be keyed. Place the key on real elements instead.`,
          getRawBindingAttr(el, 'key')
        )
      }
      // Do not use the index of v-for as the key on the sub element of < transition = group >, which is no different from using no key
      if (el.for) {
        const iterator = el.iterator2 || el.iterator1
        const parent = el.parent
        if (iterator && iterator === exp && parent && parent.tag === 'transition-group') {
          warn(
            `Do not use v-for index as key on <transition-group> children, ` +
            `this is the same as not using keys.`,
            getRawBindingAttr(el, 'key'),
            true /* tip */
          )
        }
      }
    }
    // Set el key = exp
    el.key = exp
  }
}

processRef

/src/compiler/parser/index.js

/**
 * Handle ref attribute on element
 *  el.ref = refVal
 *  el.refInFor = boolean
 * @param {*} el 
 */
function processRef(el) {
  const ref = getBindingAttr(el, 'ref')
  if (ref) {
    el.ref = ref
    // Determine whether the element containing the ref attribute is included in the element with the v-for instruction or in the descendant element
    // If so, ref points to an array containing DOM nodes or component instances
    el.refInFor = checkInFor(el)
  }
}

processSlotContent

/src/compiler/parser/index.js

/**
 * Process the content passed to the component as a slot and get:
 *  slotTarget => Slot name
 *  slotTargetDynamic => Is it a dynamic slot
 *  slotScope => Value of the scope slot
 *  When using v-slot syntax directly on the < comp > tag, put the above attributes in el Scopedslots object, and other cases are directly placed on el object
 * handle content being passed to a component as slot,
 * e.g. <template slot="xxx">, <div slot-scope="xxx">
 */
function processSlotContent(el) {
  let slotScope
  if (el.tag === 'template') {
    // Tips for using the scope attribute on the template tag
    // Scope has been deprecated and replaced with slot scope after 2.5
    // Slot scope can be used on template tags or ordinary tags
    slotScope = getAndRemoveAttr(el, 'scope')
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && slotScope) {
      warn(
        `the "scope" attribute for scoped slots have been deprecated and ` +
        `replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` +
        `can also be used on plain elements in addition to <template> to ` +
        `denote scoped slots.`,
        el.rawAttrsMap['scope'],
        true
      )
    }
    // el.slotScope = val
    el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')
  } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {
      // Element cannot use slot scope and v-for at the same time. v-for has higher priority
      // You should use the template tag as the container and put the slot scope on the template tag 
      warn(
        `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +
        `(v-for takes higher priority). Use a wrapper <template> for the ` +
        `scoped slot to make it clearer.`,
        el.rawAttrsMap['slot-scope'],
        true
      )
    }
    el.slotScope = val
    el.slotScope = slotScope
  }

  // Gets the value of the slot property
  // slot="xxx", the writing method of the old named slot
  const slotTarget = getBindingAttr(el, 'slot')
  if (slotTarget) {
    // el.slotTarget = slot name (named slot)
    el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
    // Dynamic slot name
    el.slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot'])
    // preserve slot as an attribute for native shadow DOM compat
    // only for non-scoped slots.
    if (el.tag !== 'template' && !el.slotScope) {
      addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'))
    }
  }

  // 2.6 v-slot syntax
  if (process.env.NEW_SLOT_SYNTAX) {
    if (el.tag === 'template') {
      // v-slot on the tempalt tag, get the value of v-slot
      // v-slot on <template>
      const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
      if (slotBinding) {
        // Exception prompt
        if (process.env.NODE_ENV !== 'production') {
          if (el.slotTarget || el.slotScope) {
            // Mixed use of different slot syntax is prohibited
            warn(
              `Unexpected mixed usage of different slot syntaxes.`,
              el
            )
          }
          if (el.parent && !maybeComponent(el.parent)) {
            // < template V-slot > can only appear at the root of the component, for example:
            // <comp>
            //   <template v-slot>xx</template>
            // </comp>
            // It can't be
            // <comp>
            //   <div>
            //     <template v-slot>xxx</template>
            //   </div>
            // </comp>
            warn(
              `<template v-slot> can only appear at the root level inside ` +
              `the receiving component`,
              el
            )
          }
        }
        // Get slot name
        const { name, dynamic } = getSlotName(slotBinding)
        // Slot name
        el.slotTarget = name
        // Is it a dynamic slot
        el.slotTargetDynamic = dynamic
        // Value of the scope slot
        el.slotScope = slotBinding.value || emptySlotScopeToken // force it into a scoped slot for perf
      }
    } else {
      // Process V-slots on components, < comp v-slot: header / >
      // slotBinding = { name: "v-slot:header", value: "", start, end}
      // v-slot on component, denotes default slot
      const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
      if (slotBinding) {
        // Exception prompt
        if (process.env.NODE_ENV !== 'production') {
          // If el is not a component, prompt that v-slot can only appear on the component or template label
          if (!maybeComponent(el)) {
            warn(
              `v-slot can only be used on components or <template>.`,
              slotBinding
            )
          }
          // Grammatical mixing
          if (el.slotScope || el.slotTarget) {
            warn(
              `Unexpected mixed usage of different slot syntaxes.`,
              el
            )
          }
          // To avoid Scope Ambiguity, when there are other named slots, the default slot should also use the < template > syntax
          if (el.scopedSlots) {
            warn(
              `To avoid scope ambiguity, the default slot should also use ` +
              `<template> syntax when there are other named slots.`,
              slotBinding
            )
          }
        }
        // Add the child of the component to its default slot
        // add the component's children to its default slot
        const slots = el.scopedSlots || (el.scopedSlots = {})
        // Gets the slot name and whether it is a dynamic slot
        const { name, dynamic } = getSlotName(slotBinding)
        // Create an ast object with a template tag to hold the slot contents. The parent is el
        const slotContainer = slots[name] = createASTElement('template', [], el)
        // Slot name
        slotContainer.slotTarget = name
        // Is it a dynamic slot
        slotContainer.slotTargetDynamic = dynamic
        // For all children, set the parent attribute of each child to slotContainer
        slotContainer.children = el.children.filter((c: any) => {
          if (!c.slotScope) {
            // Set the parent attribute of the element in the slot to slotContainer, that is, the template element
            c.parent = slotContainer
            return true
          }
        })
        slotContainer.slotScope = slotBinding.value || emptySlotScopeToken
        // remove children as they are returned from scopedSlots now
        el.children = []
        // mark el non-plain so data gets generated
        el.plain = false
      }
    }
  }
}

getSlotName

/src/compiler/parser/index.js

/**
 * Resolve the binding to get the slot name and whether it is a dynamic slot
 * @returns { name: Slot name, dynamic: whether it is a dynamic slot}
 */
function getSlotName(binding) {
  let name = binding.name.replace(slotRE, '')
  if (!name) {
    if (binding.name[0] !== '#') {
      name = 'default'
    } else if (process.env.NODE_ENV !== 'production') {
      warn(
        `v-slot shorthand syntax requires a slot name.`,
        binding
      )
    }
  }
  return dynamicArgRE.test(name)
    // dynamic [name]
    ? { name: name.slice(1, -1), dynamic: true }
    // static name
    : { name: `"${name}"`, dynamic: false }
}

processSlotOutlet

/src/compiler/parser/index.js

// Handle < slot / > outlets to handle closed slot Tags
// Get the slot name, El slotName
function processSlotOutlet(el) {
  if (el.tag === 'slot') {
    // Get slot name
    el.slotName = getBindingAttr(el, 'name')
    // Tip: do not use the key attribute on the slot tag
    if (process.env.NODE_ENV !== 'production' && el.key) {
      warn(
        `\`key\` does not work on <slot> because slots are abstract outlets ` +
        `and can possibly expand into multiple elements. ` +
        `Use the key on a wrapping element instead.`,
        getRawBindingAttr(el, 'key')
      )
    }
  }
}

processComponent

/src/compiler/parser/index.js

/**
 * Processing dynamic components, < component: is = "componame" > < / component >
 * Get el component = compName
 */
function processComponent(el) {
  let binding
  // Resolve the is attribute to get the attribute value, that is, the component name, El component = compName
  if ((binding = getBindingAttr(el, 'is'))) {
    el.component = binding
  }
  // <component :is="compName" inline-template>xx</component>
  // The inline template attribute exists on the component and is marked: El inlineTemplate = true
  // Indicates that the contents in the start and end labels of components appear as component templates, rather than distributed as slots, so as to facilitate the definition of component templates
  if (getAndRemoveAttr(el, 'inline-template') != null) {
    el.inlineTemplate = true
  }
}

transformNode

/src/platforms/web/compiler/modules/class.js

/**
 * Handling class attributes on elements
 * The static class attribute value is assigned to El Staticclass attribute
 * The dynamic class attribute value is assigned to El Classbinding property
 */
function transformNode (el: ASTElement, options: CompilerOptions) {
  // journal
  const warn = options.warn || baseWarn
  // Get the value xx of the static class attribute on the element, < div class = "xx" > < / div >
  const staticClass = getAndRemoveAttr(el, 'class')
  if (process.env.NODE_ENV !== 'production' && staticClass) {
    const res = parseText(staticClass, options.delimiters)
    // Tips, like the tips of style, you can't use < div class = "{Val}" > < / div >, please use
    // < div: class = "Val" > < / div > replace
    if (res) {
      warn(
        `class="${staticClass}": ` +
        'Interpolation inside attributes has been removed. ' +
        'Use v-bind or the colon shorthand instead. For example, ' +
        'instead of <div class="{{ val }}">, use <div :class="val">.',
        el.rawAttrsMap['class']
      )
    }
  }
  // The static class attribute value is assigned to El staticClass
  if (staticClass) {
    el.staticClass = JSON.stringify(staticClass)
  }
  // Get the class attribute value of dynamic binding and assign it to El classBinding
  const classBinding = getBindingAttr(el, 'class', false /* getStatic */)
  if (classBinding) {
    el.classBinding = classBinding
  }
}

transformNode

/src/platforms/web/compiler/modules/style.js

/**
 * Resolve the static style attribute and the dynamically bound style attribute from el and assign them to:
 * el.staticStyle And el styleBinding
 * @param {*} el 
 * @param {*} options 
 */
function transformNode(el: ASTElement, options: CompilerOptions) {
  // journal
  const warn = options.warn || baseWarn
  // <div style="xx"></div>
  // Get style attribute
  const staticStyle = getAndRemoveAttr(el, 'style')
  if (staticStyle) {
    // Prompt: if the delimiter is parsed from xx, it indicates a dynamic style,
    // For example, < div style = "{Val}}" > < / div > gives a prompt:
    // For dynamic style, please use < div: style = "Val" > < / div >
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production') {
      const res = parseText(staticStyle, options.delimiters)
      if (res) {
        warn(
          `style="${staticStyle}": ` +
          'Interpolation inside attributes has been removed. ' +
          'Use v-bind or the colon shorthand instead. For example, ' +
          'instead of <div style="{{ val }}">, use <div :style="val">.',
          el.rawAttrsMap['style']
        )
      }
    }
    // Assign static style to El staticStyle
    el.staticStyle = JSON.stringify(parseStyleText(staticStyle))
  }

  // Get the style attribute of dynamic binding, such as < div: style = "{Val}}" > < / div >
  const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)
  if (styleBinding) {
    // Assigned to El styleBinding
    el.styleBinding = styleBinding
  }
}

link

Thank you for your attention, likes, collections and comments. See you next time.

When learning becomes a habit, knowledge becomes common sense. Thank you for your attention, likes, collections and comments.

The new video and articles will be sent to WeChat official account for the first time. Li Yongning

The article has been included in github warehouse liyongning/blog , welcome to Watch and Star.

Topics: Javascript Front-end Vue.js source code