|
|
@@ -30,6 +30,7 @@ import {
|
|
|
isHydrating,
|
|
|
locateHydrationNode,
|
|
|
locateNextNode,
|
|
|
+ markHydrationAnchor,
|
|
|
setCurrentHydrationNode,
|
|
|
} from './dom/hydration'
|
|
|
import {
|
|
|
@@ -118,58 +119,74 @@ export const createFor = (
|
|
|
|
|
|
if (!isMounted) {
|
|
|
isMounted = true
|
|
|
- let nextNode
|
|
|
- const hydrationStart = isHydrating ? currentHydrationNode : null
|
|
|
- for (let i = 0; i < newLength; i++) {
|
|
|
- if (isHydrating) nextNode = locateNextNode(currentHydrationNode!)
|
|
|
- mount(source, i)
|
|
|
- if (isHydrating && nextNode) setCurrentHydrationNode(nextNode)
|
|
|
- }
|
|
|
-
|
|
|
if (isHydrating) {
|
|
|
- // Slot fallback can fall through an empty/invalid `v-for`. In that
|
|
|
- // case SSR only rendered the parent slot range, so this `v-for` has no
|
|
|
- // own `<!--]-->` to reuse. If `hydrationStart` is not the parent slot
|
|
|
- // end anchor, use `hydrationStart.nextSibling` as the insertion anchor
|
|
|
- // so the runtime `<!--for-->` lands immediately after that local SSR
|
|
|
- // range. Otherwise insert it before the parent slot end anchor.
|
|
|
- if (
|
|
|
- currentEmptyFragment !== undefined &&
|
|
|
- !isValidBlock(newBlocks) &&
|
|
|
- currentSlotEndAnchor
|
|
|
- ) {
|
|
|
- const anchor =
|
|
|
- // The invalid list still consumed local SSR item ranges.
|
|
|
- currentHydrationNode !== hydrationStart
|
|
|
- ? currentHydrationNode!
|
|
|
- : // Empty source with trailing slot siblings.
|
|
|
- hydrationStart !== currentSlotEndAnchor
|
|
|
- ? hydrationStart!.nextSibling!
|
|
|
- : currentSlotEndAnchor
|
|
|
- parentAnchor = __DEV__ ? createComment('for') : createTextNode()
|
|
|
- pendingHydrationAnchor = true
|
|
|
- setCurrentHydrationNode(hydrationStart)
|
|
|
- queuePostFlushCb(() =>
|
|
|
- anchor.parentNode!.insertBefore(parentAnchor, anchor),
|
|
|
- )
|
|
|
+ const hydrationStart = currentHydrationNode!
|
|
|
+ let nextNode
|
|
|
+ const emptyLocalRange =
|
|
|
+ isComment(hydrationStart, ']') &&
|
|
|
+ isComment(hydrationStart.previousSibling!, '[')
|
|
|
+
|
|
|
+ if (emptyLocalRange && newLength) {
|
|
|
+ parentAnchor = markHydrationAnchor(hydrationStart)
|
|
|
+ for (let i = 0; i < newLength; i++) {
|
|
|
+ mount(source, i)
|
|
|
+ }
|
|
|
+ setCurrentHydrationNode(parentAnchor)
|
|
|
} else {
|
|
|
- parentAnchor = currentHydrationNode!
|
|
|
+ for (let i = 0; i < newLength; i++) {
|
|
|
+ nextNode = locateNextNode(currentHydrationNode!)
|
|
|
+ mount(source, i)
|
|
|
+ if (nextNode) setCurrentHydrationNode(nextNode)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Slot fallback can fall through an empty/invalid `v-for`. In that
|
|
|
+ // case SSR only rendered the parent slot range, so this `v-for` has no
|
|
|
+ // own `<!--]-->` to reuse. If `hydrationStart` is not the parent slot
|
|
|
+ // end anchor, use `hydrationStart.nextSibling` as the insertion anchor
|
|
|
+ // so the runtime `<!--for-->` lands immediately after that local SSR
|
|
|
+ // range. Otherwise insert it before the parent slot end anchor.
|
|
|
if (
|
|
|
- __DEV__ &&
|
|
|
- (!parentAnchor || (parentAnchor && !isComment(parentAnchor, ']')))
|
|
|
+ currentEmptyFragment !== undefined &&
|
|
|
+ !isValidBlock(newBlocks) &&
|
|
|
+ currentSlotEndAnchor
|
|
|
) {
|
|
|
- throw new Error(
|
|
|
- `v-for fragment anchor node was not found. this is likely a Vue internal bug.`,
|
|
|
+ const anchor =
|
|
|
+ // The invalid list still consumed local SSR item ranges.
|
|
|
+ currentHydrationNode !== hydrationStart
|
|
|
+ ? currentHydrationNode!
|
|
|
+ : // Empty source with trailing slot siblings.
|
|
|
+ hydrationStart !== currentSlotEndAnchor
|
|
|
+ ? hydrationStart.nextSibling!
|
|
|
+ : currentSlotEndAnchor
|
|
|
+ parentAnchor = __DEV__ ? createComment('for') : createTextNode()
|
|
|
+ pendingHydrationAnchor = true
|
|
|
+ setCurrentHydrationNode(hydrationStart)
|
|
|
+ queuePostFlushCb(() =>
|
|
|
+ anchor.parentNode!.insertBefore(parentAnchor, anchor),
|
|
|
)
|
|
|
- }
|
|
|
+ } else {
|
|
|
+ parentAnchor = currentHydrationNode!
|
|
|
+ if (
|
|
|
+ __DEV__ &&
|
|
|
+ (!parentAnchor || (parentAnchor && !isComment(parentAnchor, ']')))
|
|
|
+ ) {
|
|
|
+ throw new Error(
|
|
|
+ `v-for fragment anchor node was not found. this is likely a Vue internal bug.`,
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
- // optimization: cache the fragment end anchor as $llc (last logical child)
|
|
|
- // so that locateChildByLogicalIndex can skip the entire fragment
|
|
|
- if (_insertionParent && isComment(parentAnchor, ']')) {
|
|
|
- ;(parentAnchor as any as ChildItem).$idx = _insertionIndex || 0
|
|
|
- _insertionParent.$llc = parentAnchor
|
|
|
+ // optimization: cache the fragment end anchor as $llc (last logical child)
|
|
|
+ // so that locateChildByLogicalIndex can skip the entire fragment
|
|
|
+ if (_insertionParent && isComment(parentAnchor, ']')) {
|
|
|
+ ;(parentAnchor as any as ChildItem).$idx = _insertionIndex || 0
|
|
|
+ _insertionParent.$llc = parentAnchor
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+ } else {
|
|
|
+ for (let i = 0; i < newLength; i++) {
|
|
|
+ mount(source, i)
|
|
|
+ }
|
|
|
}
|
|
|
} else {
|
|
|
parent = parent || parentAnchor!.parentNode
|