insertionState.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { isComment, isHydrating } from './dom/hydration'
  2. export type ChildItem = ChildNode & {
  3. $idx: number
  4. // used count as an anchor
  5. $uc?: number
  6. }
  7. export type InsertionParent = ParentNode & { $children?: ChildItem[] }
  8. type HydrationState = {
  9. // static nodes and the start anchors of fragments
  10. logicalChildren: ChildItem[]
  11. // hydrated dynamic children count so far
  12. prevDynamicCount: number
  13. // number of unique insertion anchors that have appeared
  14. uniqueAnchorCount: number
  15. // current append anchor
  16. appendAnchor: Node | null
  17. }
  18. const hydrationStateCache = new WeakMap<ParentNode, HydrationState>()
  19. export let insertionParent: InsertionParent | undefined
  20. export let insertionAnchor: Node | 0 | undefined | null
  21. /**
  22. * This function is called before a block type that requires insertion
  23. * (component, slot outlet, if, for) is created. The state is used for actual
  24. * insertion on client-side render, and used for node adoption during hydration.
  25. */
  26. export function setInsertionState(
  27. parent: ParentNode,
  28. anchor?: Node | 0 | null | number,
  29. ): void {
  30. insertionParent = parent
  31. if (anchor !== undefined) {
  32. if (isHydrating) {
  33. insertionAnchor = anchor as Node
  34. initializeHydrationState(parent)
  35. } else {
  36. // special handling append anchor value to null
  37. insertionAnchor =
  38. typeof anchor === 'number' && anchor > 0 ? null : (anchor as Node)
  39. cacheTemplateChildren(parent)
  40. }
  41. } else {
  42. insertionAnchor = undefined
  43. }
  44. }
  45. function initializeHydrationState(parent: ParentNode) {
  46. if (!hydrationStateCache.has(parent)) {
  47. const childNodes = parent.childNodes
  48. const len = childNodes.length
  49. // fast path for single child case. No need to build logicalChildren
  50. if (
  51. len === 1 ||
  52. (len === 3 &&
  53. isComment(childNodes[0], '[') &&
  54. isComment(childNodes[2], ']'))
  55. ) {
  56. insertionAnchor = undefined
  57. return
  58. }
  59. const logicalChildren = new Array(len) as ChildItem[]
  60. // Build logical children:
  61. // - static node: keep the node as a child
  62. // - fragment: keep only the start anchor ('<!--[-->') as a child
  63. let index = 0
  64. for (let i = 0; i < len; i++) {
  65. const n = childNodes[i] as ChildItem
  66. n.$idx = index
  67. if (n.nodeType === 8) {
  68. const data = (n as any as Comment).data
  69. // vdom fragment
  70. if (data === '[') {
  71. logicalChildren[index++] = n
  72. // find matching end anchor, accounting for nested fragments
  73. let depth = 1
  74. let j = i + 1
  75. for (; j < len; j++) {
  76. const c = childNodes[j] as Comment
  77. if (c.nodeType === 8) {
  78. const d = c.data
  79. if (d === '[') depth++
  80. else if (d === ']') {
  81. depth--
  82. if (depth === 0) break
  83. }
  84. }
  85. }
  86. // jump i to the end anchor
  87. i = j
  88. continue
  89. }
  90. }
  91. logicalChildren[index++] = n
  92. }
  93. logicalChildren.length = index
  94. hydrationStateCache.set(parent, {
  95. logicalChildren,
  96. prevDynamicCount: 0,
  97. uniqueAnchorCount: 0,
  98. appendAnchor: null,
  99. })
  100. }
  101. }
  102. function cacheTemplateChildren(parent: InsertionParent) {
  103. if (!parent.$children) {
  104. const nodes = parent.childNodes
  105. const len = nodes.length
  106. const children = new Array(len) as ChildItem[]
  107. for (let i = 0; i < len; i++) {
  108. const node = nodes[i] as ChildItem
  109. node.$idx = i
  110. children[i] = node
  111. }
  112. parent.$children = children
  113. }
  114. }
  115. export function resetInsertionState(): void {
  116. insertionParent = insertionAnchor = undefined
  117. }
  118. export function getHydrationState(
  119. parent: ParentNode,
  120. ): HydrationState | undefined {
  121. return hydrationStateCache.get(parent)
  122. }