vue -- compile结果代码解读

Published: · LastMod: May 10, 2023 · 1860 words

vue – compile结果代码解读 🔗

vue中对于sfc文件最终的编译结果,可以在playground中看到

源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<script setup>
import { ref } from 'vue'

const msg = ref('Hello World!')
</script>

<template>
  <h1>{{ msg }}</h1>
  <div>hello world</div>
  <input v-model="msg">
</template>

结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* Analyzed bindings: {
  "ref": "setup-const",
  "msg": "setup-ref"
} */
import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, vModelText as _vModelText, withDirectives as _withDirectives, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, "hello world", -1 /* HOISTED */)

import { ref } from 'vue'


const __sfc__ = {
  __name: 'App',
  setup(__props) {

const msg = ref('Hello World!')

return (_ctx, _cache) => {
  return (_openBlock(), _createElementBlock(_Fragment, null, [
    _createElementVNode("h1", null, _toDisplayString(msg.value), 1 /* TEXT */),
    _hoisted_1,
    _withDirectives(_createElementVNode("input", {
      "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ((msg).value = $event))
    }, null, 512 /* NEED_PATCH */), [
      [_vModelText, msg.value]
    ])
  ], 64 /* STABLE_FRAGMENT */))
}
}

}
__sfc__.__file = "App.vue"
export default __sfc__

openBlock开启块 🔗

openBlock很简单,就是在blockStack中推入currentBlock

1
2
3
export function openBlock(disableTracking = false) {
  blockStack.push((currentBlock = disableTracking ? null : []))
}

默认disableTrackingfalse,这里把currentBlock重置为空数组

createElementBlock 🔗

在调用完创建块之后,调用createElementBlock,这里创建一个元素块

内部调用了setupBlock,并且传入了createBaseVNode的结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
export function createElementBlock(
  type: string | typeof Fragment,
  props?: Record<string, any> | null,
  children?: any,
  patchFlag?: number,
  dynamicProps?: string[],
  shapeFlag?: number
) {
  return setupBlock(
    createBaseVNode(
      type,
      props,
      children,
      patchFlag,
      dynamicProps,
      shapeFlag,
      true /* isBlock */
    )
  )
}

setupBlock 🔗

这个函数中把当前vnode的动态节点设置成当前块currentBlock了,方便后续引用操作

内部调用了关闭块,也就是block栈销毁当前的currentBlock,并重新赋值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function setupBlock(vnode: VNode) {
  // save current block children on the block vnode
  vnode.dynamicChildren =
    isBlockTreeEnabled > 0 ? currentBlock || (EMPTY_ARR as any) : null
  // close block
  closeBlock()
  // a block is always going to be patched, so track it as a child of its
  // parent block
  // 这个时候的currentBlock已经是它前一个了,也就是父节点的
  if (isBlockTreeEnabled > 0 && currentBlock) {
    currentBlock.push(vnode)
  }
  return vnode
}

closeBlock关闭块 🔗

这里调用blockStackpop方法,推出最后一个

并且把currentBlock设置为倒数第一个

1
2
3
4
export function closeBlock() {
  blockStack.pop()
  currentBlock = blockStack[blockStack.length - 1] || null
}

createElementVNode 🔗

创建基础的VNode节点

可以看到其实就是createBaseVNode

export { createBaseVNode as createElementVNode }

createElementVNode做了下面几个事情

  • 初始化vnode的对象
  • 如果有子节点,初始化子节点
    • 如果子节点是字符串,这里的shapeFlag会进行标记,后续在diff的时候就比较快(优化点)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
function createBaseVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag = 0,
  dynamicProps: string[] | null = null,
  shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
  isBlockNode = false,
  needFullChildrenNormalization = false
) {
  const vnode = {
    // ....vnode的初始对象
    // 。。。
  } as VNode

  if (needFullChildrenNormalization) {
    normalizeChildren(vnode, children)
    // normalize suspense children
    if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
      ;(type as typeof SuspenseImpl).normalize(vnode)
    }
  } else if (children) {
    // compiled element vnode - if children is passed, only possible types are
    // string or Array.
    vnode.shapeFlag |= isString(children)
      ? ShapeFlags.TEXT_CHILDREN
      : ShapeFlags.ARRAY_CHILDREN
  }

  // track vnode for block tree
  if (
    isBlockTreeEnabled > 0 &&
    // avoid a block node from tracking itself
    !isBlockNode &&
    // has current parent block
    currentBlock &&
    // presence of a patch flag indicates this node needs patching on updates.
    // component nodes also should always be patched, because even if the
    // component doesn't need to update, it needs to persist the instance on to
    // the next vnode so that it can be properly unmounted later.
    (vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
    // the EVENTS flag is only for hydration and if it is the only flag, the
    // vnode should not be considered dynamic due to handler caching.
    vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
  ) {
    currentBlock.push(vnode)
  }

  // 兼容vue2代码
  if (__COMPAT__) {
    convertLegacyVModelProps(vnode)
    defineLegacyVNodeProperties(vnode)
  }

  return vnode
}

createVNode 🔗

内部走的是_createVNode方法

1
2
3
export const createVNode = (
  __DEV__ ? createVNodeWithArgsTransform : _createVNode
) as typeof _createVNode

_createVNode 🔗

如果type不对, type重置为Comment类型 🔗

1
2
3
4
5
6
if (!type || type === NULL_DYNAMIC_COMPONENT) {
    if (__DEV__ && !type) {
      warn(`Invalid vnode type when creating vnode: ${type}.`)
    }
    type = Comment
}

如果type是vnode 🔗

  • 克隆vnode,这里克隆时进行了优化,对于原来的ref进行了合并
  • 有子节点的进行子节点初始化
  • currentBlock依赖搜集
  • patchFlag进行标记
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (isVNode(type)) {
    // createVNode receiving an existing vnode. This happens in cases like
    // <component :is="vnode"/>
    // #2078 make sure to merge refs during the clone instead of overwriting it
    // 克隆vnode, 内部ref进行合并
    const cloned = cloneVNode(type, props, true /* mergeRef: true */)
    // 子节点初始化
    if (children) {
      normalizeChildren(cloned, children)
    }
  	// 
    if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) {
      if (cloned.shapeFlag & ShapeFlags.COMPONENT) {
        currentBlock[currentBlock.indexOf(type)] = cloned
      } else {
        currentBlock.push(cloned)
      }
    }
    // 这里把patchFlag进行标记,后续跳过diff优化
    cloned.patchFlag |= PatchFlags.BAIL
    return cloned
}

如果type是class组件 🔗

type重新赋值

1
2
3
4
// class component normalization.
if (isClassComponent(type)) {
  type = type.__vccOpts
}

存在属性 🔗

存在class或者style属性时

进行初始化计算, 这里把class进行了拼接、style进行了拼接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// class & style normalization.
  if (props) {
    // for reactive or proxy objects, we need to clone it to enable mutation.
    props = guardReactiveProps(props)!
    let { class: klass, style } = props
    if (klass && !isString(klass)) {
      props.class = normalizeClass(klass)
    }
    if (isObject(style)) {
      // reactive state objects need to be cloned since they are likely to be
      // mutated
      if (isProxy(style) && !isArray(style)) {
        style = extend({}, style)
      }
      props.style = normalizeStyle(style)
    }
}

最后shapeFlag进行重新赋值为位值 🔗

缩减字符长度

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  // encode the vnode type information into a bitmap
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
    ? ShapeFlags.SUSPENSE
    : isTeleport(type)
    ? ShapeFlags.TELEPORT
    : isObject(type)
    ? ShapeFlags.STATEFUL_COMPONENT
    : isFunction(type)
    ? ShapeFlags.FUNCTIONAL_COMPONENT
    : 0

走到createBaseVNode函数,创建出vnode