const _component_Comp = _resolveComponent("Comp")
const n0 = t0()
const n3 = t1()
- const n2 = _child(n3)
+ const n2 = _child(n3, 1)
_setInsertionState(n3, 0)
const n1 = _createComponentWithFallback(_component_Comp)
_renderEffect(() => {
pushBlock(...genCall(helper('nthChild'), from, String(elementIndex)))
}
} else {
+ // offset is used to determine the child during hydration.
+ // if offset is not 0, we need to specify the offset to skip the dynamic
+ // children and get the correct child.
+ let childOffset = offset === 0 ? undefined : `${Math.abs(offset)}`
if (elementIndex === 0) {
- pushBlock(...genCall(helper('child'), from))
+ pushBlock(...genCall(helper('child'), from, childOffset))
} else {
// check if there's a node that we can reuse from
- let init = genCall(helper('child'), from)
+ let init = genCall(helper('child'), from, childOffset)
if (elementIndex === 1) {
init = genCall(helper('next'), init)
} else if (elementIndex > 1) {
)
})
+ test('mixed consecutive slot and element', async () => {
+ const data = reactive({
+ text: 'foo',
+ msg: 'hi',
+ })
+ const { container } = await testHydration(
+ `<template>
+ <components.Child>
+ <template #foo><span>{{data.text}}</span></template>
+ <template #bar><span>bar</span></template>
+ </components.Child>
+ </template>`,
+ {
+ Child: `<template><div><slot name="foo"/><slot name="bar"/><div>{{data.msg}}</div></div></template>`,
+ },
+ data,
+ )
+
+ expect(container.innerHTML).toBe(
+ `<div>` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<div>hi</div>` +
+ `</div>`,
+ )
+
+ data.msg = 'bar'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<div>` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<div>bar</div>` +
+ `</div>`,
+ )
+ })
+
test('mixed slot and element', async () => {
const data = reactive({
text: 'foo',
*
* Client Compiled Code (Simplified):
* const n2 = t0() // n2 = `<div> </div>`
- * const n1 = _child(n2) // n1 = text node
+ * const n1 = _child(n2, 1) // n1 = text node
* // ... slot creation ...
* _renderEffect(() => _setText(n1, _ctx.msg))
*
*
* Hydration Mismatch:
* - During hydration, `n2` refers to the SSR `<div>`.
- * - `_child(n2)` would return `<!--[-->`.
+ * - `_child(n2, 1)` would return `<!--[-->`.
* - The client code expects `n1` to be the text node, but gets the comment.
* The subsequent `_setText(n1, ...)` would fail or target the wrong node.
*
* Solution (`__child`):
- * - `__child(n2)` is used during hydration. It skips the SSR fragment anchors
- * (`<!--[-->...<!--]-->`) and any other non-content nodes to find the
- * "Actual Text Node", correctly matching the client's expectation for `n1`.
+ * - `__child(n2, offset)` is used during hydration. It skips the dynamic children
+ * to find the "Actual Text Node", correctly matching the client's expectation
+ * for `n1`.
*/
/*! #__NO_SIDE_EFFECTS__ */
-export function __child(node: ParentNode): Node {
- let n = node.firstChild!
+export function __child(node: ParentNode, offset?: number): Node {
+ let n = offset ? __nthChild(node, offset) : node.firstChild!
if (isComment(n, '[')) {
n = locateEndAnchor(n)!.nextSibling!
}
/*! #__NO_SIDE_EFFECTS__ */
-export const child: DelegatedFunction<typeof _child> = node => {
- return child.impl(node)
+export const child: DelegatedFunction<typeof __child> = (node, offset) => {
+ return child.impl(node, offset)
}
child.impl = _child