Vue源碼學習4.4:編譯codegen

編譯的最後一步就是把優化後的 AST 樹轉換成可執行的代碼,這部份內容也比較多,瞭解總體流程便可。部分細節咱們會在以後的章節配合一個具體 case 去詳細講。html

看這個例子:vue

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

它通過編譯,執行 const code = generate(ast, options),生成的 render 代碼串以下:node

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()
}
複製代碼

這裏的 _c 函數定義在 src/core/instance/render.js 中。web

vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
複製代碼

_e_l_v 定義在 src/core/instance/render-helpers/index.js 中:express

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
}
複製代碼

顧名思義,_c 就是執行 createElement 去建立 VNode,而 _l 對應 renderList 渲染列表;_v 對應 createTextVNode 建立文本 VNode_e 對於 createEmptyVNode建立空的 VNodebash

compileToFunctions 中,會把這個 render 代碼串轉換成函數,它的定義在 src/compler/to-function.js 中:app

const compiled = compile(template, options)
res.render = createFunction(compiled.render, fnGenErrors)

function createFunction (code, errors{
  try {
    return new Function(code)
  } catch (err) {
    errors.push({ err, code })
    return noop
  }
}
複製代碼

實際上就是把 render 代碼串經過 new Function 的方式轉換成可執行的函數,賦值給 vm.options.render,這樣當組件經過 vm._render 的時候,就會執行這個 render 函數。那麼接下來咱們就重點關注一下這個 render 代碼串的生成過程。dom

1. generate

const code = generate(ast, options)
複製代碼

generate 函數的定義在 src/compiler/codegen/index.js 中:編輯器

export function generate (
  ast: ASTElement | void,
  options: CompilerOptions
): CodegenResult 
{
  const state = new CodegenState(options)
  const code = ast ? genElement(ast, state) : '_c("div")'
  return {
    render: `with(this){return ${code}}`,
    staticRenderFns: state.staticRenderFns
  }
}
複製代碼

generate 函數首先經過 genElement(ast, state) 生成 code,再把 codewith(this){return ${code}}} 包裹起來。這裏的 stateCodegenState 的一個實例,稍後咱們在用到它的時候會介紹它。先來看一下 genElementide

export function genElement (el: ASTElement, state: CodegenState): string {
  if (el.parent) {
    el.pre = el.pre || el.parent.pre
  }

  if (el.staticRoot && !el.staticProcessed) {
    return genStatic(el, state)
  } else if (el.once && !el.onceProcessed) {
    return genOnce(el, state)
  } else if (el.for && !el.forProcessed) {
    return genFor(el, state)
  } else if (el.if && !el.ifProcessed) {
    return genIf(el, state)
  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
    return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
    return genSlot(el, state)
  } else {
    // component or element
    let code
    if (el.component) {
      code = genComponent(el.component, el, state)
    } else {
      let data
      if (!el.plain || (el.pre && state.maybeComponent(el))) {
        data = genData(el, state)
      }

      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
  }
}
複製代碼

基本就是判斷當前 AST 元素節點的屬性執行不一樣的代碼生成函數,在咱們的例子中,咱們先了解一下 genForgenIf

2. genIf

export function genIf (
  el: any,
  state: CodegenState,
  altGen?: Function,
  altEmpty?: string
): string 
{
  el.ifProcessed = true // avoid recursion
  return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}

function genIfConditions (
  conditions: ASTIfConditions,
  state: CodegenState,
  altGen?: Function,
  altEmpty?: string
): string 
{
  if (!conditions.length) {
    return altEmpty || '_e()'
  }

  const condition = conditions.shift()
  if (condition.exp) {
    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)
  }
}
複製代碼

genIf 主要是經過執行 genIfConditions,它是依次從 conditions 獲取第一個 condition,而後經過對 condition.exp 去生成一段三元運算符的代碼。

?後調用genTernaryExp,這裏不考慮 v-once 的狀況,最終是調用了 genElement

:後遞歸調用 genIfConditions,這樣若是有多個 conditions,就生成多層三元運算邏輯。

在咱們的例子中,只有一個 condition.expisShow,所以生成以下僞代碼:

return (isShow) ? genElement(el, state) : _e()
複製代碼

3. genFor

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}` : ''

  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 */
    )
  }

  el.forProcessed = true // avoid recursion
  return `${altHelper || '_l'}((${exp}),` +
    `function(${alias}${iterator1}${iterator2}){` +
      `return ${(altGen || genElement)(el, state)}` +
    '})'
}
複製代碼

genFor 的邏輯很簡單,首先 AST 元素節點中獲取了和 for 相關的一些屬性,而後返回了一個代碼字符串。

在咱們的例子中,expdataaliasitemiterator1index,所以生成以下僞代碼:

_l((data), function(item, index) {
  return genElememt(el, state)
})
複製代碼

4. genData & genChildren

再次回顧咱們的例子,它的最外層是 ul,首先執行 genIf,它最終調用了 genElement(el, state) 去生成子節點,注意,這裏的 el 仍然指向的是 ul 對應的 AST 節點,可是此時的 el.ifProcessedtrue,因此命中最後一個 else 邏輯:

else {
  // component or element
  let code
  if (el.component) {
    code = genComponent(el.component, el, state)
  } else {
    let data
    if (!el.plain || (el.pre && state.maybeComponent(el))) {
      data = genData(el, state)
    }

    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
}
複製代碼

這裏咱們只關注 2 個邏輯,genDatagenChildren

1.1 genData

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}",`
  }
  // 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
  if (el.events) {
    data += `${genHandlers(el.events, false)},`
  }
  if (el.nativeEvents) {
    data += `${genHandlers(el.nativeEvents, true)},`
  }
  // slot target
  // only for non-scoped slots
  if (el.slotTarget && !el.slotScope) {
    data += `slot:${el.slotTarget},`
  }
  // scoped slots
  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
}
複製代碼

genData 函數就是根據 AST 元素節點的屬性構造出一個 data 對象字符串,這個在後面建立 VNode 的時候的時候會做爲參數傳入。

以前咱們提到了 CodegenState 的實例 state,這裏有一段關於 state 的邏輯:

export function genData (el: ASTElement, state: CodegenState): string {
  // ...
  
  // module data generation functions
  for (let i = 0; i < state.dataGenFns.length; i++) {
    data += state.dataGenFns[i](el)
  }
  
  // ...
}
複製代碼

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

export class CodegenState {
  constructor (options: CompilerOptions) {
    // ...
    this.dataGenFns = pluckModuleFunction(options.modules, 'genData')
    // ...
  }
}
複製代碼

實際上就是獲取全部 modules 中的 genData 函數,其中,class modulestyle module 定義了 genData 函數。好比定義在 src/platforms/web/compiler/modules/class.js 中的 genData 方法:

function genData (el: ASTElement): string {
  let data = ''
  if (el.staticClass) {
    data += `staticClass:${el.staticClass},`
  }
  if (el.classBinding) {
    data += `class:${el.classBinding},`
  }
  return data
}
複製代碼

在咱們的例子中,ul AST 元素節點定義了 el.staticClassel.classBinding,所以最終生成的 data 字符串以下:

{
  staticClass: "list",
  class: bindCls
}
複製代碼

1.2 genChildren

接下來咱們再來看一下 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'
    ) {
      const normalizationType = checkSkip
        ? state.maybeComponent(el) ? `,1` : `,0`
        : ``
      return `${(altGenElement || genElement)(el, state)}${normalizationType}`
    }
    const normalizationType = checkSkip
      ? getNormalizationType(children, state.maybeComponent)
      : 0
    const gen = altGenNode || genNode
    return `[${children.map(c => gen(c, state)).join(',')}]${
      normalizationType ? `,${normalizationType}` : ''
    }
`

  }
}
複製代碼

在咱們的例子中,li AST 元素節點是 ul AST 元素節點的 children 之一,知足 (children.length === 1 && el.for && el.tag !== 'template' && el.tag !== 'slot') 條件,所以經過 genElement(el, state) 生成 li AST元素節點的代碼,也就回到了咱們以前調用 genFor 生成的代碼,把它們拼在一塊兒生成的僞代碼以下:

return (isShow) ?
    _c('ul', {
        staticClass: "list",
        class: bindCls
      },
      _l((data), function(item, index) {
        return genElememt(el, state)
      })
    ) : _e()
複製代碼

在咱們的例子中,在執行 genElememt(el, state) 的時候,el 仍是 li AST 元素節點,el.forProcessed 已爲 true,因此會繼續執行 genDatagenChildren 的邏輯。因爲 el.events 不爲空,在執行 genData 的時候,會執行 以下邏輯:

if (el.events) {
  data += `${genHandlers(el.events, false, state.warn)},`
}
複製代碼

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

export function genHandlers (
  events: ASTElementHandlers,
  isNative: boolean,
  warn: Function
): string 
{
  let res = isNative ? 'nativeOn:{' : 'on:{'
  for (const name in events) {
    res += `"${name}":${genHandler(name, events[name])},`
  }
  return res.slice(0-1) + '}'
}
複製代碼

genHandler 的邏輯就不介紹了,很大部分都是對修飾符 modifier 的處理,感興趣同窗能夠本身看,對於咱們的例子,它最終 genData 生成的 data 字符串以下:

{
  on: {
    "click": function($event) {
      clickItem(index)
    }
  }
}
複製代碼

genChildren 的時候,會執行到以下邏輯:

export function genChildren (
  el: ASTElement,
  state: CodegenState,
  checkSkip?: boolean,
  altGenElement?: Function,
  altGenNode?: Function
): string | void 
{
  // ...
  const normalizationType = checkSkip
    ? getNormalizationType(children, state.maybeComponent)
    : 0
  const gen = altGenNode || genNode
  return `[${children.map(c => gen(c, state)).join(',')}]${
    normalizationType ? `,${normalizationType}` : ''
  }
`

}

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

genChildren 的就是遍歷 children,而後執行 genNode 方法,根據不一樣的 type 執行具體的方法。在咱們的例子中,li AST 元素節點的 childrentype2 的表達式 AST 元素節點,那麼會執行到 genText(node) 邏輯。

export function genText (text: ASTText | ASTExpression): string {
  return `_v(${text.type === 2
    ? text.expression
    : transformSpecialNewlines(JSON.stringify(text.text))
  }
)`

}
複製代碼

所以在咱們的例子中,genChildren 生成的代碼串以下:

[_v(_s(item) + ":" + _s(index))]
複製代碼

和以前拼在一塊兒,最終生成的 code 以下:

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()
複製代碼

總結

這一節經過例子配合解析,咱們對從 ast -> code 這一步有了一些瞭解,編譯後生成的代碼就是在運行時執行的代碼。因爲 genCode 的內容有不少,因此我對你們的建議是不必把全部的細節都一次性看完,咱們應該根據具體一個 case,走完一條主線便可。

在以後的章節咱們會對 slot 的實現作解析,咱們會從新複習編譯的章節,針對具體問題作具體分析,有利於咱們排除干擾,對編譯過程的學習有更深刻的理解。

相關文章
相關標籤/搜索