【Vue源码-编译】编译之codegen

242 阅读1分钟

vue编译过程经过了parse以及optimize的过程后,就是对ast进行codegen过程了 看大佬的文章# Vue模板编译原理 由于这篇文章对codegen过程没有详细阐述,那么。。我就结合一个例子来阐述一下

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

最终生成的render函数为

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()
}

ok 正式开始单步过程,ast这里也不关心,其实我自己觉得把ast列出了看到那么多属性也没用,还不如理解为解析过程中进啥分支就知道有啥属性:

generate 入口

export function generate (
  ast: ASTElement | void,
  options: CompilerOptions
): CodegenResult {
  const state = new CodegenState(options)
  // genElement核心 进!
  const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")'
  return {
    render: `with(this){return ${code}}`,
    staticRenderFns: state.staticRenderFns
  }
}

genElement 核心

/*处理element,分别处理static静态节点、v-once、v-for、v-if、template、slot以及组件或元素*/
function genElement (el: ASTElement): string {
  if (el.staticRoot && !el.staticProcessed) {
    /*处理static静态节点*/
    return genStatic(el)
  } else if (el.once && !el.onceProcessed) {
    /*处理v-once*/
    return genOnce(el)
  } else if (el.for && !el.forProcessed) {
    /*处理v-for*/
    return genFor(el)
  } else if (el.if && !el.ifProcessed) {
    /*处理v-if*/
    return genIf(el)
  } else if (el.tag === 'template' && !el.slotTarget) {
    /*处理template*/
    return genChildren(el) || 'void 0'
  } else if (el.tag === 'slot') {
    /*处理slot*/
    return genSlot(el)
  } else {
    // component or element
    /*处理组件或元素*/
    let code
    if (el.component) {
      code = genComponent(el.component, el)
    } else {
      const data = el.plain ? undefined : genData(el)

      const children = el.inlineTemplate ? null : genChildren(el, true)
      code = `_c('${el.tag}'${
        data ? `,${data}` : '' // data
      }${
        children ? `,${children}` : '' // children
      })`
    }
    // module transforms
    for (let i = 0; i < transforms.length; i++) {
      code = transforms[i](el, code)
    }
    return code
  }
}

这里进入处理v-if的分支genIf

v-if

function genIf (el: any): string {
  /*标记位*/
  // 这里标记el节点已经处理过了if,防止递归调用genElement
  // 时进入死循环
  el.ifProcessed = true // avoid recursion
  return genIfConditions(el.ifConditions.slice())
}

/*处理if条件*/
function genIfConditions (conditions: ASTIfConditions): string {
  /* 表达式不存在 也就是 v-if="" */
  if (!conditions.length) {
    return '_e()'
  }

  const condition = conditions.shift()
  if (condition.exp) {
  // 重点是这的三目表达式
    return `(${condition.exp})?${genTernaryExp(condition.block)}:${genIfConditions(conditions)}`
  } else {
    return `${genTernaryExp(condition.block)}`
  }
  
  function genTernaryExp (el) {
    return el.once ? genOnce(el) : genElement(el)
  }

这里核心是三目表达式,但是在生成表达式之前递归调用了genElement,这次调用因为v-if已经打上了标记,不会重复处理v-if,于是就会一路走到 else分支genData中

genData

genData生成一个json字符串

function genData (el: ASTElement): string {
  let data = '{'

  // directives first.
  // directives may mutate the el's other properties before they are generated.
  const dirs = genDirectives(el)
  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}",`
  }
  // module data generation functions
  for (let i = 0; i < dataGenFns.length; i++) {
    data += dataGenFns[i](el)
  }
  // attributes
  if (el.attrs) {
    data += `attrs:{${genProps(el.attrs)}},`
  }
  // DOM props
  if (el.props) {
    data += `domProps:{${genProps(el.props)}},`
  }
  // event handlers
  if (el.events) {
    data += `${genHandlers(el.events, false, warn)},`
  }
  if (el.nativeEvents) {
    data += `${genHandlers(el.nativeEvents, true, warn)},`
  }
  // slot target
  if (el.slotTarget) {
    data += `slot:${el.slotTarget},`
  }
  // scoped slots
  if (el.scopedSlots) {
    data += `${genScopedSlots(el.scopedSlots)},`
  }
  // 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)
    if (inlineTemplate) {
      data += `${inlineTemplate},`
    }
  }
  data = data.replace(/,$/, '') + '}'
  // v-bind data wrap
  if (el.wrapData) {
    data = el.wrapData(data)
  }
  return data
}

在这个case中,其他的不会命中,直接进入循环,遍历平台下每个dataGenFuns,拼接到data串中

for (let i = 0; i < dataGenFns.length; i++) { 
    data += dataGenFns[i](el) 
}

对于web平台,只有两个dataGenFuns(weex平台暂且不表):

image.png

来进去康康:

class.js中的genData

function genData (el: ASTElement): string {
  let data = ''
  if (el.staticClass) {
  // class = "foo"
    data += `staticClass:${el.staticClass},`
  }
  if (el.classBinding) {
  // :class = "barrrrr"
    data += `class:${el.classBinding},`
  }
  return data
}

style.js 中的genData

// 一样的道理
function genData (el: ASTElement): string {
  let data = ''
  if (el.staticStyle) {
    data += `staticStyle:${el.staticStyle},`
  }
  if (el.styleBinding) {
    data += `style:(${el.styleBinding}),`
  }
  return data
}

回到index.js 的genData:没有其他命中的分支了 这里直接data = data.replace(/,$/, '') + '}' 一个正则匹配到结尾的逗号,然后replace到封口的花括号结束返回data"{staticClass:"list",class:bindCls}"

调用栈回到了genElement,处理好了data,现在处理子节点 genChildren

genChildren

/*处理chidren*/
function genChildren (el: ASTElement, checkSkip?: boolean): string | void {
  const children = el.children
  if (children.length) {
    const el: any = children[0]
    // optimize single v-for
    /*优化单个v-for*/
    if (children.length === 1 &&
        el.for &&
        el.tag !== 'template' &&
        el.tag !== 'slot') {
      return genElement(el)
    }
    const normalizationType = checkSkip ? getNormalizationType(children) : 0
    /*用genNode处理children,内部有子节点也会继续遍历*/
    return `[${children.map(genNode).join(',')}]${
      normalizationType ? `,${normalizationType}` : ''
    }`
  }
}

/*处理节点*/
function genNode (node: ASTNode): string {
  if (node.type === 1) {
    return genElement(node)
  } else {
    return genText(node)
  }
}

这里会触发一个v-for的性能优化当只有一个子节点并且是v-for节点的时候,非template或者slot 直接调用genElement,好的,又回到了genElement。在这次调用中,会触发v-for的处理分支genFor

genFor

/*处理v-for循环*/
function genFor (el: any): string {
  const exp = el.for  // exp: "data"
  const alias = el.alias  // alias: "item"
  const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''

  if (
    process.env.NODE_ENV !== 'production' &&
    maybeComponent(el) && el.tag !== 'slot' && el.tag !== 'template' && !el.key
  ) {
    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.`,
      true /* tip */
    )
  }

  /*标记位,避免递归*/
  el.forProcessed = true // avoid recursion
  
  // 还是把自己传入genElement 但是因为 v-for已经被标记过
  // 因此会继续往下走,这里会走到genData
  return `_l((${exp}),` +
    `function(${alias}${iterator1}${iterator2}){` +
      `return ${genElement(el)}` +
    '})'
}

这里因为再次genElement调用了自己,v-for已经处理过,这里会直接走到genData,在genData中,处理了@click 也就是el.events分支调用 genHandlers 过于复杂日后再说,这里只是说返回了data = "{on:{"click":function($event){return clickItem(index)}}}"

到这里 code已经成为了 "_c('li',{on:{"click":function($event){return clickItem(index)}}},[_v(_s(item)+":"+_s(index))])"

return 回去,还记得吗,回到了 genFor return 回到genChildren return 回到了 genElement(处理ul v-for那个)

_c(
  'ul',
  { staticClass: 'list', class: bindCls },
  _l(data, function (item, index) {
    return _c(
      'li',
      {
        on: {
          click: function ($event) {
            return clickItem(index)
          }
        }
      },
      [_v(_s(item) + ':' + _s(index))]
    )
  }),
  0
)

其实就是,_c函数包裹,第一个参数是tag,第二个参数是genData,第三个参数是v-for函数:_l(迭代器,function(你写的alise) return _c(迭代元素,tag,data,children)

回到最外层的genIf 三目运算符套上去,完工!

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