编译的最后一步就是把优化后的 AST 树转换成可执行的代码

例子

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

经过编译,执行 const code = generate(ast, options),生成的 render 代码串如下

  1. with(this){
  2. return (isShow) ?
  3. _c('ul', {
  4. staticClass: "list",
  5. class: bindCls
  6. },
  7. _l((data), function(item, index) {
  8. return _c('li', {
  9. on: {
  10. "click": function($event) {
  11. clickItem(index)
  12. }
  13. }
  14. },
  15. [_v(_s(item) + ":" + _s(index))])
  16. })
  17. ) : _e()
  18. }

_l、_v这类方法定义在src/core/instance/render-helpers/index.js中

  1. // _c就是执行createElement去创建VNode
  2. export function installRenderHelpers (target: any) {
  3. target._o = markOnce
  4. target._n = toNumber
  5. target._s = toString
  6. target._l = renderList // 渲染列表
  7. target._t = renderSlot
  8. target._q = looseEqual
  9. target._i = looseIndexOf
  10. target._m = renderStatic
  11. target._f = resolveFilter
  12. target._k = checkKeyCodes
  13. target._b = bindObjectProps
  14. target._v = createTextVNode // 创建文本VNode
  15. target._e = createEmptyVNode // 创建空的VNode
  16. target._u = resolveScopedSlots
  17. target._g = bindObjectListeners
  18. target._d = bindDynamicKeys
  19. target._p = prependModifier
  20. }

在compileToFunctions中会把这个render字符串转换成函数、
定义在src/compiler/to-function.js中

  1. // compile
  2. const compiled = compile(template, options)
  3. // turn code into functions
  4. const res = {}
  5. res.render = createFunction(compiled.render, fnGenErrors)
  6. function createFunction (code, errors) {
  7. try {
  8. return new Function(code)
  9. } catch (err) {
  10. errors.push({ err, code })
  11. return noop
  12. }
  13. }

实际上就是把 render 代码串通过 new Function 的方式转换成可执行的函数,赋值给 vm.options.render,这样当组件通过 vm._render 的时候,就会执行这个 render 函数

generate

  1. const code = generate(ast, options)

generate函数定义在src/compiler/codegen/index.js中

  1. export function generate (
  2. ast: ASTElement | void,
  3. options: CompilerOptions
  4. ): CodegenResult {
  5. const state = new CodegenState(options)
  6. // fix #11483, Root level <script> tags should not be rendered.
  7. const code = ast ?
  8. (ast.tag === 'script' ? 'null' : genElement(ast, state)) // genElement生成code
  9. :
  10. '_c("div")'
  11. return {
  12. render: `with(this){return ${code}}`, // 把code包裹起来
  13. staticRenderFns: state.staticRenderFns
  14. }
  15. }

genElement判断当前AST元素节点的属性执行不同的代码生成函数

  1. export function genElement (el: ASTElement, state: CodegenState): string {
  2. if (el.parent) {
  3. el.pre = el.pre || el.parent.pre
  4. }
  5. if (el.staticRoot && !el.staticProcessed) {
  6. return genStatic(el, state)
  7. } else if (el.once && !el.onceProcessed) {
  8. return genOnce(el, state)
  9. } else if (el.for && !el.forProcessed) {
  10. return genFor(el, state)
  11. } else if (el.if && !el.ifProcessed) {
  12. return genIf(el, state)
  13. } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
  14. return genChildren(el, state) || 'void 0'
  15. } else if (el.tag === 'slot') {
  16. return genSlot(el, state)
  17. } else {
  18. // component or element
  19. let code
  20. if (el.component) {
  21. code = genComponent(el.component, el, state)
  22. } else {
  23. let data
  24. if (!el.plain || (el.pre && state.maybeComponent(el))) {
  25. data = genData(el, state)
  26. }
  27. const children = el.inlineTemplate ? null : genChildren(el, state, true)
  28. code = `_c('${el.tag}'${
  29. data ? `,${data}` : '' // data
  30. }${
  31. children ? `,${children}` : '' // children
  32. })`
  33. }
  34. // module transforms
  35. for (let i = 0; i < state.transforms.length; i++) {
  36. code = state.transforms[i](el, code)
  37. }
  38. return code
  39. }
  40. }

genIf

  1. export function genIf (
  2. el: any,
  3. state: CodegenState,
  4. altGen?: Function,
  5. altEmpty?: string
  6. ): string {
  7. el.ifProcessed = true // avoid recursion
  8. return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
  9. }
  10. function genIfConditions (
  11. conditions: ASTIfConditions,
  12. state: CodegenState,
  13. altGen?: Function,
  14. altEmpty?: string
  15. ): string {
  16. if (!conditions.length) {
  17. return altEmpty || '_e()'
  18. }
  19. // 依次从conditions获取第一个condition
  20. const condition = conditions.shift()
  21. // 生成一段三元运算符的代码
  22. if (condition.exp) {
  23. return `(${condition.exp})?${
  24. genTernaryExp(condition.block)
  25. }:${
  26. genIfConditions(conditions, state, altGen, altEmpty) // 递归调用自身 如果有多个conditions时就生成多层三元运算逻辑
  27. }`
  28. } else {
  29. return `${genTernaryExp(condition.block)}`
  30. }
  31. // v-if with v-once should generate code like (a)?_m(0):_m(1)
  32. function genTernaryExp (el) {
  33. return altGen
  34. ? altGen(el, state)
  35. : el.once
  36. ? genOnce(el, state)
  37. : genElement(el, state) // 不考虑v-once时最终调用了genElement
  38. }
  39. }

在例子中只有一个condition,exp为isShow,生成代码如下

  1. return (isShow) ? genElement(el, state) : _e()

genFor

  1. export function genFor (
  2. el: any,
  3. state: CodegenState,
  4. altGen?: Function,
  5. altHelper?: string
  6. ): string {
  7. // AST元素节点中获取和for相关的一些属性
  8. const exp = el.for
  9. const alias = el.alias
  10. const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
  11. const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
  12. if (process.env.NODE_ENV !== 'production' &&
  13. state.maybeComponent(el) &&
  14. el.tag !== 'slot' &&
  15. el.tag !== 'template' &&
  16. !el.key
  17. ) {
  18. state.warn(
  19. `<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
  20. `v-for should have explicit keys. ` +
  21. `See https://vuejs.org/guide/list.html#key for more info.`,
  22. el.rawAttrsMap['v-for'],
  23. true /* tip */
  24. )
  25. }
  26. el.forProcessed = true // avoid recursion
  27. // 返回代码字符串
  28. return `${altHelper || '_l'}((${exp}),` +
  29. `function(${alias}${iterator1}${iterator2}){` +
  30. `return ${(altGen || genElement)(el, state)}` +
  31. '})'
  32. }

在例子中exp是data,alias是item Iterator1是index,生成代码如下

  1. _l((data), function(item, index) {
  2. return genElememt(el, state)
  3. })

genData & genChildren

回顾例子,它的最外层是 ul,首先执行 genIf,它最终调用了 genElement(el, state) 去生成子节点,注意,这里的 el 仍然指向的是 ul 对应的 AST 节点,但是此时的 el.ifProcessed 为 true,所以命中最后一个 else 逻辑

  1. // component or element
  2. let code
  3. if (el.component) {
  4. code = genComponent(el.component, el, state)
  5. } else {
  6. let data
  7. if (!el.plain || (el.pre && state.maybeComponent(el))) {
  8. data = genData(el, state)
  9. }
  10. const children = el.inlineTemplate ? null : genChildren(el, state, true)
  11. code = `_c('${el.tag}'${
  12. data ? `,${data}` : '' // data
  13. }${
  14. children ? `,${children}` : '' // children
  15. })`
  16. }
  17. // module transforms
  18. for (let i = 0; i < state.transforms.length; i++) {
  19. code = state.transforms[i](el, code)
  20. }
  21. return code

genData

根据AST元素节点的属性构造出一个data字符串,在创建VNode时会作为参数传入

  1. export function genData (el: ASTElement, state: CodegenState): string {
  2. let data = '{'
  3. // directives first.
  4. // directives may mutate the el's other properties before they are generated.
  5. const dirs = genDirectives(el, state)
  6. if (dirs) data += dirs + ','
  7. // key
  8. if (el.key) {
  9. data += `key:${el.key},`
  10. }
  11. // ref
  12. if (el.ref) {
  13. data += `ref:${el.ref},`
  14. }
  15. if (el.refInFor) {
  16. data += `refInFor:true,`
  17. }
  18. // pre
  19. if (el.pre) {
  20. data += `pre:true,`
  21. }
  22. // record original tag name for components using "is" attribute
  23. if (el.component) {
  24. data += `tag:"${el.tag}",`
  25. }
  26. // module data generation functions
  27. // state是CodegenState的实例
  28. for (let i = 0; i < state.dataGenFns.length; i++) {
  29. data += state.dataGenFns[i](el)
  30. }
  31. // attributes
  32. if (el.attrs) {
  33. data += `attrs:${genProps(el.attrs)},`
  34. }
  35. // DOM props
  36. if (el.props) {
  37. data += `domProps:${genProps(el.props)},`
  38. }
  39. // event handlers
  40. if (el.events) {
  41. data += `${genHandlers(el.events, false)},`
  42. }
  43. if (el.nativeEvents) {
  44. data += `${genHandlers(el.nativeEvents, true)},`
  45. }
  46. // slot target
  47. // only for non-scoped slots
  48. if (el.slotTarget && !el.slotScope) {
  49. data += `slot:${el.slotTarget},`
  50. }
  51. // scoped slots
  52. if (el.scopedSlots) {
  53. data += `${genScopedSlots(el, el.scopedSlots, state)},`
  54. }
  55. // component v-model
  56. if (el.model) {
  57. data += `model:{value:${
  58. el.model.value
  59. },callback:${
  60. el.model.callback
  61. },expression:${
  62. el.model.expression
  63. }},`
  64. }
  65. // inline-template
  66. if (el.inlineTemplate) {
  67. const inlineTemplate = genInlineTemplate(el, state)
  68. if (inlineTemplate) {
  69. data += `${inlineTemplate},`
  70. }
  71. }
  72. data = data.replace(/,$/, '') + '}'
  73. // v-bind dynamic argument wrap
  74. // v-bind with dynamic arguments must be applied using the same v-bind object
  75. // merge helper so that class/style/mustUseProp attrs are handled correctly.
  76. if (el.dynamicAttrs) {
  77. data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)})`
  78. }
  79. // v-bind data wrap
  80. if (el.wrapData) {
  81. data = el.wrapData(data)
  82. }
  83. // v-on data wrap
  84. if (el.wrapListeners) {
  85. data = el.wrapListeners(data)
  86. }
  87. return data
  88. }

state.dataGenFns 的初始化在它的构造器中

  1. export class CodegenState {
  2. constructor (options: CompilerOptions) {
  3. // ...
  4. this.dataGenFns = pluckModuleFunction(options.modules, 'genData')
  5. // ...
  6. }
  7. }

实际上就是获取所有 modules 中的 genData 函数,其中,class module 和 style module 定义了 genData 函数
genData方法定义在src/platforms/web/compiler/modules/class.js中

  1. function genData (el: ASTElement): string {
  2. let data = ''
  3. if (el.staticClass) {
  4. data += `staticClass:${el.staticClass},`
  5. }
  6. if (el.classBinding) {
  7. data += `class:${el.classBinding},`
  8. }
  9. return data
  10. }

在例子中ul AST元素节点定义了el.staticClass和el.classBinding,因此最终生成的data字符串如下

  1. {
  2. staticClass: "list",
  3. class: bindCls
  4. }

genChildren

  1. export function genChildren (
  2. el: ASTElement,
  3. state: CodegenState,
  4. checkSkip?: boolean,
  5. altGenElement?: Function,
  6. altGenNode?: Function
  7. ): string | void {
  8. const children = el.children
  9. if (children.length) {
  10. const el: any = children[0]
  11. // optimize single v-for
  12. if (children.length === 1 &&
  13. el.for &&
  14. el.tag !== 'template' &&
  15. el.tag !== 'slot'
  16. ) {
  17. const normalizationType = checkSkip
  18. ? state.maybeComponent(el) ? `,1` : `,0`
  19. : ``
  20. return `${(altGenElement || genElement)(el, state)}${normalizationType}`
  21. }
  22. const normalizationType = checkSkip
  23. ? getNormalizationType(children, state.maybeComponent)
  24. : 0
  25. const gen = altGenNode || genNode
  26. return `[${children.map(c => gen(c, state)).join(',')}]${
  27. normalizationType ? `,${normalizationType}` : ''
  28. }`
  29. }
  30. }

在例子中,li AST 元素节点是 ul AST 元素节点的 children 之一,满足 (children.length === 1 && el.for && el.tag !== ‘template’ && el.tag !== ‘slot’) 条件,因此通过 genElement(el, state) 生成 li AST元素节点的代码,也就回到了之前调用 genFor 生成的代码,把它们拼在一起生成的伪代码如下

  1. return (isShow) ?
  2. _c('ul', {
  3. staticClass: "list",
  4. class: bindCls
  5. },
  6. _l((data), function(item, index) {
  7. return genElememt(el, state)
  8. })
  9. ) : _e()

在例子中,在执行 genElememt(el, state) 的时候,el 还是 li AST 元素节点,el.forProcessed 已为 true,所以会继续执行 genData 和 genChildren 的逻辑。由于 el.events 不为空,在执行 genData 的时候,会执行 如下逻辑

  1. // event handlers
  2. if (el.events) {
  3. data += `${genHandlers(el.events, false)},`
  4. }

genHandlers 的定义在 src/compiler/codegen/events.js 中

  1. export function genHandlers (
  2. events: ASTElementHandlers,
  3. isNative: boolean
  4. ): string {
  5. const prefix = isNative ? 'nativeOn:' : 'on:'
  6. let staticHandlers = ``
  7. let dynamicHandlers = ``
  8. for (const name in events) {
  9. const handlerCode = genHandler(events[name])
  10. if (events[name] && events[name].dynamic) {
  11. dynamicHandlers += `${name},${handlerCode},`
  12. } else {
  13. staticHandlers += `"${name}":${handlerCode},`
  14. }
  15. }
  16. staticHandlers = `{${staticHandlers.slice(0, -1)}}`
  17. if (dynamicHandlers) {
  18. return prefix + `_d(${staticHandlers},[${dynamicHandlers.slice(0, -1)}])`
  19. } else {
  20. return prefix + staticHandlers
  21. }
  22. }

对于例子,它最终 genData 生成的 data 字符串如下

  1. {
  2. on: {
  3. "click": function($event) {
  4. clickItem(index)
  5. }
  6. }
  7. }

genChildren 的时候,会执行到如下逻辑

  1. export function genChildren (
  2. el: ASTElement,
  3. state: CodegenState,
  4. checkSkip?: boolean,
  5. altGenElement?: Function,
  6. altGenNode?: Function
  7. ): string | void {
  8. const children = el.children
  9. if (children.length) {
  10. const el: any = children[0]
  11. // optimize single v-for
  12. if (children.length === 1 &&
  13. el.for &&
  14. el.tag !== 'template' &&
  15. el.tag !== 'slot'
  16. ) {
  17. const normalizationType = checkSkip
  18. ? state.maybeComponent(el) ? `,1` : `,0`
  19. : ``
  20. return `${(altGenElement || genElement)(el, state)}${normalizationType}`
  21. }
  22. const normalizationType = checkSkip
  23. ? getNormalizationType(children, state.maybeComponent)
  24. : 0
  25. const gen = altGenNode || genNode
  26. return `[${children.map(c => gen(c, state)).join(',')}]${
  27. normalizationType ? `,${normalizationType}` : ''
  28. }`
  29. }
  30. }
  31. function genNode (node: ASTNode, state: CodegenState): string {
  32. if (node.type === 1) {
  33. return genElement(node, state)
  34. } else if (node.type === 3 && node.isComment) {
  35. return genComment(node)
  36. } else {
  37. return genText(node)
  38. }
  39. }

genChildren 的就是遍历 children,然后执行 genNode 方法,根据不同的 type 执行具体的方法
在例子中,li AST 元素节点的 children 是 type 为 2 的表达式 AST 元素节点,那么会执行到 genText(node) 逻辑

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

因此在例子中,genChildren 生成的代码串如下

  1. [_v(_s(item) + ":" + _s(index))]

和之前拼在一起,最终生成的 code 如下

  1. return (isShow) ?
  2. _c('ul', {
  3. staticClass: "list",
  4. class: bindCls
  5. },
  6. _l((data), function(item, index) {
  7. return _c('li', {
  8. on: {
  9. "click": function($event) {
  10. clickItem(index)
  11. }
  12. }
  13. },
  14. [_v(_s(item) + ":" + _s(index))])
  15. })
  16. ) : _e()