As mentioned in the earlier section, we provided two versions for us to use when creating projects using VUE-CLI, the Runtime Only version and the Runtime + Compiler version. The Runtime Only version does not include a compiler, which compiles the template into the render function, also known as precompilation, when the project is packaged. The Runtime + Compiler version includes a Compiler that allows the compilation process to be done at Runtime.

The entrance

This piece of code is more, mainly for various cases to do some boundary processing. Focus only on the main flow here. Those who are interested in details can do their own research. Generally, the Runtime + Compiler version may be more common.

// src/platforms/web/entry-runtime-with-compiler.js
Vue.prototype.$mount = function (el? :string| Element, hydrating? :boolean
) :Component {
  // ...
  if(! options.render) {// The template is ready to compile
    if (template) {
      // Compile the template to get the dynamic rendering function and static rendering function
      const { render, staticRenderFns } = compileToFunctions(template, {
        // In non-production environments, compile-time records the index of the starting and ending positions of the tag attributes in the template string
        outputSourceRange: process.env.NODE_ENV ! = ='production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        // Delimiter, default {{}}
        delimiters: options.delimiters,
        // Whether to keep comments
        comments: options.comments
      }, this)}}}Copy the code

The methods to compileToFunctions compile template to render and staticRenderFns.

compileToFunctions

// src/platforms/web/compiler/index.js
import { baseOptions } from './options'
import { createCompiler } from 'compiler/index'

const { compile, compileToFunctions } = createCompiler(baseOptions)

export { compile, compileToFunctions }
Copy the code

createCompiler

// src/compiler/index.js
export const createCompiler = createCompilerCreator(function baseCompile (
  template: string,
  options: CompilerOptions
) :CompiledResult {
  // Parse the template into an AST
  const ast = parse(template.trim(), options)
  // Optimize AST, static markup
  if(options.optimize ! = =false) {
    optimize(ast, options)
  }
  // Generate a render function, which converts the AST into a string of executable render functions
  // code = {
  // render: `with(this){return ${_c(tag, data, children, normalizationType)}}`,
  // staticRenderFns: [_c(tag, data, children, normalizationType), ...]
  // }
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})
Copy the code

CreateCompiler is returned by calling createCompilerCreator, where a baseCompile function is passed in as an argument. This function is the focus, and it is in this function that the core compilation process is performed.

createCompilerCreator

// src/compiler/create-compiler.js
export function createCompilerCreator (baseCompile: Function) :Function {
  return function createCompiler (baseOptions: CompilerOptions{
    function compile (
      template: string, options? : CompilerOptions) :CompiledResult {
      // Create compilation option objects modeled after platform-specific compilation configurations
      const finalOptions = Object.create(baseOptions)
      const errors = []
      const tips = []
      // Log, which records error and tip
      let warn = (msg, range, tip) = > {
        (tip ? tips : errors).push(msg)
      }
      if (options) {
        if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
          // $flow-disable-line
          const leadingSpaceLength = template.match(/^\s*/) [0].length
          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 configuration options to finalOptions
        // merge custom modules
        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 options
        for (const key in options) {
          if(key ! = ='modules'&& key ! = ='directives') {
            finalOptions[key] = options[key]
          }
        }
      }
      finalOptions.warn = warn
      // The core compiler function passes the template string and the final configuration item to get the compilation result
      const compiled = baseCompile(template.trim(), finalOptions)
      if(process.env.NODE_ENV ! = ='production') {
        detectErrors(compiled.ast, warn)
      }
      // Mount errors and prompts generated during compilation to the compilation result
      compiled.errors = errors
      compiled.tips = tips
      return compiled
    }
    return {
      compile,
      compileToFunctions: createCompileToFunctionFn(compile)
    }
  }
}
Copy the code

CreateCompilerCreator returns a createCompiler function. CreateCompiler returns an object containing compile and compileToFunctions, This corresponds to the compileToFunctions method called in $mount. Within the createCompiler function defines the compile method, and pass it to createCompileToFunctionFn, compile the main purpose is the unique platform configuration items do some consolidation, Such as web platforms and handling errors that occur during compilation.

createCompileToFunctionFn

// src/compiler/to-function.js
export function createCompileToFunctionFn (compile: Function) :Function {
  const cache = Object.create(null)
  return function compileToFunctions (
    template: string, options? : CompilerOptions, vm? : Component) :CompiledFunctionResult {
    // The compiler option passed in
    options = extend({}, options)
    const warn = options.warn || baseWarn
    delete options.warn
    /* istanbul ignore if */
    if(process.env.NODE_ENV ! = ='production') {
      // detect possible CSP restriction
      // Detect possible CSP limits
      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.')}}}// check cache
    // If there is a cache, the result of the last compilation will be skipped and fetched directly from the cache
    const key = options.delimiters
      ? String(options.delimiters) + template
      : template
    if (cache[key]) {
      return cache[key]
    }
    // compile
    // Execute the compile function to get the compile result
    const compiled = compile(template, options)
    // check compilation errors/tips
    // Check errors/tips generated during compilation and print 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))
        }
      }
    }
    // turn code into functions
    // Convert the compiled string code to a Function, implemented by new Function(code)
    const res = {}
    const fnGenErrors = []
    res.render = createFunction(compiled.render, fnGenErrors)
    res.staticRenderFns = compiled.staticRenderFns.map(code= > {
      return createFunction(code, fnGenErrors)
    })
    // check function generation errors.
    // this should only happen if there is a bug in the compiler itself.
    // mostly for codegen development use
    /* istanbul ignore if */
    // Handle the error that occurred during the code conversion above
    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 the compiled results
    return (cache[key] = res)
  }
}
Copy the code

CreateCompileToFunctionFn returned compileToFunctions this is the final definition we are looking for, it is mainly to do so a few things:

  • Execute the compilation function to get the compilation result.
  • Converts the compiled string code into an executable function.
  • Handle exceptions
  • The cache

summary

From the above code, we can see that the actual compilation process is in the baseCompile passed by createCompilerCreator function, which is mainly divided into the following parts:

  • Parse the template into an AST
const ast = parse(template.trim(), options)
Copy the code
  • Optimize AST (static markup)
optimize(ast, options)
Copy the code
  • Generate code string
const code = generate(ast, options)
Copy the code

The reason for doing so much foreplay before actually compiling is to do some processing for different platforms. Let’s look at what we did for these three parts.

parse

Parse is used to parse templates into an AST, an abstract syntax tree. This is a complex process that sets up all of the element’s information on the AST object for each node, such as: parent, child, label information, attribute information, slot information, and so on. A number of regular expressions are used to match the template.

parse

// src/compiler/parser/index.js
export function parse (
  template: string,
  options: CompilerOptions
) :ASTElement | void {
  warn = options.warn || baseWarn
  // Whether the label is pre
  platformIsPreTag = options.isPreTag || no
  // A property that must be bound using props
  platformMustUseProp = options.mustUseProp || no
  // Get the label's namespace
  platformGetTagNamespace = options.getTagNamespace || no
  // Whether to keep the tag (HTML + SVG)
  const isReservedTag = options.isReservedTag || no
  // Whether it is a component
  maybeComponent = (el: ASTElement) = >!!!!! ( el.component || el.attrsMap[':is'] ||
    el.attrsMap['v-bind:is') | |! (el.attrsMap.is ? isReservedTag(el.attrsMap.is) : isReservedTag(el.tag)) )// Get the transformNode, preTransformNode, and postTransformNode methods in the class, model, and style modules of options.modules
  // Handle class, style, v-model on element nodes
  transforms = pluckModuleFunction(options.modules, 'transformNode')
  preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
  postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
  
  // Delimiters such as {{}}
  delimiters = options.delimiters
  
  const stack = []
  // Space options
  constpreserveWhitespace = options.preserveWhitespace ! = =false
  const whitespaceOption = options.whitespace
  // The root node, which takes root as the root, will be mounted to the root node according to the hierarchy, and the last return is root, an AST syntax tree
  let root
  // The parent of the current element
  let currentParent
  let inVPre = false
  let inPre = false
  let warned = false
  function warnOnce (msg, range{
    if(! warned) { warned =true
      warn(msg, range)
    }
  }
  
  // Because the code is quite long, the following methods are separate.
  function closeElement (element/ *... * / }
  
  function trimEndingWhitespace (el/ *... * / }
  
  function checkRootConstraints (el/ *... * / }
  
  parseHTML(template, {
    / *... * /
  })
  return root
}
Copy the code

Parse receives two parameters template and options, and is the template string and configuration options, the options defined in/SRC/platforms/web/compiler options, Different platforms (Web and WEEX) have different configuration options.

closeElement

// src/compiler/parser/index.js
function closeElement (element{
  // Remove the space at the end of the node
  trimEndingWhitespace(element)
  // The current element is not in the pre node and has not been processed
  if(! inVPre && ! element.processed) {// Handle element nodes' keys, refs, slots, self-closing slot tags, dynamic components, class, style, V-bind, V-ON, other directives, and some native attributes separately
    element = processElement(element, options)
  }
  // Handle the v-if, V-else, v-else instructions on the root node
  // If the root node has a V-if instruction, then a node of the same level with v-else or V-else must be provided in case the root element does not exist
  // 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 }
      )
    }
  }
  // Create a parent-child relationship, place yourself in the children array of the parent element, and set your parent property to currentParent
  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
    }
  }
  // Set all of your non-slot children into the Element. children array
  // 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
  }
  // Execute the postTransform methods of model, class, and style modules for element
  // But the Web platform does not provide this method
  // apply post-transforms
  for (let i = 0; i < postTransforms.length; i++) {
    postTransforms[i](element, options)
  }
}
Copy the code

The closeElement method does three things:

  • Call if the element has not been processedprocessElementHandle some attributes on the element.
  • Sets the parent element of the current element.
  • Sets the children of the current element.

trimEndingWhitespace

// src/compiler/parser/index.js
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()
    }
  }
}
Copy the code

TrimEndingWhitespace removes empty text nodes from elements.

checkRootConstraints

// src/compiler/parser/index.js
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'])}}Copy the code

CheckRootConstraints is a check on the root element. Slot and template cannot be used as root elements. You cannot use V-for on the root element of a stateful component because it renders multiple elements.

parseHTML

// src/compiler/parser/index.js
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 processing tag *@param {*} Tag Indicates the tag name *@param {*} attrs [{ name: attrName, value: attrVal, start, end }, ...] An array of properties of the form *@param {*} Unary Self-closing label *@param {*} The index * of the start tag in the HTML string@param {*} The end index of the end tag in the HTML string */
    start (tag, attrs, unary, start, end) {
      // If there is a namespace,, inherits 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)
      }
      // Create an AST object
      let element: ASTElement = createASTElement(tag, attrs, currentParent)
      // Set the namespace
      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
              }
            )
          }
        })
      }
      ,  tags should not appear in the template for non-server rendering
      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 }
        )
      }
      // Execute the class, style, and Model module preTransforms methods for the Element object, respectively
      // Handle input tags with v-model directives. Handle checkbox, radio, etc
      // apply pre-transforms
      for (let i = 0; i < preTransforms.length; i++) {
        element = preTransforms[i](element, options) || element
      }
      if(! inVPre) {// If there is a v-pre directive, set element.pre = true
        processPre(element)
        if (element.pre) {
          // If v-pre is present, set inVPre to true
          inVPre = true}}// If pre tags, set inPre to true
      if (platformIsPreTag(element.tag)) {
        inPre = true
      }
      if (inVPre) {
        // There is a v-pre directive, such a node will only render once, set the attributes on the node to the el.attrs array object, as static attributes, data update will not render this part of the content
        processRawAttrs(element)
      } else if(! element.processed) {// structural directives
        // Handle the V-for attribute
        processFor(element)
        // Handle v-if, v-else, v-else
        processIf(element)
        // Process the V-once instruction
        processOnce(element)
      }
      // Root does not exist. The element being processed is the first element, the root element of the component
      if(! root) { root = elementif(process.env.NODE_ENV ! = ='production') {
          // Check the root element, do not use slot, template, v-for
          checkRootConstraints(root)
        }
      }
      if(! unary) {// Non-self-closing tags. CurrentParent is used to record the current element, and the next element knows its parent when it is processed
        currentParent = element
        // Then push the element into the stack array for future processing of the current element's closing tag
        stack.push(element)
      } else {
        // Indicates that the current element is a self-closing label
        closeElement(element)
      }
    },
    /** * Process the end tag *@param {*} Tag End tag name *@param {*} Start Indicates the start index of the end tag@param {*} End End index of the end tag */
    end (tag, start, end) {
      // AST object corresponding to the end tag
      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
      }
      // Set the parent-child relationship
      closeElement(element)
    },
    // Process the text, generate an AST object based on the text, and place the AST in the children of its parent element.
    chars (text: string.startnumber.endnumber) {
      // Exception handling
      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()) {
        // The text is inside the pre tag or text.trim() is not empty
        text = isTextTag(currentParent) ? text : decodeHTMLCached(text)
      } else if(! children.length) {If text is not in the pre tag and text.trim() is null, and the parent element has no child nodes, set text to null
        // remove the whitespace-only node right after an opening tag
        text = ' '
      } else if (whitespaceOption) {
        // compress
        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 the text still exists after processing
      if (text) {
        if(! inPre && whitespaceOption ==='condense') {
          // Not in the Pre node, and the compression option exists in the configuration options, compress multiple consecutive whitespace into a single
          // condense consecutive whitespaces into single space
          text = text.replace(whitespaceRE, ' ')}let res
        // Generate AST objects based on text
        letchild: ? ASTNodeif(! inVPre && text ! = =' ' && (res = parseText(text, delimiters))) {
          // Expressions exist in text (i.e. delimiters (placeholders))
          child = {
            type2.expression: res.expression,
            tokens: res.tokens,
            text
          }
        } else if(text ! = =' '| |! children.length || children[children.length -1].text ! = =' ') {
          // Plain text node
          child = {
            type3,
            text
          }
        }
        // push into the children of the parent element
        if (child) {
          if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
            child.start = start
            child.end = end
          }
          children.push(child)
        }
      }
    },
    // Process the comment node
    comment (text: string, start, end) {
      // adding anything as a sibling to the root node is forbidden
      // comments should still be allowed, but ignored
      // Do not add anything as a sibling of root's node. Comments should be allowed, but ignored
      // If currentParent does not exist, comment is at the same level as root
      if (currentParent) {
        // Comment the AST of the node
        const child: ASTText = {
          type3,
          text,
          isCommenttrue
        }
        if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
          // Record the start index and end index of the node
          child.start = start
          child.end = end
        }
        // push into the children of the parent element
        currentParent.children.push(child)
      }
    }
  })
  return root
}
Copy the code

Templates are parsed using the parseHTML function, which is defined in SRC/Compiler /parser/html-parser:

// src/compiler/parser/html-parser
const attribute = /^\s*([^\s"'<>\/=]+)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /
const dynamicArgAttribute = /^\s*((? :v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeRegExp.source}] * `
const qnameCapture = ` ((? :${ncname}\ \ :)?${ncname}) `
const startTagOpen = new RegExp(` ^ <${qnameCapture}`)
const startTagClose = /^\s*(\/?) >/
const endTag = new RegExp(` ^ < \ \ /${qnameCapture}[^ >] * > `)
const doctype = / ^ 
      ]+>/i
// #7298: escape - to avoid being passed as HTML comment when inlined in page
const comment = / ^ 
      
const conditionalComment = / ^ 
      
// Special Elements (can contain anything)
export const isPlainTextElement = makeMap('script,style,textarea'.true)
const reCache = {}
const decodingMap = {
  '< ''<'.'> ''>'.'" ''"'.'& ''&'.'& # 10; ''\n'.'& # 9. ''\t'.'& # 39; '"'"
}
const encodedAttr = / & (? :lt|gt|quot|amp|#39); /g
const encodedAttrWithNewLines = / & (? :lt|gt|quot|amp|#39|#10|#9); /g
/ / # 5992
const isIgnoreNewlineTag = makeMap('pre,textarea'.true)
const shouldIgnoreFirstNewline = (tag, html) = > tag && isIgnoreNewlineTag(tag) && html[0= = ='\n'
function decodeAttr (value, shouldDecodeNewlines{
  const re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr
  return value.replace(re, match= > decodingMap[match])
}
/** * Loop through the HTML template string, processing each tag in turn, as well as the attribute * on the tag@param {*} HTML HTML template *@param {*} Options Configuration items */
export function parseHTML (html, options{
  const stack = []
  const expectHTML = options.expectHTML
  // Whether the label is self-closing
  const isUnaryTag = options.isUnaryTag || no
  // Can only have the start tag
  const canBeLeftOpenTag = options.canBeLeftOpenTag || no
  // Records the current start position in the raw HTML string
  let index = 0
  let last, lastTag
  while (html) {
    last = html
    // Make sure we're not in a plaintext content element like script/style
    // Make sure it's not in plain text elements like script, style, textarea, etc
    if(! lastTag || ! isPlainTextElement(lastTag)) {// find the first < character
      let textEnd = html.indexOf('<')
      // textEnd === 0 indicates that it is found at the beginning
      // Process possible comment tags, conditional comment tags, Doctype, start tag, end tag respectively
      // After each case is processed, the loop is truncated (continue) and the HTML string is reset, truncating the processed tags, and the next loop processes the remaining HTML string template
      if (textEnd === 0) {
        // Comment:
        // Handle the comment tag <! -- xx -->
        if (comment.test(html)) {
          // The end index of the comment tag
          const commentEnd = html.indexOf('-->')
          if (commentEnd >= 0) {
            // Whether comments should be kept
            if (options.shouldKeepComment) {
              // Get: comment content, comment start index, comment end index
              options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3)}// Adjust the HTML and index variables
            advance(commentEnd + 3)
            continue}}// Handle condition comment tag: <! --[if IE]>
        // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
        if (conditionalComment.test(html)) {
          // End position
          const conditionalEnd = html.indexOf('] > ')
          if (conditionalEnd >= 0) {
            // Adjust the HTML and index variables
            advance(conditionalEnd + 2)
            continue}}// handle Doctype, 
      
        const doctypeMatch = html.match(doctype)
        if (doctypeMatch) {
          advance(doctypeMatch[0].length)
          continue
        }
        /** * Processing the start tag and end tag is the karyotype part of the whole function * these two parts are constructing element ast */
        // Handle closing tags, such as 
        const endTagMatch = html.match(endTag)
        if (endTagMatch) {
          const curIndex = index
          advance(endTagMatch[0].length)
          // Process the closing tag
          parseEndTag(endTagMatch[1], curIndex, index)
          continue
        }
        // Process the start tag
        const startTagMatch = parseStartTag()
        if (startTagMatch) {
          // Process the results of the previous step further and finally call the options.start method
          // The real parsing is done in the start method
          handleStartTag(startTagMatch)
          if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
            advance(1)}continue}}let text, rest, next
      if (textEnd >= 0) {
        
        // It is just a plain piece of text: < I am text
        // Find the next < in the HTML until 
        // The entire process is adjusting the textEnd value as the starting point for the next valid tag in the HTML
        // Capture the content after textEnd in the HTML template string, rest = 
        rest = html.slice(textEnd)
        // This while loop deals with plain text after 
        // Intercepts the text and finds the starting position of the valid tag (textEnd)
        while(! endTag.test(rest) && ! startTagOpen.test(rest) && ! comment.test(rest) && ! conditionalComment.test(rest) ) {< < < < < < < < < < < < < < < < < < < < < < < <
          next = rest.indexOf('<'.1)
          // If < is not found, the loop ends
          if (next < 0break
          // The < is found in the following string, indexed at textEnd
          textEnd += next
          // Intercepts the content after the HTML string template textEnd and assigns it to REST
          rest = html.slice(textEnd)
        }
        < < < is a piece of plain text, or < < is a valid tag found after the text, truncated text
        text = html.substring(0, textEnd)
      }
      // If textEnd < 0, the < is not found in the HTML, and the HTML is just text
      if (textEnd < 0) {
        text = html
      }
      // Intercepts the text content from the HTML template string
      if (text) {
        advance(text.length)
      }
      // Process text
      // Generate an AST object based on text and place the AST in its parent element
      // in currentParent.children array
      if (options.chars && text) {
        options.chars(text, index - text.length, index)
      }
    } else {
      // Handle closed tags for script, style, textarea tags
      let endTagLength = 0
      // Start the tag in lower case
      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 end tag, such as 
      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 there is any content in the stack array, there is a label that has not been closed, and a message is displayed
    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()
  /** * resets HTML, HTML = all characters back from index position n * index is the starting index of HTML in the original template string, and the starting position of the character for the next processing *@param {*} N index * /
  function advance (n{
    index += n
    html = html.substring(n)
  }
  /** * parse the start tag, such as <div id="app"> *@returns { tagName: 'div', attrs: [[xx], ...] , start: index }* /
  function parseStartTag ({
    const start = html.match(startTagOpen)
    if (start) {
      // Process the result
      const match = {
        / / tag name
        tagName: start[1].// Attributes, placeholders
        attrs: [].// Start position of the label
        start: index
      }
      /** * adjust HTML and index, for example: * HTML = 'id="app">' * index = current index * start[0] = '
      advance(start[0].length)
      let end, attr
      // Process the attributes in the start tag and place them in the match-.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 = '>' or end = '/>'
      if (end) {
        match.unarySlash = end[1]
        advance(end[0].length)
        match.end = index
        return match
      }
    }
  }
  Attrs, if the tag is not self-closing, the tag information is placed in the stack array, and the tag information is popped out of the stack when its closing tag is being processed. Now all the information about the tag is in the Element AST object. We call the options.start method to process the tag and generate the Element AST from the tag information, along with the attributes and directives on the start tag. Finally, place element AST in 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 
      
    constunary = isUnaryTag(tagName) || !! unarySlashAttrs = [{name: attrName, value: attrVal, start: xx, end: xx},...]     / / such as 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]       / / such as 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)       }       // In non-production environments, record the start and end indexes of 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 the tag is not self-closing, the tag information is placed in the stack array, and it will be popped out of the stack when its closing tag is processed in the future     // If it is a self-closing tag, the tag information does not need to go to the stack. If it is a self-closing tag, the tag information does not need to go to the stack. If it is a self-closing tag, the tag information does not need to go to the stack     if(! unary) {{tag, lowerCasedTag, attrs, start, end}       stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end })       // Identifies the end tag of the current tag as tagName       lastTag = tagName     }     /** * Call the start method, which does the following six things: * 1, create AST object * 2, handle input tag with v-model instruction, checkbox, radio, etc. * 3, handle tag with v-model instruction. For example, v-pre, V-for, V-if, v-once * 4, if root does not exist, set the current element to root * 5, if the current element is not self-closing tag, push itself into the stack array, and record currentParent. 6. If the current element is a self-closing tag, it indicates that the tag is finished processing, and it relates itself to the parent element, and sets its child element */     if (options.start) {       options.start(tagName, attrs, unary, match.start, match.end)     }   }   /** * Parse the closing tag, such as </div> * * 2. After processing the end tag, adjust the stack array. Make sure that the last element in the stack array is the start tag corresponding to the next end tag *. 3. Handle exceptions such as the last element in the stack array is not the start tag corresponding to the current end tag, and the * BR and P tags are handled separately *@param {*} TagName specifies the tagName, such as div *@param {*} Start Indicates the start index of the end tag@param {*} End End index of the 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 to find the first label that is the same as the current end label. This label is the description object of the start label corresponding to the end label     // Theoretically, 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 never found in the stack, pos < 0 and else branch     if (pos >= 0) {       // This for loop is responsible for closing all tags in the stack array whose index is >= pos       The last element of the stack array is the start tag.       // There are exceptions where elements do not provide closing tags, such as:       // stack = ['span', 'div', 'span', 'h1'], the current processing end tag tagName = div       // match to div, pos = 1, then the two tags with index 2 and 3 (span, h1) do not provide the closing tag       // The for loop closes the div, span, and h1 tags,       // And the development environment for the span and h1 tags "did not match the end tag"       // 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) {           // Call options.end to handle the normal closing tag           options.end(stack[i].tag, start, end)         }       }       // Remove the tags you just processed from the array, ensuring 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 current label being processed is the

tag
      if (options.start) {         options.start(tagName, [], true, start, end)       }     } else if (lowerCasedTagName === 'p') {       // Process the

tag

      if (options.start) {         options.start(tagName, [], false, start, end)       }       // Process the

tag
      if (options.end) {         options.end(tagName, start, end)       }     }   } } Copy the code

The main logic of parseHTML is loop parsing, using the re to match, and then doing different things, using the advance function to change the index during the match until the parsing is done.

Regex matches comment tags, document type tags, start tags, and end tags. Use the handleStartTag method to parse the start tag, push the AST object constructed from the non-unary tag into the stack, parse the close tag using the parseEndTag method, that is, the stack in reverse order, and find the first tag that is identical to the current end tag. The label is the description object of the start label corresponding to the end label.

There are three types of AST element nodes: type 1 indicates a normal element, type 2 indicates an expression, and type 3 indicates plain text. Parse uses regex to parse template characters into an AST syntax tree.

optimize

After the template is parsed, the AST tree is generated, followed by some optimizations to the AST.

optimize

// src/compiler/optimizer.js
/** * Goal of the optimizer: walk the generated template AST tree * and detect sub-trees that are purely static, i.e. parts of * the DOM that never needs to change. * * Once we detect these sub-trees, we can: * * 1. Hoist them into constants, so that we no longer need to * create fresh nodes for them on each re-render; * 2. Completely skip them in the patching process. */
export function optimize (root: ? ASTElement, options: CompilerOptions{
  if(! root)return
  /** * options.staticKeys = 'staticClass,staticStyle' * isStaticKey = function(val) { return map[val] } */
  isStaticKey = genStaticKeysCached(options.staticKeys || ' ')
  // The platform retains the label
  isPlatformReservedTag = options.isReservedTag || no
  // first pass: mark all non-static nodes.
  // Mark the static node
  markStatic(root)
  // second pass: mark static roots.
  // Mark the static root
  markStaticRoots(root, false)}Copy the code

markStatic

// src/compiler/optimizer.js
function markStatic (node: ASTNode{
  // Use node.static to indicate whether a node is static
  node.static = isStatic(node)
  if (node.type === 1) {
    // do not make component slot content static. this avoids
    // 1. components not able to mutate slot nodes
    // 2. static slot content fails for hot-reloading
    /** * Do not set the component's slot content to a static node to avoid: * 1, the component cannot change the slot node * 2, static slot content fails when hot reloading */
    if(! isPlatformReservedTag(node.tag) && node.tag ! = ='slot' &&
      node.attrsMap['inline-template'] = =null
    ) {
      // Recursive terminating condition. If the node is not a platform reserved tag && nor a slot tag && nor an inline template, it ends
      return
    }
    // Iterate over the children, recursively calling markStatic to mark the static properties of those children
    for (let i = 0, l = node.children.length; i < l; i++) {
      const child = node.children[i]
      markStatic(child)
      // If the child node is non-static, update the parent node to non-static
      if(! child.static) { node.static =false}}// If the node has v-if, V-else, v-else instructions, then mark the nodes in the block as static
    if (node.ifConditions) {
      for (let i = 1, l = node.ifConditions.length; i < l; i++) {
        const block = node.ifConditions[i].block
        markStatic(block)
        if(! block.static) { node.static =false
        }
      }
    }
  }
}

/** * Check whether the node is static: * Check whether the node is static: * Check whether the node is static: * Check whether the node is static: Dynamic node */ if the parent is a template tag containing the V-for instruction
function isStatic (node: ASTNode) :boolean {
  if (node.type === 2) { // expression
    {{MSG}}
    return false
  }
  if (node.type === 3) { // text
    return true
  }
  return!!!!! (node.pre || ( ! node.hasBindings &&// no dynamic bindings! node.if && ! node.for &&// not v-if or v-for or v-else! isBuiltInTag(node.tag) &&// not a built-in
    isPlatformReservedTag(node.tag) && // not a component! isDirectChildOfTemplateFor(node) &&Object.keys(node).every(isStaticKey)
  ))
}
Copy the code

markStaticRoots

// src/compiler/optimizer.js
function markStaticRoots (node: ASTNode, isInFor: boolean{
  if (node.type === 1) {
    if (node.static || node.once) {
      // A node that is already static or a node for the V-once directive
      node.staticInFor = isInFor
    }
    // For a node to qualify as a static root, it should have children that
    // are not just static text. Otherwise the cost of hoisting out will
    // outweigh the benefits and it's better off to just always render it fresh.
    if(node.static && node.children.length && ! ( node.children.length ===1 &&
      node.children[0].type === 3
    )) {
      In addition to being a static node itself, children must be satisfied, and children cannot be just a text node
      node.staticRoot = true
      return
    } else {
      node.staticRoot = false
    }
    // The current node is not a static root, recursively traverses its children, marking the static root
    if (node.children) {
      for (let i = 0, l = node.children.length; i < l; i++) { markStaticRoots(node.children[i], isInFor || !! node.for) } }// If the node has v-if, V-else, v-else instructions, then mark the static root of the block node
    if (node.ifConditions) {
      for (let i = 1, l = node.ifConditions.length; i < l; i++) {
        markStaticRoots(node.ifConditions[i].block, isInFor)
      }
    }
  }
}
Copy the code

Not all data in the template are responsive, and some data will not change after the first rendering. By traversing the generated template AST tree and marking these nodes, they can be skipped in the patch process to improve the performance of comparison.

In Optimize, it’s really markStatic(root) for static nodes and markStaticRoots(root, False) for static roots.

generate

After the AST syntax tree is generated, the AST is optimized to mark static nodes and static roots. Finally, generate code strings through generate.

generate

// src/compiler/codegen/index.js
export function generate (
  ast: ASTElement | void,
  options: CompilerOptions
) :CodegenResult {
  const state = new CodegenState(options)
  // fix #11483, Root level <script> tags should not be rendered.
  const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")'
  return {
    render`with(this){return ${code}} `.staticRenderFns: state.staticRenderFns
  }
}
Copy the code

State is an instance of CodegenState, and some of its properties and methods are used when generating code. The generate function mainly generates code from genElement and then wraps it around with(this){return ${code}}.

genElement

// src/compiler/codegen/index.js
export function genElement (el: ASTElement, state: CodegenState) :string {
  if (el.parent) {
    el.pre = el.pre || el.parent.pre
  }
  if(el.staticRoot && ! el.staticProcessed) {// Process the static root node
    return genStatic(el, state)
  } else if(el.once && ! el.onceProcessed) {// Process nodes with v-once instructions
    return genOnce(el, state)
  } else if(el.for && ! el.forProcessed) {/ / v - for processing
    return genFor(el, state)
  } else if(el.if && ! el.ifProcessed) {/ / v - the if processing
    return genIf(el, state)
  } else if (el.tag === 'template'&&! el.slotTarget && ! state.pre) {// Process the child nodes
    return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
    // Process the slot
    return genSlot(el, state)
  } else {
    // component or element
    let code
    if (el.component) {
      // Handle dynamic components
      code = genComponent(el.component, el, state)
    } else {
      // Custom components and native tags
      let data
      if(! el.plain || (el.pre && state.maybeComponent(el))) {// Non-ordinary elements or components with v-pre instructions go here to handle all attributes of the node
        data = genData(el, state)
      }
      // Process the child nodes to get an array of string code for all the child nodes
      const children = el.inlineTemplate ? null : genChildren(el, state, true)
      code = `_c('${el.tag}'${data ? `,${data}` : ' ' // data
        }${children ? `,${children}` : ' ' // children
        }) `
    }
    // module transforms
    for (let i = 0; i < state.transforms.length; i++) {
      code = state.transforms[i](el, code)
    }
    return code
  }
}
Copy the code

The main purpose of genElement is to generate code functions for attributes on AST nodes using different methods.

genStatic

// src/compiler/codegen/index.js
// hoist static sub-trees out
function genStatic (el: ASTElement, state: CodegenState) :string {
  // Indicates that the current static node has been processed
  el.staticProcessed = true
  // Some elements (templates) need to behave differently inside of a v-pre
  // node. All pre nodes are static roots, so we can use this as a location to
  // wrap a state change and reset it upon exiting the pre node.
  const originalPreState = state.pre
  if (el.pre) {
    state.pre = el.pre
  }
  // Add the generated code to staticRenderFns
  state.staticRenderFns.push(`with(this){return ${genElement(el, state)}} `)
  state.pre = originalPreState
  / / return _m function, state. StaticRenderFns. Said in the array subscript length - 1
  return `_m(${state.staticRenderFns.length - 1
    }${el.staticInFor ? ',true' : ' '
    }) `
}
Copy the code

genOnce

// src/compiler/codegen/index.js
function genOnce (el: ASTElement, state: CodegenState) :string {
  // Indicates that the v-once instruction for the current node has been processed
  el.onceProcessed = true
  if(el.if && ! el.ifProcessed) {// If contains v-if and v-if is not processed, processing v-if eventually generates a ternary operator code
    return genIf(el, state)
  } else if (el.staticInFor) {
    // Indicates that the current node is a static node wrapped inside the v-for instruction node
    let key = ' '
    let parent = el.parent
    while (parent) {
      if (parent.for) {
        key = parent.key
        break
      }
      parent = parent.parent
    }
    if(! key) { process.env.NODE_ENV ! = ='production' && state.warn(
        `v-once can only be used inside v-for that is keyed. `,
        el.rawAttrsMap['v-once'])return genElement(el, state)
    }
    // Generate the _o function
    return `_o(${genElement(el, state)}.${state.onceId++}.${key}) `
  } else {
    // This is a simple static node, generating _m function
    return genStatic(el, state)
  }
}
Copy the code

genFor

// src/compiler/codegen/index.js
export function genFor (
  el: any, state: CodegenState, altGen? :Function, altHelper? :string
) :string {
  const exp = el.for
  const alias = el.alias
  const iterator1 = el.iterator1 ? `,${el.iterator1}` : ' '
  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ' '
  // Indicate that v-for must use key when on a component
  if(process.env.NODE_ENV ! = ='production'&& state.maybeComponent(el) && el.tag ! = ='slot'&& el.tag ! = ='template' &&
    !el.key
  ) {
    state.warn(
      ` <${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
      `v-for should have explicit keys. ` +
      `See https://vuejs.org/guide/list.html#key for more info.`,
      el.rawAttrsMap['v-for'].true /* tip */)}// indicates that the v-for instruction on the current node has been processed
  el.forProcessed = true // avoid recursion
  /** * generates _l functions, such as:  * v-for="(item,index) in data" * * _l((data), function (item, index) { * return genElememt(el, state) * }) */
  return `${altHelper || '_l'}((${exp}), ` +
    `function(${alias}${iterator1}${iterator2}) {` +
    `return ${(altGen || genElement)(el, state)}` +
    '}) '
}
Copy the code

genIf

// src/compiler/codegen/index.js
export function genIf (
  el: any, state: CodegenState, altGen? :Function, altEmpty? :string
) :string {
  // The v-if directive marking the current node has been processed
  el.ifProcessed = true // avoid recursion
  // Get the ternary expression, condition? render1 : render2
  return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}

function genIfConditions (conditions: ASTIfConditions, state: CodegenState, altGen? :Function, altEmpty? :string
) :string {
  // The length is empty and returns an empty node rendering function directly
  if(! conditions.length) {return altEmpty || '_e()'
  }
  // Get the first condition from conditions
  const condition = conditions.shift()
  if (condition.exp) {
    // generate a ternary operator by condition. Exp,
    // if there are multiple conditions, the multilevel ternary operation is generated
    return ` (${condition.exp})?${genTernaryExp(condition.block) }:${genIfConditions(conditions, state, altGen, altEmpty) }`
  } else {
    return `${genTernaryExp(condition.block)}`
  }
  // v-if with v-once should generate code like (a)? _m(0):_m(1)
  function genTernaryExp (el{
    return altGen
      ? altGen(el, state)
      : el.once
        ? genOnce(el, state)
        : genElement(el, state)
  }
}
Copy the code

genChildren

// src/compiler/codegen/index.js
export function genChildren (el: ASTElement, state: CodegenState, checkSkip? :boolean, altGenElement? :Function, altGenNode? :Function
) :string | void {
  const children = el.children
  if (children.length) {
    const el: any = children[0]
    // optimize single v-for
    if (children.length === 1&& el.for && el.tag ! = ='template'&& el.tag ! = ='slot'
    ) {
      // a child && node has v-for && not template tag && not slot
      // Call genElement directly to enter genFor
      const normalizationType = checkSkip
        ? state.maybeComponent(el) ? ` `, 1 : ` `, 0
        : ` `
      return `${(altGenElement || genElement)(el, state)}${normalizationType}`
    }
    const normalizationType = checkSkip
      ? getNormalizationType(children, state.maybeComponent)
      : 0
    // a function that generates code
    const gen = altGenNode || genNode
    // Return an array, each element of which is a render function of a child node,
    // Format: ['_c(tag, data, children, normalizationType)',...
    return ` [${children.map(c => gen(c, state)).join(', ')}]${normalizationType ? `,${normalizationType}` : ' '
      }`}}Copy the code

genSlot

// src/compiler/codegen/index.js
function genSlot (el: ASTElement, state: CodegenState) :string {
  // Slot name
  const slotName = el.slotName || '"default"'
  // Process the child nodes
  const children = genChildren(el, state)
  // Finally return _t function
  let res = `_t(${slotName}${children ? `,function(){return ${children}} ` : ' '}`
  const attrs = el.attrs || el.dynamicAttrs
    ? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(attr= > ({
      // slot props are camelized
      name: camelize(attr.name),
      value: attr.value,
      dynamic: attr.dynamic
    })))
    : null
  const bind = el.attrsMap['v-bind']
  if((attrs || bind) && ! children) { res +=`,null`
  }
  if (attrs) {
    res += `,${attrs}`
  }
  if (bind) {
    res += `${attrs ? ' ' : ',null'}.${bind}`
  }
  return res + ') '
}
Copy the code

genProps

// src/compiler/codegen/index.js
function genProps (props: Array<ASTAttr>) :string {
  // Static attributes
  let staticProps = ` `
  // Dynamic attributes
  let dynamicProps = ` `
  // Iterate over the property array
  for (let i = 0; i < props.length; i++) {
    / / property
    const prop = props[i]
    / / property values
    const value = __WEEX__
      ? generateValue(prop.value)
      : transformSpecialNewlines(prop.value)
    if (prop.dynamic) {
      // Dynamic properties, 'dAttrName,dAttrVal... `
      dynamicProps += `${prop.name}.${value}, `
    } else {
      // Static attribute, 'attrName,attrVal... '
      staticProps += `"${prop.name}":${value}, `}}// Remove the last comma of the static attribute
  staticProps = ` {${staticProps.slice(0, -1)}} `
  if (dynamicProps) {
    // If there is a dynamic attribute, return:
    // _d(static attribute string, dynamic attribute string)
    return `_d(${staticProps}[${dynamicProps.slice(0, -1)}]) `
  } else {
    // Indicates that there is no dynamic attribute in the property array
    return staticProps
  }
}
Copy the code

genData

// src/compiler/codegen/index.js
export function genData (el: ASTElement, state: CodegenState) :string {
  let data = '{'
  // directives first.
  // directives may mutate the el's other properties before they are generated.
  const dirs = genDirectives(el, state)
  if (dirs) data += dirs + ', '
  // key
  if (el.key) {
    data += `key:${el.key}, `
  }
  // ref
  if (el.ref) {
    data += `ref:${el.ref}, `
  }
  if (el.refInFor) {
    data += `refInFor:true,`
  }
  // pre
  if (el.pre) {
    data += `pre:true,`
  }
  // record original tag name for components using "is" attribute
  if (el.component) {
    data += `tag:"${el.tag}", `
  }
  // Execute the genData method for the node module (class, style),
  // module data generation functions
  for (let i = 0; i < state.dataGenFns.length; i++) {
    data += state.dataGenFns[i](el)
  }
  // attributes
  if (el.attrs) {
    data += `attrs:${genProps(el.attrs)}, `
  }
  // DOM props
  if (el.props) {
    data += `domProps:${genProps(el.props)}, `
  }
  // event handlers
  {' on${eventName}:handleCode '} or {' on_d(${eventName}:handleCode ', '${eventName},handleCode')}
  if (el.events) {
    data += `${genHandlers(el.events, false)}, `
  }
  // Events with.native modifier,
  if (el.nativeEvents) {
    data += `${genHandlers(el.nativeEvents, true)}, `
  }
  // slot target
  // only for non-scoped slots
  // Non-scoped slot
  if(el.slotTarget && ! el.slotScope) { data +=`slot:${el.slotTarget}, `
  }
  // scoped slots
  // Scope slot
  if (el.scopedSlots) {
    data += `${genScopedSlots(el, el.scopedSlots, state)}, `
  }
  // component v-model
  if (el.model) {
    data += `model:{value:${el.model.value },callback:${el.model.callback },expression:${el.model.expression }}, `
  }
  // inline-template
  if (el.inlineTemplate) {
    const inlineTemplate = genInlineTemplate(el, state)
    if (inlineTemplate) {
      data += `${inlineTemplate}, `
    }
  }
  data = data.replace($/ /,.' ') + '} '
  // v-bind dynamic argument wrap
  // v-bind with dynamic arguments must be applied using the same v-bind object
  // merge helper so that class/style/mustUseProp attrs are handled correctly.
  if (el.dynamicAttrs) {
    data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)}) `
  }
  // v-bind data wrap
  if (el.wrapData) {
    data = el.wrapData(data)
  }
  // v-on data wrap
  if (el.wrapListeners) {
    data = el.wrapListeners(data)
  }
  return data
}
Copy the code

The genData function constructs a data object string based on the attributes of the AST element node, which will be passed as a parameter later when creating a VNode.

genComponent

// src/compiler/codegen/index.js
function genComponent (
  componentName: string,
  el: ASTElement,
  state: CodegenState
) :string {
  // All child nodes
  const children = el.inlineTemplate ? null : genChildren(el, state, true)
  // Return '_c(compName, data, children)'
  CompName is the value of the IS attribute
  return `_c(${componentName}.${genData(el, state)}${children ? `,${children}` : ' '
    }) `
}
Copy the code

Take a chestnut

The template

<ul :class="bindCls" class="list" v-if="isShow">
  <li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li>
</ul>
Copy the code

The parse generated AST

ast = {
  'type'1.'tag''ul'.'attrsList': [].'attrsMap': {
    ':class''bindCls'.'class''list'.'v-if''isShow'
  },
  'if''isShow'.'ifConditions': [{
    'exp''isShow'.'block'// ul ast element}].'parent'undefined.'plain'false.'staticClass''list'.'classBinding''bindCls'.'children': [{
    'type'1.'tag''li'.'attrsList': [{
      'name''@click'.'value''clickItem(index)'}].'attrsMap': {
      '@click''clickItem(index)'.'v-for''(item,index) in data'
    },
    'parent'// ul ast element
      'plain'false.'events': {
      'click': {
        'value''clickItem(index)'}},'hasBindings'true.'for''data'.'alias''item'.'iterator1''index'.'children': [
      'type'2.'expression''_s(item)+":"+_s(index)'
      'text''{{item}}:{{index}}'.'tokens': [{'@binding''item' },
        ':',
        { '@binding''index'}]]}Copy the code

Optimize optimization AST

ast = {
  'type'1.'tag''ul'.'attrsList': [].'attrsMap': {
    ':class''bindCls'.'class''list'.'v-if''isShow'
  },
  'if''isShow'.'ifConditions': [{
    'exp''isShow'.'block'// ul ast element}].'parent'undefined.'plain'false.'staticClass''list'.'classBinding''bindCls'.'static'false.'staticRoot'false.'children': [{
    'type'1.'tag''li'.'attrsList': [{
      'name''@click'.'value''clickItem(index)'}].'attrsMap': {
      '@click''clickItem(index)'.'v-for''(item,index) in data'
    },
    'parent'// ul ast element
      'plain'false.'events': {
      'click': {
        'value''clickItem(index)'}},'hasBindings'true.'for''data'.'alias''item'.'iterator1''index'.'static'false.'staticRoot'false.'children': [
      'type'2.'expression''_s(item)+":"+_s(index)'
      'text''{{item}}:{{index}}'.'tokens': [{'@binding''item' },
        ':',
        { '@binding''index'}].'static'false]]}}Copy the code

Generate generate code

with (this) {
  return (isShow) ?
    _c('ul', {
      staticClass"list".class: bindCls
    },
      _l((data), function (item, index{
        return _c('li', {
          on: {
            "click"function ($event{
              clickItem(index)
            }
          }
        },
          [_v(_s(item) + ":" + _s(index))])
      })
    ) : _e()
}
Copy the code

The resulting shorthand functions, such as _c, _t, _l, and _m, are defined in:

// src/core/instance/render-helpers/index.js
export function installRenderHelpers (target: any{ target._o = markOnce target._n = toNumber target._s = toString target._l = renderList target._t = renderSlot target._q  = looseEqual target._i = looseIndexOf target._m = renderStatic target._f = resolveFilter target._k = checkKeyCodes target._b = bindObjectProps target._v = createTextVNode target._e = createEmptyVNode target._u = resolveScopedSlots target._g = bindObjectListeners target._d = bindDynamicKeys target._p = prependModifier }Copy the code

To this, Vue source code general comb finished, during this period to view a lot of big guy’s article, thank you big guys selfless sharing, thank you for the friendly front end circle.

A link to the

Vue (V2.6.14) source code detoxification (pre) : handwritten a simple version of Vue

Vue (V2.6.14) source code detoxification (a) : preparation

Vue (V2.6.14) source code detoxification (two) : initialization and mount

Vue (V2.6.14) source code detoxification (three) : response type principle

Vue (V2.6.14) source code detoxification (four) : update strategy

Vue (v2.6.14) source code detoxification (five) : render and VNode

Vue (V2.6.14) source code detoxification (six) : Update and patch

Vue (V2.6.14) source code detoxification (seven) : template compilation

If you think it’s ok, give it a thumbs up!! You can also visit my personal blog at www.mingme.net/