hydration.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import {
  2. VNode,
  3. normalizeVNode,
  4. Text,
  5. Comment,
  6. Static,
  7. Fragment,
  8. Portal
  9. } from './vnode'
  10. import { queuePostFlushCb, flushPostFlushCbs } from './scheduler'
  11. import { ComponentInternalInstance } from './component'
  12. import { invokeDirectiveHook } from './directives'
  13. import { warn } from './warning'
  14. import { PatchFlags, ShapeFlags, isReservedProp, isOn } from '@vue/shared'
  15. import { RendererOptions } from './renderer'
  16. // Note: hydration is DOM-specific
  17. // But we have to place it in core due to tight coupling with core - splitting
  18. // it out creates a ton of unnecessary complexity.
  19. // Hydration also depends on some renderer internal logic which needs to be
  20. // passed in via arguments.
  21. export function createHydrationFunctions(
  22. mountComponent: any, // TODO
  23. patchProp: RendererOptions['patchProp']
  24. ) {
  25. const hydrate = (vnode: VNode, container: Element) => {
  26. if (__DEV__ && !container.hasChildNodes()) {
  27. warn(`Attempting to hydrate existing markup but container is empty.`)
  28. return
  29. }
  30. hydrateNode(container.firstChild!, vnode)
  31. flushPostFlushCbs()
  32. }
  33. // TODO handle mismatches
  34. // TODO SVG
  35. // TODO Suspense
  36. const hydrateNode = (
  37. node: Node,
  38. vnode: VNode,
  39. parentComponent: ComponentInternalInstance | null = null
  40. ): Node | null | undefined => {
  41. const { type, shapeFlag } = vnode
  42. vnode.el = node
  43. switch (type) {
  44. case Text:
  45. case Comment:
  46. case Static:
  47. return node.nextSibling
  48. case Fragment:
  49. const anchor = (vnode.anchor = hydrateChildren(
  50. node.nextSibling,
  51. vnode.children as VNode[],
  52. parentComponent
  53. )!)
  54. // TODO handle potential hydration error if fragment didn't get
  55. // back anchor as expected.
  56. return anchor.nextSibling
  57. case Portal:
  58. // TODO
  59. break
  60. default:
  61. if (shapeFlag & ShapeFlags.ELEMENT) {
  62. return hydrateElement(node as Element, vnode, parentComponent)
  63. } else if (shapeFlag & ShapeFlags.COMPONENT) {
  64. mountComponent(vnode, null, null, parentComponent, null, false)
  65. const subTree = vnode.component!.subTree
  66. return (subTree.anchor || subTree.el).nextSibling
  67. } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
  68. // TODO
  69. } else if (__DEV__) {
  70. warn('Invalid HostVNode type:', type, `(${typeof type})`)
  71. }
  72. }
  73. }
  74. const hydrateElement = (
  75. el: Element,
  76. vnode: VNode,
  77. parentComponent: ComponentInternalInstance | null
  78. ) => {
  79. const { props, patchFlag } = vnode
  80. // skip props & children if this is hoisted static nodes
  81. if (patchFlag !== PatchFlags.HOISTED) {
  82. // props
  83. if (props !== null) {
  84. if (
  85. patchFlag & PatchFlags.FULL_PROPS ||
  86. patchFlag & PatchFlags.HYDRATE_EVENTS
  87. ) {
  88. for (const key in props) {
  89. if (!isReservedProp(key) && isOn(key)) {
  90. patchProp(el, key, props[key], null)
  91. }
  92. }
  93. } else if (props.onClick != null) {
  94. // Fast path for click listeners (which is most often) to avoid
  95. // iterating through props.
  96. patchProp(el, 'onClick', props.onClick, null)
  97. }
  98. // vnode hooks
  99. const { onVnodeBeforeMount, onVnodeMounted } = props
  100. if (onVnodeBeforeMount != null) {
  101. invokeDirectiveHook(onVnodeBeforeMount, parentComponent, vnode)
  102. }
  103. if (onVnodeMounted != null) {
  104. queuePostFlushCb(() => {
  105. invokeDirectiveHook(onVnodeMounted, parentComponent, vnode)
  106. })
  107. }
  108. }
  109. // children
  110. if (
  111. vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN &&
  112. // skip if element has innerHTML / textContent
  113. !(props !== null && (props.innerHTML || props.textContent))
  114. ) {
  115. hydrateChildren(
  116. el.firstChild,
  117. vnode.children as VNode[],
  118. parentComponent
  119. )
  120. }
  121. }
  122. return el.nextSibling
  123. }
  124. const hydrateChildren = (
  125. node: Node | null | undefined,
  126. vnodes: VNode[],
  127. parentComponent: ComponentInternalInstance | null
  128. ): Node | null | undefined => {
  129. for (let i = 0; node != null && i < vnodes.length; i++) {
  130. // TODO can skip normalizeVNode in optimized mode
  131. // (need hint on rendered markup?)
  132. const vnode = (vnodes[i] = normalizeVNode(vnodes[i]))
  133. node = hydrateNode(node, vnode, parentComponent)
  134. }
  135. return node
  136. }
  137. return [hydrate, hydrateNode] as const
  138. }