preface

This article is the last part of the Vue compiler. The first two parts are: Vue source code interpretation (8) — compiler parsing, and Vue source code interpretation (9) — compiler optimization.

Starting from the HTML template string, all tags and attributes on the tags are parsed to get the AST syntax tree. Then, static tags are made based on the AST syntax tree. First, whether each node is static or static is marked, and then the static root node is marked. These static root node updates can be skipped in subsequent updates to improve performance.

This last section is about generating render functions from the AST.

The target

Get a deeper understanding of how rendering functions are generated, how the compiler turns the AST into runtime code, and what does the htML-like template we wrote end up with?

The source code interpretation

The entrance

/src/compiler/index.js

/** * All this work has been done with the sole purpose of building platform-specific compilation options. For example, web platform * * 1, parse HTML template into AST * 2, static mark ast tree * 3, ast generation rendering function * static rendering function into code. StaticRenderFns array * code Execute the render function to get vNode */ for the dynamic render function *
export const createCompiler = createCompilerCreator(function baseCompile (template: string, options: CompilerOptions) :CompiledResult {
  // Parse the template into an AST. Each AST object has all the information of the element, such as label information, attribute information, slot information, parent node, child node, etc.
  Start and options.end are two methods that handle the start and end tags
  const ast = parse(template.trim(), options)
  // Optimize by traversing the AST, marking each node statically
  // Mark whether each node is static or not, and then further mark the static root node
  // This allows you to skip the static nodes in subsequent updates
  // Mark the static root, which is used in the render function generation stage, to generate the render function of the static root node
  if(options.optimize ! = =false) {
    optimize(ast, options)
  }
  // Code generation that converts the AST to the string form of the executable render function
  // 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

generate

/src/compiler/codegen/index.js

/** * generate render function * from AST@returns { * render: `with(this){return _c(tag, data, children)}`,
 *   staticRenderFns: state.staticRenderFns
 * } 
 */
export function generate(
  ast: ASTElement | void,
  options: CompilerOptions
) :CodegenResult {
  // Instantiate the CodegenState object, some of which will be needed to generate code
  const state = new CodegenState(options)
  // Generate code for string format, such as: '_c(tag, data, children, normalizationType)'
  // data forms a JSON string for the attributes on the node, such as '{key: xx, ref: xx,... } '
  // children is a string array composed of string format code for all child nodes, format:
  // `['_c(tag, data, children)', ...] , normalizationType `,
  // The final normalization is the fourth parameter of _c,
  // Indicates the normalized type of the node
  // Code does not have to be _c, but it can be something else. For example, if the entire component is static, the result will be _m(0).
  const code = ast ? genElement(ast, state) : '_c("div")'
  return {
    render: `with(this){return ${code}} `.staticRenderFns: state.staticRenderFns
  }
}

Copy the code

genElement

/src/compiler/codegen/index.js

Reading Suggestions:

Read the statement portion of the final Else module that generates code, the else branch that handles custom components and native tags, to understand what the resulting data format will look like. Then go back to reading genChildren and genData, reading genChildren first, with less code, fully understanding the resulting data structure, and then reading the other branches from top to bottom.

When reading the following code, please put aside the AST object obtained by the Vue source interpretation (8) — the compiler’s parsing (2), because the process of generating the rendering function is the process of processing many properties on the object.

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 and generate the rendering function for the node * 1, put the rendering function for the current static node into the staticRenderFns array * 2, return an executable function _m(idx, true or "") */
    return genStatic(el, state)
  } else if(el.once && ! el.onceProcessed) {/** * process a node with a v-if instruction, and there are three results: * 1, the current node has a V-if instruction, and a ternary expression, condition? render1 : Render2 * 2, the current node is a static node contained inside the V-for directive, resulting in _o(_C (tag, data, children), number, key) * 3, the current node is a pure V-once node. _m(idx, true of "") */
    return genOnce(el, state)
  } else if(el.for && ! el.forProcessed) {/** * Process the v-for directive on the node * to get '_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})' */
    return genFor(el, state)
  } else if(el.if && ! el.ifProcessed) {/** * process the node with the v-if directive, resulting in a ternary expression: condition? render1 : render2 */
    return genIf(el, state)
  } else if (el.tag === 'template'&&! el.slotTarget && ! state.pre) {/** * generate a rendering function for all child nodes and return an array in the following format:  * [_c(tag, data, children, normalizationType), ...] * /
    return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
    /** * generate the render function for the slot and get * _t(slotName, children, attrs, bind) */
    return genSlot(el, state)
  } else {
    // component or element
    // Handle dynamic components and normal elements (custom components, native tags)
    let code
    if (el.component) {
      /** * processing dynamic component, generate dynamic component rendering function * get '_c(compName, data, children)' */
      code = genComponent(el.component, el, state)
    } else {
      // Custom components and native tags go here
      let data
      if(! el.plain || (el.pre && state.maybeComponent(el))) {// Non-ordinary elements or components with v-pre directives go here, process all the attributes of the node, and return a JSON string,
        '{key: xx, ref: xx,... } '
        data = genData(el, state)
      }

      // Process the child nodes to get an array of all the child nodes string format code, format:
      // `['_c(tag, data, children)', ...] , normalizationType `,
      // The final normalization means the normalized type of the node and is not important as it needs no attention
      const children = el.inlineTemplate ? null : genChildren(el, state, true)
      // Get the final string format code, format:
      // '_c(tag, data, children, normalizationType)'
      code = `_c('${el.tag}'${data ? `,${data}` : ' ' // data
        }${children ? `,${children}` : ' ' // children
        }) `
    }
    // If you provide a method transformCode,
    // Then the final code will be processed by the method of each module.
    // The framework does not provide this method, but even if it does, the final format is _c(tag, data, children).
    // module transforms
    for (let i = 0; i < state.transforms.length; i++) {
      code = state.transforms[i](el, code)
    }
    return code
  }
}

Copy the code

genChildren

/src/compiler/codegen/index.js

/** * generate a rendering function for all child nodes and return an array in the format: * [_c(tag, data, children, normalizationType),...] * /
export function genChildren(el: ASTElement, state: CodegenState, checkSkip? : boolean, altGenElement? :Function, altGenNode? :Function
) :string | void {
  // All child nodes
  const children = el.children
  if (children.length) {
    // The first child
    const el: any = children[0]
    // optimize single v-for
    if (children.length === 1&& el.for && el.tag ! = ='template'&& el.tag ! = ='slot'
    ) {
      // Optimize so that only one child has a V-for directive on it and the child tag is not template or slot
      // The optimized approach is to call genElement directly to generate the render function for the node, without having to go through the following loop and then call genCode to get the render function
      const normalizationType = checkSkip
        ? state.maybeComponent(el) ? ` `, 1 : ` `, 0
        : ` `
      return `${(altGenElement || genElement)(el, state)}${normalizationType}`
    }
    // Get the normalized type of the node and return a number 0, 1, 2
    const normalizationType = checkSkip
      ? getNormalizationType(children, state.maybeComponent)
      : 0
    // function, a function that generates code
    const gen = altGenNode || genNode
    // Return an array, each element of which is a rendering function for a child node,
    ['_c(tag, data, children, normalizationType)',...]
    return ` [${children.map(c => gen(c, state)).join(', ')}]${normalizationType ? `,${normalizationType}` : ' '
      }`}}Copy the code

genNode

/src/compiler/codegen/index.js

function genNode(node: ASTNode, state: CodegenState) :string {
  if (node.type === 1) {
    return genElement(node, state)
  } else if (node.type === 3 && node.isComment) {
    return genComment(node)
  } else {
    return genText(node)
  }
}

Copy the code

genText

/src/compiler/codegen/index.js

export function genText(text: ASTText | ASTExpression) :string {
  return `_v(${text.type === 2
    ? text.expression // no need for () because already wrapped in _s()
    : transformSpecialNewlines(JSON.stringify(text.text))
    }) `
}

Copy the code

genComment

/src/compiler/codegen/index.js

export function genComment(comment: ASTText) :string {
  return `_e(The ${JSON.stringify(comment.text)}) `
}

Copy the code

genData

/src/compiler/codegen/index.js

Data = {key: xx, ref: xx,... } * /
export function genData(el: ASTElement, state: CodegenState) :string {
  // A JSON string composed of the attributes of the node
  let data = '{'

  // Process the instructions first, because the instructions may change these attributes before generating other attributes
  // Execute instructions to compile methods such as Web platform V-text, V-HTML, v-Model, and then add corresponding attributes to the EL object.
  // For example, v-text: el.textContent = _s(value, dir)
  // v-html: el.innerhtml = _s(value, dir)
  // When the directive is running and there is a task, such as the V-Model, returns directives: [{name, rawName, value, arg, modifiers},...}]
  // directives first.
  // directives may mutate the el's other properties before they are generated.
  const dirs = genDirectives(el, state)
  if (dirs) data += dirs + ', '

  Key, data = {key: xx}
  if (el.key) {
    data += `key:${el.key}, `
  }
  // ref, data = {ref: xx}
  if (el.ref) {
    data += `ref:${el.ref}, `
  }
  // The node with the ref attribute is inside the node with the V-for directive, data = {refInFor: true}
  if (el.refInFor) {
    data += `refInFor:true,`
  }
  // pre, v-pre directive, data = {pre: true}
  if (el.pre) {
    data += `pre:true,`
  }
  // Dynamic component, data = {tag: 'component'}
  // record original tag name for components using "is" attribute
  if (el.component) {
    data += `tag:"${el.tag}", `
  }
  // Execute the genData method of the module (class, style) for the node,
  Data = {staticClass: xx, class: xx, staticStyle: xx, style: xx}
  // module data generation functions
  for (let i = 0; i < state.dataGenFns.length; i++) {
    data += state.dataGenFns[i](el)
  }
  // Other attributes, get data = {attrs: static attribute string} or
  // data = {attrs: '_d(static attribute string, dynamic attribute string)'}
  // attributes
  if (el.attrs) {
    data += `attrs:${genProps(el.attrs)}, `
  }
  // DOM props, same as el.attrs
  if (el.props) {
    data += `domProps:${genProps(el.props)}, `
  }
  ${eventName}:handleCode '} or {' on_d(${eventName}:handleCode ', '${eventName},handleCode')}
  // event handlers
  if (el.events) {
    data += `${genHandlers(el.events, false)}, `
  }
  // Events with.native modifier,
  // data = { `nativeOn${eventName}:handleCode` } 或者 { `nativeOn_d(${eventName}:handleCode`, `${eventName},handleCode`) }
  if (el.nativeEvents) {
    data += `${genHandlers(el.nativeEvents, true)}, `
  }
  Data = {slot: slotName}
  // slot target
  // only for non-scoped slots
  if(el.slotTarget && ! el.slotScope) { data +=`slot:${el.slotTarget}, `
  }
  // Scoped slots, data = {scoped slots: '_u(XXX)'}
  if (el.scopedSlots) {
    data += `${genScopedSlots(el, el.scopedSlots, state)}, `
  }
  // Process the v-Model attribute to get
  // data = { model: { value, callback, expression } }
  // component v-model
  if (el.model) {
    data += `model:{value:${el.model.value },callback:${el.model.callback },expression:${el.model.expression }}, `
  }
  // Inline-template, handles the inline template, and gets
  // data = {inlineTemplate: {render: function() {render: function}, staticRenderFns: [function() {},...] }}
  if (el.inlineTemplate) {
    const inlineTemplate = genInlineTemplate(el, state)
    if (inlineTemplate) {
      data += `${inlineTemplate}, `}}// Delete the comma at the end of the JSON string and add the closing parentheses}
  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) {
    // Dynamic attributes exist, data = '_b(data, tag, static attribute string or _d(static attribute string, dynamic attribute string))'
    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

genDirectives

/src/compiler/codegen/index.js

Reading advice: This section can also be put behind other methods if you want to delve deeper into how the V-Model is implemented

/** * Runs the compilation method of directives and returns directives if there is a runtime task: [{name, rawName, value, arg, modifiers},...}] */
function genDirectives(el: ASTElement, state: CodegenState) :string | void {
  // Get the instruction array
  const dirs = el.directives
  // If there is no command, it ends directly
  if(! dirs)return
  // The result of processing the instruction
  let res = 'directives:['
  // Tag, which indicates whether the directive needs to complete tasks at runtime, such as input events for the V-Model
  let hasRuntime = false
  let i, l, dir, needRuntime
  // Iterate over the instruction array
  for (i = 0, l = dirs.length; i < l; i++) {
    dir = dirs[i]
    needRuntime = true
    // Get the processing method of the node's current instruction, such as the Web platform V-HTML, V-Text, V-Model
    const gen: DirectiveFunction = state.directives[dir.name]
    if (gen) {
      // Execute the compilation method of the instruction, returning true if the instruction still needs to perform some part of the runtime task, such as v-model
      // compile-time directive that manipulates AST.
      // returns true if it also needs a runtime counterpart.needRuntime = !! gen(el, dir, state.warn) }if (needRuntime) {
      // indicates that the directive has a task at runtime
      hasRuntime = true
      // res = directives:[{ name, rawName, value, arg, modifiers }, ...]
      res += `{name:"${dir.name}",rawName:"${dir.rawName}"${dir.value ? `,value:(${dir.value}),expression:The ${JSON.stringify(dir.value)}` : ' '
        }${dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ' '
        }${dir.modifiers ? `,modifiers:The ${JSON.stringify(dir.modifiers)}` : ' '
        }}, `}}if (hasRuntime) {
    // That is, res is returned only if the instruction exists in a runtime task
    return res.slice(0, -1) + '] '}}Copy the code

genProps

/src/compiler/codegen/index.js

/** * Iterates through the attribute array props to get a string of all the attributes * if no dynamic attribute exists, return: * 'attrName,attrVal,... '* if dynamic attributes exist, return: * '_d(static attribute string, dynamic attribute string)' */
function genProps(props: Array<ASTAttr>) :string {
  // Static properties
  let staticProps = ` `
  // Dynamic properties
  let dynamicProps = ` `
  // Iterate over the attribute 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 comma at the end of the static attribute
  staticProps = ` {${staticProps.slice(0, -1)}} `
  if (dynamicProps) {
    // If dynamic properties exist, return:
    // _d(static attribute string, dynamic attribute string)
    return `_d(${staticProps}[${dynamicProps.slice(0, -1)}]) `
  } else {
    // Return a string of static attributes
    return staticProps
  }
}

Copy the code

genHandlers

/src/compiler/codegen/events.js

/ * * * to generate a custom event dynamic code * : 'nativeOn | on_d (staticHandlers, [dynamicHandlers])' * static: ` nativeOn | on ${staticHandlers} ` * /
 export function genHandlers (events: ASTElementHandlers, isNative: boolean) :string {
  // Native: nativeOn, otherwise on
  const prefix = isNative ? 'nativeOn:' : 'on:'
  / / static
  let staticHandlers = ` `
  / / dynamic
  let dynamicHandlers = ` `
  // Iterate through the Events array
  // events = [{name: {value: callback,...}}]
  for (const name in events) {
    // Get the name of the callback function for the specified event, i.e. This. methodName or [this.methodName1,...
    const handlerCode = genHandler(events[name])
    if (events[name] && events[name].dynamic) {
      // Handles = 'eventName,handleCode,... , `
      dynamicHandlers += `${name}.${handlerCode}, `
    } else {
      StaticHandles = '"eventName":handleCode,'
      staticHandlers += `"${name}":${handlerCode}, `}}// Remove the trailing comma
  staticHandlers = ` {${staticHandlers.slice(0, -1)}} `
  if (dynamicHandlers) {
    // dynamic, on_d(statickHandles, [dynamicHandlers])
    return prefix + `_d(${staticHandlers}[${dynamicHandlers.slice(0, -1)}]) `
  } else {
    // Static, 'on${staticHandlers}'
    return prefix + staticHandlers
  }
}

Copy the code

genStatic

/src/compiler/codegen/index.js

/** * generate static node render function * 1, put the current static node render function in the staticRenderFns array * 2, return an executable function _m(idx, true or "") */
// 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
  }
  // Push the rendering function of the static root node into the staticRenderFns array, such as:
  // [`with(this){return _c(tag, data, children)}`]
  state.staticRenderFns.push(`with(this){return ${genElement(el, state)}} `)
  state.pre = originalPreState
  Return an executable function: _m(idx, true or "")
  // idx = The rendering function of the current static node is subscripted in the staticRenderFns array
  return `_m(${state.staticRenderFns.length - 1
    }${el.staticInFor ? ',true' : ' '
    }) `
}

Copy the code

genOnce

/src/compiler/codegen/index.js

/** * process a node with a v-if instruction, and there are three results: * 1, the current node has a V-if instruction, and a ternary expression, condition? render1 : Render2 * 2, the current node is a static node contained inside the V-for directive, resulting in _o(_C (tag, data, children), number, key) * 3, the current node is a pure V-once node. _m(idx, true of "") */
function genOnce(el: ASTElement, state: CodegenState) :string {
  // Indicates that the current node's V-once directive has been processed
  el.onceProcessed = true
  if(el.if && ! el.ifProcessed) {// If there are v-if directives && if directives that have not been processed, go here
    // Process the node with the v-if instruction, resulting in a ternary expression, condition? render1 : render2
    return genIf(el, state)
  } else if (el.staticInFor) {
    // Note that the current node is a static node wrapped inside a node with the V-for directive
    // Get the key for the V-for instruction
    let key = ' '
    let parent = el.parent
    while (parent) {
      if (parent.for) {
        key = parent.key
        break
      }
      parent = parent.parent
    }
    The v-once node can only be used inside a V-for node with a key
    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 '_o(_c(tag, data, children), number, key)'
    return `_o(${genElement(el, state)}.${state.onceId++}.${key}) `
  } else {
    // If none of the above is true, it is a simple static node, just like the static root node.
    // get _m(idx, true or "")
    return genStatic(el, state)
  }
}

Copy the code

genFor

/src/compiler/codegen/index.js

/** * Process the v-for directive on the node * to get '_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})' */
export function genFor(el: any, state: CodegenState, altGen? :Function, altHelper? : string) :string {
  // A v-for iterator, such as an array
  const exp = el.for
  // The alias for iteration
  const alias = el.alias
  // if iterator is v-for = "(item,idx) in obj", for example, iterator1 = idx
  const iterator1 = el.iterator1 ? `,${el.iterator1}` : ' '
  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ' '

  // Note that the v-for directive must use the key on the 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 directive on the current node has been processed
  el.forProcessed = true // avoid r
  Function (exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})
  return `${altHelper || '_l'}((${exp}), ` +
    `function(${alias}${iterator1}${iterator2}) {` +
    `return ${(altGen || genElement)(el, state)}` +
    '}) '
}

Copy the code

genIf

/src/compiler/codegen/index.js

/** * process the node with the v-if directive, resulting in a ternary expression, condition? render1 : render2 */
export function genIf(el: any, state: CodegenState, altGen? :Function, altEmpty? : string) :string {
  // Indicates that the current node's v-if directive has been processed to avoid invalid recursion
  el.ifProcessed = true // avoid recursion
  // Obtain 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 {
  // If the length is null, return an empty node rendering function
  if(! conditions.length) {return altEmpty || '_e()'
  }

  // Take the first condition object from the conditions array {exp, block}
  const condition = conditions.shift()
  // Return a ternary expression string, condition? Render function 1: Render function 2
  if (condition.exp) {
    // If the condition.exp condition is true, then we get a teradata expression,
    // If the conditions are not valid, we recursively search for the next element in the conditions array.
    // Return a ternary expression until the condition is found
    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

genSlot

/src/compiler/codegen/index.js

/** * generate the render function for the slot and get * _t(slotName, children, attrs, bind) */
function genSlot(el: ASTElement, state: CodegenState) :string {
  // Slot name
  const slotName = el.slotName || '"default"'
  // Generate all child nodes
  const children = genChildren(el, state)
  // Result string, _t(slotName, children, attrs, bind)
  let res = `_t(${slotName}${children ? `,${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

genComponent

/src/compiler/codegen/index.js

// componentName is el.component, take it as argument to shun flow's pessimistic refinement
/** * The rendering function that generates dynamic components * returns' _c(compName, data, children) '*/
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

conclusion

  • Interviewer: What does Vue’s compiler do briefly?

    A:

    Vue’s compiler does three things:

    • Parse the component’s HTML template into an AST object

    • Optimization, traversing the AST, making static tags for each node, marking whether it is static node or not, and then further marking the static root node, so that these static nodes can be skipped in the process of subsequent updates; The tag static root is used in the render function generation phase to generate the render function for the static root node

    • The staticRenderFns array contains all static node render functions


  • Interviewer: Tell me more about the rendering function generation process

    A:

    There are two types of render generated by the compiler:

    • The first is a render function that generates a vNode with dynamic nodes

    • The second is static rendering functions placed in an array called staticRenderFns, which are responsible for generating vNodes with static nodes

    The rendering function generation process is actually traversing the AST nodes, processing each node through a recursive way, and finally generating the result of the form: _c(Tag, attr, children, normalizationType). Tag is the tag name, attr is the attribute object, children is an array of children, each of which is of the form _c(Tag, attr, children, normalizationTYpe). Normalization means the normalized type of the node, which is a number 0, 1, 2, and is not important.

    In the process of dealing with AST nodes, we need to focus on the common questions in the interview:

    • What about static nodes

      The processing of static nodes is divided into two steps:

      • Put the generating static node vnode function in the staticRenderFns array

      • Return an executable function _m(idx) that executes the function of staticRenderFns with subscript IDx, generating a vnode with static nodes

    • How to deal with v-ONCE, V-IF, V-for, components, etc

      • The processing mode of pure V-Once nodes is the same as that of static nodes

      • The result of processing the V-if node is a ternary expression

      • The result of the processing of the V-for node is the executable _L function, which is responsible for generating the VNode of the V-for node

      • The result of component processing is the same as that of ordinary elements. The result is executable code of the form _c(compName), which generates vNode of the component


At this point, the Vue compiler’s interpretation of the source code ends. I believe you will inevitably have a feeling in the process of reading. That’s okay. The compiler is the most complicated part of the framework, the most difficult part to understand and the most code. Be sure to calm down to read a few more times, encounter can not understand the place, be sure to work hard, through example code and breakpoint debugging way to help yourself understand.

After you’ve read it a few times, you’ll probably get better, but you’ll still get a little dizzy in some places, and that’s fine, that’s normal. After all, this is a framework compiler that handles a lot of stuff, and you just need to understand the core ideas (template parsing, static markup, code generation). A hand-written Vue series will follow, and a simplified implementation will be available in the compiler section to help deepen the understanding.

When the compiler reads through it, it will find that the code generated by the compiler is wrapped with, such as:

<div id="app">
  <div v-for="item in arr" :key="item">{{ item }}</div>
</div>
Copy the code

After compilation, generate:

with (this) {
  return _c(
    'div',
    {
      attrs:
      {
        "id": "app"
      }
    },
    _l(
      (arr),
      function (item) {
        return _c(
          'div',
          {
            key: item
          },
          [_v(_s(item))]
        )
      }
    ),
    0)}Copy the code

As we all know, the with statement extends the scope chain, so the _c, _L, _v, and _s in the generated code are methods on this, meaning that when executed at run time, these methods will generate vnodes for each node.

Therefore, in connection with the previous knowledge, the entire implementation process of responsive data update is:

  • Responsively intercepts updates to the data

  • The DEP informs Watcher to make an asynchronous update

  • The component update function updateComponent is executed when Watcher updates

  • The vnode of the vm._render generated component is executed first, which executes the compiler generated function

  • Question:

    • What are the _c, _l, _v, _s methods in the render function?

    • How do they generate vNodes?

The next article Vue source code Interpretation (11) — Render Helper will bring a detailed interpretation of this knowledge, which is often asked in interviews: for example: what is the principle of V-for?

Form a complete set of video

Vue source code interpretation (10) — compiler generation rendering function

Please focus on

Welcome everyone to follow my gold mining account and B station, if the content has to help you, welcome everyone to like, collect + attention

link

  • Vue source code interpretation (1) – preface

  • Vue source code interpretation (2) — Vue initialization process

  • Vue source code interpretation (3) – response principle

  • Vue source code interpretation (4) — asynchronous update

  • Vue source code Interpretation (5) — Global API

  • Vue source code interpretation (6) — instance method

  • (7) — Hook Event

  • Vue source code interpretation (8) — compiler parsing

  • Vue source code interpretation (9) — compiler optimization

  • Vue source code interpretation (10) — compiler generation rendering function

  • (11) — Render helper

  • Vue source code interpretation (12) — patch

Learning exchange group

link