expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[--><div>true</div>-true-<!--if--><!--]-->
- </div>"
+ <!--[--><div>true</div>-true-<!--]-->
+ <!--if--></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[--><!--if--><!--]-->
- </div>"
+ <!--[--><!--]-->
+ <!--if--></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[--><div>true</div>-true-<!--if--><!--]-->
- </div>"
+ <!--[--><!--]-->
+ <div>true</div>-true-<!--if--></div>"
`,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[--><div>true</div>-true-<!--if--><!--]-->
- <span></span></div>"
+ <!--[--><div>true</div>-true-<!--]-->
+ <!--if--><span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[--><!--if--><!--]-->
- <span></span></div>"
+ <!--[--><!--]-->
+ <!--if--><span></span></div>"
`,
)
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`
"<div><span></span>
- <!--[--><div>true</div>-true-<!--if--><!--]-->
- <span></span></div>"
+ <!--[--><!--]-->
+ <div>true</div>-true-<!--if--><span></span></div>"
`)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[--><div>true</div>-true-<!--if--><!--]-->
- <!--[--><div>true</div>-true-<!--if--><!--]-->
- <span></span></div>"
+ <!--[--><div>true</div>-true-<!--]-->
+ <!--if-->
+ <!--[--><div>true</div>-true-<!--]-->
+ <!--if--><span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[--><!--if--><!--]-->
- <!--[--><!--if--><!--]-->
- <span></span></div>"
+ <!--[--><!--]-->
+ <!--if-->
+ <!--[--><!--]-->
+ <!--if--><span></span></div>"
`,
)
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`
"<div><span></span>
- <!--[--><div>true</div>-true-<!--if--><!--]-->
- <!--[--><div>true</div>-true-<!--if--><!--]-->
- <span></span></div>"
+ <!--[--><!--]-->
+ <div>true</div>-true-<!--if-->
+ <!--[--><!--]-->
+ <div>true</div>-true-<!--if--><span></span></div>"
`)
})
<!--[-->
<!--[--><div>foo</div>-bar-<!--]-->
<!--[--><div>foo</div>-bar-<!--]-->
- <!--[--><div>foo</div>-bar-<div>foo</div>-bar-<!--]-->
- <!--]-->
+ <!--[--><div>foo</div>-bar-<!--]-->
+ <div>foo</div>-bar-<!--]-->
</div>"
`,
)
watch,
} from '@vue/reactivity'
import { isArray, isObject, isString } from '@vue/shared'
-import { createComment, createTextNode } from './dom/node'
+import {
+ createComment,
+ createTextNode,
+ updateLastLogicalChild,
+} from './dom/node'
import {
type Block,
insert,
locateHydrationNode,
setCurrentHydrationNode,
} from './dom/hydration'
-import { ForFragment, VaporFragment, findLastChild } from './fragment'
+import { ForFragment, VaporFragment, findBlockNode } from './fragment'
import {
- type ChildItem,
insertionAnchor,
insertionParent,
resetInsertionState,
for (let i = 0; i < newLength; i++) {
const nodes = mount(source, i).nodes
if (isHydrating) {
- setCurrentHydrationNode(findLastChild(nodes!)!.nextSibling)
+ setCurrentHydrationNode(findBlockNode(nodes!).nextNode)
}
}
if (!parentAnchor || (parentAnchor && !isComment(parentAnchor, ']'))) {
throw new Error(`v-for fragment anchor node was not found.`)
}
- // the lastLogicalChild is the fragment start anchor; replacing it with end anchor
- // can avoid the call to locateEndAnchor within locateChildByLogicalIndex
- if (_insertionParent && _insertionParent!.$llc) {
- ;(parentAnchor as any as ChildItem).$idx = (
- _insertionParent!.$llc as ChildItem
- ).$idx
- _insertionParent.$llc = parentAnchor
+
+ if (_insertionParent) {
+ updateLastLogicalChild(_insertionParent!, parentAnchor)
}
}
} else {
}
}
+export function isFragmentBlock(block: Block): boolean {
+ if (isArray(block)) {
+ return true
+ } else if (isVaporComponent(block)) {
+ return isFragmentBlock(block.block!)
+ } else if (isFragment(block)) {
+ return isFragmentBlock(block.nodes)
+ }
+ return false
+}
+
/**
* dev / test only
*/
locateHydrationNode,
} from './dom/hydration'
import { DynamicFragment, type VaporFragment } from './fragment'
+import { updateLastLogicalChild } from './dom/node'
export type RawSlots = Record<string, VaporSlot> & {
$?: DynamicSlotSource[]
if (fragment.insert) {
;(fragment as VaporFragment).hydrate!()
}
+ if (_insertionParent) {
+ updateLastLogicalChild(_insertionParent!, fragment.anchor)
+ }
if (_insertionAnchor !== undefined) {
advanceHydrationNode(_insertionParent!)
}
;(Node.prototype as any).$lpn = undefined
;(Node.prototype as any).$lan = undefined
;(Node.prototype as any).$lin = undefined
+ ;(Node.prototype as any).$curIdx = undefined
isOptimized = true
}
? firstChild
: locateChildByLogicalIndex(insertionParent!, insertionAnchor)!
}
+
+ insertionParent!.$llc = node
+ ;(node as ChildItem).$idx = insertionParent!.$curIdx =
+ insertionParent!.$curIdx === undefined ? 0 : insertionParent!.$curIdx + 1
} else {
node = currentHydrationNode
if (insertionParent && (!node || node.parentNode !== insertionParent)) {
return null
}
+
+// use fragment end anchor as the logical child to avoid locateEndAnchor calls
+// in locateChildByLogicalIndex
+export function updateLastLogicalChild(
+ parent: InsertionParent,
+ child: Node,
+): void {
+ if (!isComment(child, ']')) return
+ ;(child as any as ChildItem).$idx = parent.$curIdx || 0
+ parent.$llc = child
+}
type TransitionOptions,
type VaporTransitionHooks,
insert,
+ isFragmentBlock,
isValidBlock,
remove,
} from './block'
}
}
+ const { parentNode, nextNode } = findBlockNode(this.nodes)!
// create an anchor
- const { parentNode, nextSibling } = findLastChild(this)!
queuePostFlushCb(() => {
parentNode!.insertBefore(
(this.anchor = __DEV__
? createComment(this.anchorLabel!)
: createTextNode()),
- nextSibling,
+ nextNode,
)
})
}
: fragment
}
-export function findLastChild(node: Block): Node | undefined | null {
+export function findBlockNode(block: Block): {
+ parentNode: Node | null
+ nextNode: Node | null
+} {
+ let { parentNode, nextSibling: nextNode } = findLastChild(block)!
+
+ // if nodes render as a fragment and the current nextNode is fragment
+ // end anchor, need to move to the next node
+ if (nextNode && isComment(nextNode, ']') && isFragmentBlock(block)) {
+ nextNode = nextNode.nextSibling
+ }
+
+ return {
+ parentNode,
+ nextNode,
+ }
+}
+
+function findLastChild(node: Block): Node | undefined | null {
if (node && node instanceof Node) {
return node
} else if (isArray(node)) {
$lpn?: Node | null
// last append node
$lan?: Node | null
+ // the logical index of current hydration node
+ $curIdx?: number
}
export let insertionParent: InsertionParent | undefined
export let insertionAnchor: Node | 0 | undefined | null
hydrateVNode(vnode, parentInstance as any)
onScopeDispose(unmount, true)
isMounted = true
- frag.nodes = [vnode.el as Node]
+ frag.nodes = vnode.el as any
}
frag.insert = (parentNode, anchor, transition) => {
}
// update the fragment nodes
- frag.nodes = [vnode.el as Node]
+ frag.nodes = vnode.el as any
simpleSetCurrentInstance(prev)
}
frag.hydrate = () => {
render()
+ if (__DEV__ && isComment(currentHydrationNode!, ']')) {
+ throw new Error(`Failed to locate vdom slot anchor`)
+ }
+ frag.anchor = currentHydrationNode!
isMounted = true
}