diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
index 1bae37c0f..fb2fff865 100644
--- a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
@@ -246,7 +246,7 @@ describe('ssr: components', () => {
_ssrRenderList(list, (i) => {
_push(\`\`)
})
- _push(\`\`)
+ _push(\`\`)
_push(\`\`)
} else {
_push(\`\`)
@@ -270,7 +270,7 @@ describe('ssr: components', () => {
_ssrRenderList(_ctx.list, (i) => {
_push(\`\`)
})
- _push(\`\`)
+ _push(\`\`)
_push(\`\`)
} else {
_push(\`\`)
diff --git a/packages/compiler-ssr/__tests__/ssrVFor.spec.ts b/packages/compiler-ssr/__tests__/ssrVFor.spec.ts
index 0d9572651..dad426de0 100644
--- a/packages/compiler-ssr/__tests__/ssrVFor.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrVFor.spec.ts
@@ -10,7 +10,7 @@ describe('ssr: v-for', () => {
_ssrRenderList(_ctx.list, (i) => {
_push(\`
\`)
})
- _push(\`\`)
+ _push(\`\`)
}"
`)
})
@@ -25,7 +25,7 @@ describe('ssr: v-for', () => {
_ssrRenderList(_ctx.list, (i) => {
_push(\`foobar
\`)
})
- _push(\`\`)
+ _push(\`\`)
}"
`)
})
@@ -51,9 +51,9 @@ describe('ssr: v-for', () => {
_ssrInterpolate(j)
}\`)
})
- _push(\`\`)
+ _push(\`\`)
})
- _push(\`\`)
+ _push(\`\`)
}"
`)
})
@@ -68,7 +68,7 @@ describe('ssr: v-for', () => {
_ssrRenderList(_ctx.list, (i) => {
_push(\`\${_ssrInterpolate(i)}\`)
})
- _push(\`\`)
+ _push(\`\`)
}"
`)
})
@@ -85,7 +85,7 @@ describe('ssr: v-for', () => {
_ssrRenderList(_ctx.list, (i) => {
_push(\`\${_ssrInterpolate(i)}\`)
})
- _push(\`\`)
+ _push(\`\`)
}"
`)
})
@@ -107,7 +107,7 @@ describe('ssr: v-for', () => {
_ssrInterpolate(i + 1)
}\`)
})
- _push(\`\`)
+ _push(\`\`)
}"
`)
})
@@ -127,7 +127,7 @@ describe('ssr: v-for', () => {
_ssrRenderList(_ctx.list, ({ foo }, index) => {
_push(\`\${_ssrInterpolate(foo + _ctx.bar + index)}
\`)
})
- _push(\`\`)
+ _push(\`\`)
}"
`)
})
diff --git a/packages/compiler-ssr/__tests__/ssrVIf.spec.ts b/packages/compiler-ssr/__tests__/ssrVIf.spec.ts
index e8909a828..840d48508 100644
--- a/packages/compiler-ssr/__tests__/ssrVIf.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrVIf.spec.ts
@@ -147,7 +147,7 @@ describe('ssr: v-if', () => {
_ssrRenderList(_ctx.list, (i) => {
_push(\`\`)
})
- _push(\`\`)
+ _push(\`\`)
_push(\`\`)
} else {
_push(\`\`)
diff --git a/packages/compiler-ssr/__tests__/ssrVModel.spec.ts b/packages/compiler-ssr/__tests__/ssrVModel.spec.ts
index db4af81d0..c88f6ba31 100644
--- a/packages/compiler-ssr/__tests__/ssrVModel.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrVModel.spec.ts
@@ -70,7 +70,7 @@ describe('ssr: v-model', () => {
: _ssrLooseEqual(_ctx.model, i))) ? " selected" : ""
}>\`)
})
- _push(\`\`)
+ _push(\`\`)
}"
`)
diff --git a/packages/compiler-ssr/src/ssrCodegenTransform.ts b/packages/compiler-ssr/src/ssrCodegenTransform.ts
index 56f38b9e5..29f198370 100644
--- a/packages/compiler-ssr/src/ssrCodegenTransform.ts
+++ b/packages/compiler-ssr/src/ssrCodegenTransform.ts
@@ -381,6 +381,7 @@ function processChildrenDynamicInfo(
* // Dynamic node -> should be wrapped
* // Dynamic node -> should NOT be wrapped
* // Static node
+ *
*/
function shouldProcessChildAsDynamic(
parent: { tag?: string; children: TemplateChildNode[] },
diff --git a/packages/compiler-ssr/src/transforms/ssrVFor.ts b/packages/compiler-ssr/src/transforms/ssrVFor.ts
index 827650785..251b1fef5 100644
--- a/packages/compiler-ssr/src/transforms/ssrVFor.ts
+++ b/packages/compiler-ssr/src/transforms/ssrVFor.ts
@@ -49,8 +49,7 @@ export function ssrProcessFor(
)
if (!disableNestedFragments) {
context.pushStringPart(``)
- } else {
- // add anchor for non-fragment v-for
- context.pushStringPart(``)
}
+ // v-for anchor for vapor hydration
+ context.pushStringPart(``)
}
diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts
index 4c8be2281..6c702f683 100644
--- a/packages/runtime-core/src/hydration.ts
+++ b/packages/runtime-core/src/hydration.ts
@@ -125,7 +125,7 @@ export function createHydrationFunctions(
let n = next(node)
// skip if:
// - dynamic anchors (``, ``)
- // - dynamic fragment end anchors (e.g. ``, ``)
+ // - vapor fragment end anchors (e.g. ``, ``)
if (n && (isDynamicAnchor(n) || isVaporFragmentEndAnchor(n))) {
n = next(n)
}
diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts
index 14faf569c..b0878e139 100644
--- a/packages/runtime-vapor/__tests__/hydration.spec.ts
+++ b/packages/runtime-vapor/__tests__/hydration.spec.ts
@@ -4,7 +4,12 @@ import { compileScript, parse } from '@vue/compiler-sfc'
import * as runtimeVapor from '../src'
import * as runtimeDom from '@vue/runtime-dom'
import * as VueServerRenderer from '@vue/server-renderer'
-import { DYNAMIC_COMPONENT_ANCHOR_LABEL, IF_ANCHOR_LABEL } from '@vue/shared'
+import {
+ DYNAMIC_COMPONENT_ANCHOR_LABEL,
+ FOR_ANCHOR_LABEL,
+ IF_ANCHOR_LABEL,
+ SLOT_ANCHOR_LABEL,
+} from '@vue/shared'
const Vue = { ...runtimeDom, ...runtimeVapor }
@@ -1438,6 +1443,9 @@ describe('Vapor Mode hydration', () => {
})
describe('for', () => {
+ const forAnchorLabel = FOR_ANCHOR_LABEL
+ const slotAnchorLabel = SLOT_ANCHOR_LABEL
+
test('basic v-for', async () => {
const { container, data } = await testHydration(
`
@@ -1454,7 +1462,7 @@ describe('Vapor Mode hydration', () => {
`a` +
`b` +
`c` +
- `` +
+ `` +
``,
)
@@ -1466,8 +1474,9 @@ describe('Vapor Mode hydration', () => {
`a` +
`b` +
`c` +
- `d` +
`` +
+ `d` +
+ `` +
``,
)
})
@@ -1483,13 +1492,23 @@ describe('Vapor Mode hydration', () => {
ref(['a', 'b', 'c']),
)
expect(container.innerHTML).toBe(
- `abc
`,
+ `` +
+ `` +
+ `abc` +
+ `` +
+ `
`,
)
data.value.push('d')
await nextTick()
expect(container.innerHTML).toBe(
- `abcd
`,
+ `` +
+ `` +
+ `abc` +
+ `` +
+ `d` +
+ `` +
+ `
`,
)
})
@@ -1512,7 +1531,7 @@ describe('Vapor Mode hydration', () => {
`a` +
`b` +
`c` +
- `` +
+ `` +
`` +
``,
)
@@ -1526,8 +1545,9 @@ describe('Vapor Mode hydration', () => {
`a` +
`b` +
`c` +
- `d` +
`` +
+ `d` +
+ `` +
`` +
``,
)
@@ -1540,8 +1560,9 @@ describe('Vapor Mode hydration', () => {
`` +
`b` +
`c` +
- `d` +
`` +
+ `d` +
+ `` +
`` +
``,
)
@@ -1567,12 +1588,12 @@ describe('Vapor Mode hydration', () => {
`a` +
`b` +
`c` +
- `` +
+ `` +
`` +
`a` +
`b` +
`c` +
- `` +
+ `` +
`` +
``,
)
@@ -1586,14 +1607,16 @@ describe('Vapor Mode hydration', () => {
`a` +
`b` +
`c` +
- `d` +
`` +
+ `d` +
+ `` +
`` +
`a` +
`b` +
`c` +
- `d` +
`` +
+ `d` +
+ `` +
`` +
``,
)
@@ -1605,12 +1628,14 @@ describe('Vapor Mode hydration', () => {
`` +
`` +
`c` +
- `d` +
`` +
+ `d` +
+ `` +
`` +
`c` +
- `d` +
`` +
+ `d` +
+ `` +
`` +
``,
)
@@ -1635,7 +1660,7 @@ describe('Vapor Mode hydration', () => {
`comp
` +
`comp
` +
`comp
` +
- `` +
+ `` +
``,
)
@@ -1647,8 +1672,9 @@ describe('Vapor Mode hydration', () => {
`comp
` +
`comp
` +
`comp
` +
- `comp
` +
`` +
+ `comp
` +
+ `` +
``,
)
})
@@ -1670,10 +1696,10 @@ describe('Vapor Mode hydration', () => {
expect(container.innerHTML).toBe(
`` +
`` +
- `a` +
- `b` +
- `c` +
- `` +
+ `a` +
+ `b` +
+ `c` +
+ `` +
`
`,
)
@@ -1682,11 +1708,12 @@ describe('Vapor Mode hydration', () => {
expect(container.innerHTML).toBe(
`` +
`` +
- `a` +
- `b` +
- `c` +
- `d` +
+ `a` +
+ `b` +
+ `c` +
`` +
+ `d` +
+ `` +
`
`,
)
})
@@ -1709,7 +1736,7 @@ describe('Vapor Mode hydration', () => {
`foo
-bar-` +
`foo
-bar-` +
`foo
-bar-` +
- `` +
+ `` +
``,
)
@@ -1721,18 +1748,17 @@ describe('Vapor Mode hydration', () => {
`foo
-bar-` +
`foo
-bar-` +
`foo
-bar-` +
- `foo
-bar-` +
`` +
+ `foo
-bar-` +
+ `` +
``,
)
})
-
- // TODO wait for vapor TransitionGroup support
- // v-for inside TransitionGroup does not render as a fragment
- test.todo('v-for in TransitionGroup', async () => {})
})
describe('slots', () => {
+ const slotAnchorLabel = SLOT_ANCHOR_LABEL
+ const forAnchorLabel = FOR_ANCHOR_LABEL
test('basic slot', async () => {
const { data, container } = await testHydration(
`
@@ -1745,13 +1771,13 @@ describe('Vapor Mode hydration', () => {
},
)
expect(container.innerHTML).toBe(
- `foo`,
+ `foo`,
)
data.value = 'bar'
await nextTick()
expect(container.innerHTML).toBe(
- `bar`,
+ `bar`,
)
})
@@ -1769,13 +1795,13 @@ describe('Vapor Mode hydration', () => {
},
)
expect(container.innerHTML).toBe(
- `foo`,
+ `foo`,
)
data.value = 'bar'
await nextTick()
expect(container.innerHTML).toBe(
- `bar`,
+ `bar`,
)
})
@@ -1793,12 +1819,14 @@ describe('Vapor Mode hydration', () => {
},
)
expect(container.innerHTML).toBe(
- `foo`,
+ `foo`,
)
data.value = false
await nextTick()
- expect(container.innerHTML).toBe(``)
+ expect(container.innerHTML).toBe(
+ ``,
+ )
})
test('named slot with v-if and v-for', async () => {
@@ -1821,15 +1849,15 @@ describe('Vapor Mode hydration', () => {
)
expect(container.innerHTML).toBe(
`` +
- `abc` +
+ `abc` +
`` +
- ``,
+ ``,
)
data.show = false
await nextTick()
expect(container.innerHTML).toBe(
- ``,
+ ``,
)
})
@@ -1852,7 +1880,7 @@ describe('Vapor Mode hydration', () => {
`foo` +
`` +
`` +
- ``,
+ ``,
)
data.value = 'bar'
@@ -1863,7 +1891,7 @@ describe('Vapor Mode hydration', () => {
`bar` +
`` +
`` +
- ``,
+ ``,
)
})
@@ -1896,7 +1924,7 @@ describe('Vapor Mode hydration', () => {
`foo` +
`` +
`` +
- `` +
+ `` +
`` +
``,
)
@@ -1912,14 +1940,13 @@ describe('Vapor Mode hydration', () => {
`bar` +
`` +
`` +
- `` +
+ `` +
`` +
``,
)
})
- // problem is next child is incorrect after slot
- test.todo('mixed slot and text node', async () => {
+ test('mixed slot and text node', async () => {
const data = reactive({
text: 'foo',
msg: 'hi',
@@ -1937,11 +1964,11 @@ describe('Vapor Mode hydration', () => {
)
expect(container.innerHTML).toMatchInlineSnapshot(
- `"foohi
"`,
+ `"foohi
"`,
)
})
- test.todo('mixed slot and element', async () => {
+ test('mixed slot and element', async () => {
const data = reactive({
text: 'foo',
msg: 'hi',
@@ -1959,14 +1986,272 @@ describe('Vapor Mode hydration', () => {
)
expect(container.innerHTML).toMatchInlineSnapshot(
- `""`,
+ `""`,
)
})
- // mixed slot and component
- // mixed slot and fragment component
- // mixed slot and v-if
- // mixed slot and v-for
+ test('mixed slot and component', async () => {
+ const data = reactive({
+ msg1: 'foo',
+ msg2: 'bar',
+ })
+ const { container } = await testHydration(
+ `
+
+ {{data.msg1}}
+
+ `,
+ {
+ Child: `
+
+
+
+
+
+
+ `,
+ Child2: `
+
+ {{data.msg2}}
+ `,
+ },
+ data,
+ )
+ expect(container.innerHTML).toBe(
+ `` +
+ `
bar
` +
+ `
foo` +
+ `
bar
` +
+ `
`,
+ )
+ data.msg2 = 'hello'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `` +
+ `
hello
` +
+ `
foo` +
+ `
hello
` +
+ `
`,
+ )
+ })
+
+ test('mixed slot and fragment component', async () => {
+ const data = reactive({
+ msg1: 'foo',
+ msg2: 'bar',
+ })
+ const { container } = await testHydration(
+ `
+
+ {{data.msg1}}
+
+ `,
+ {
+ Child: `
+
+
+
+
+
+
+ `,
+ Child2: `
+
+ {{data.msg1}}
{{data.msg2}}
+ `,
+ },
+ data,
+ )
+ expect(container.innerHTML).toBe(
+ `` +
+ `
foo
bar` +
+ `
foo` +
+ `
foo
bar` +
+ `
`,
+ )
+
+ data.msg1 = 'hello'
+ data.msg2 = 'vapor'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `` +
+ `
hello
vapor` +
+ `
hello` +
+ `
hello
vapor` +
+ `
`,
+ )
+ })
+
+ test('mixed slot and v-if', async () => {
+ const data = reactive({
+ show: true,
+ msg: 'foo',
+ })
+ const { container } = await testHydration(
+ `
+
+ {{data.msg}}
+
+ `,
+ {
+ Child: `
+
+ {{data.msg}}
+
+ {{data.msg}}
+ `,
+ },
+ data,
+ )
+
+ expect(container.innerHTML).toBe(
+ `` +
+ `foo
` +
+ `foo` +
+ `foo
` +
+ ``,
+ )
+
+ data.show = false
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `` +
+ `` +
+ `foo` +
+ `` +
+ ``,
+ )
+ })
+
+ test('mixed slot and v-for', async () => {
+ const data = reactive({
+ items: ['a', 'b', 'c'],
+ msg: 'foo',
+ })
+ const { container } = await testHydration(
+ `
+
+ {{data.msg}}
+
+ `,
+ {
+ Child: `
+
+ {{item}}
+
+ {{item}}
+ `,
+ },
+ data,
+ )
+
+ expect(container.innerHTML).toBe(
+ `` +
+ `a
b
c
` +
+ `foo` +
+ `a
b
c
` +
+ ``,
+ )
+
+ data.items.push('d')
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `` +
+ `a
b
c
d
` +
+ `foo` +
+ `a
b
c
d
` +
+ ``,
+ )
+ })
+
+ test('consecutive slots', async () => {
+ const data = reactive({
+ msg1: 'foo',
+ msg2: 'bar',
+ })
+
+ const { container } = await testHydration(
+ `
+
+ {{data.msg1}}
+
+ {{data.msg2}}
+
+
+ `,
+ {
+ Child: ``,
+ },
+ data,
+ )
+
+ expect(container.innerHTML).toBe(
+ `` +
+ `foo` +
+ `bar` +
+ ``,
+ )
+
+ data.msg1 = 'hello'
+ data.msg2 = 'vapor'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `` +
+ `hello` +
+ `vapor` +
+ ``,
+ )
+ })
+
+ test('consecutive slots with anchor insertion', async () => {
+ const data = reactive({
+ msg1: 'foo',
+ msg2: 'bar',
+ })
+
+ const { container } = await testHydration(
+ `
+
+ {{data.msg1}}
+
+ {{data.msg2}}
+
+
+ `,
+ {
+ Child: `
+
+
+
+
+
+
+ `,
+ },
+ data,
+ )
+
+ expect(container.innerHTML).toBe(
+ `` +
+ `` +
+ `foo` +
+ `bar` +
+ `` +
+ `
`,
+ )
+
+ data.msg1 = 'hello'
+ data.msg2 = 'vapor'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `` +
+ `` +
+ `hello` +
+ `vapor` +
+ `` +
+ `
`,
+ )
+ })
})
// test('element with ref', () => {
diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts
index 54b65d50b..704b01973 100644
--- a/packages/runtime-vapor/src/apiCreateFor.ts
+++ b/packages/runtime-vapor/src/apiCreateFor.ts
@@ -19,7 +19,7 @@ import {
import {
createComment,
createTextNode,
- nextVaporFragmentAnchor,
+ findVaporFragmentAnchor,
} from './dom/node'
import {
type Block,
@@ -34,7 +34,6 @@ import { renderEffect } from './renderEffect'
import { VaporVForFlags } from '../../shared/src/vaporFlags'
import {
currentHydrationNode,
- isComment,
isHydrating,
locateHydrationNode,
} from './dom/hydration'
@@ -99,15 +98,20 @@ export const createFor = (
let oldBlocks: ForBlock[] = []
let newBlocks: ForBlock[]
let parent: ParentNode | undefined | null
- const parentAnchor = isHydrating
- ? // Use fragment end anchor if available, otherwise use the specific for anchor.
- nextVaporFragmentAnchor(
- currentHydrationNode!,
- isComment(currentHydrationNode!, '[') ? ']' : FOR_ANCHOR_LABEL,
- )!
- : __DEV__
- ? createComment('for')
- : createTextNode()
+ let parentAnchor: Node
+ if (isHydrating) {
+ parentAnchor = findVaporFragmentAnchor(
+ currentHydrationNode!,
+ FOR_ANCHOR_LABEL,
+ )!
+ if (__DEV__ && !parentAnchor) {
+ // TODO warn, should not happen
+ warn(`createFor anchor not found...`)
+ }
+ } else {
+ parentAnchor = __DEV__ ? createComment('for') : createTextNode()
+ }
+
const frag = new VaporFragment(oldBlocks)
const instance = currentInstance!
const canUseFastRemove = flags & VaporVForFlags.FAST_REMOVE
diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts
index c846cc872..987949646 100644
--- a/packages/runtime-vapor/src/block.ts
+++ b/packages/runtime-vapor/src/block.ts
@@ -8,7 +8,7 @@ import {
import {
createComment,
createTextNode,
- nextVaporFragmentAnchor,
+ findVaporFragmentAnchor,
} from './dom/node'
import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
import {
@@ -99,7 +99,7 @@ export class DynamicFragment extends VaporFragment {
this.anchor = currentHydrationNode
} else {
// find next sibling dynamic fragment end anchor
- const anchor = nextVaporFragmentAnchor(currentHydrationNode!, label)!
+ const anchor = findVaporFragmentAnchor(currentHydrationNode!, label)!
if (anchor) {
this.anchor = anchor
} else if (__DEV__) {
diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 548babebf..03675475b 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -59,7 +59,11 @@ import {
} from './componentSlots'
import { hmrReload, hmrRerender } from './hmr'
import { isHydrating, locateHydrationNode } from './dom/hydration'
-import { insertionAnchor, insertionParent } from './insertionState'
+import {
+ insertionAnchor,
+ insertionParent,
+ resetInsertionState,
+} from './insertionState'
export { currentInstance } from '@vue/runtime-dom'
@@ -142,6 +146,8 @@ export function createComponent(
const _insertionAnchor = insertionAnchor
if (isHydrating) {
locateHydrationNode()
+ } else {
+ resetInsertionState()
}
// vdom interop enabled and component is not an explicit vapor component
diff --git a/packages/runtime-vapor/src/dom/hydration.ts b/packages/runtime-vapor/src/dom/hydration.ts
index 2506a5c8f..3ed3ee2b8 100644
--- a/packages/runtime-vapor/src/dom/hydration.ts
+++ b/packages/runtime-vapor/src/dom/hydration.ts
@@ -6,7 +6,7 @@ import {
setInsertionState,
} from '../insertionState'
import {
- child,
+ _child,
disableHydrationNodeLookup,
enableHydrationNodeLookup,
next,
@@ -28,6 +28,7 @@ export function withHydration(container: ParentNode, fn: () => void): void {
if (!isOptimized) {
// optimize anchor cache lookup
;(Comment.prototype as any).$fs = undefined
+ ;(Node.prototype as any).$nc = undefined
isOptimized = true
}
enableHydrationNodeLookup()
@@ -87,19 +88,17 @@ function locateHydrationNodeImpl(hasFragmentAnchor?: boolean) {
let node: Node | null
// prepend / firstChild
if (insertionAnchor === 0) {
- node = child(insertionParent!)
+ node = _child(insertionParent!)
} else if (insertionAnchor) {
// for dynamic children, use insertionAnchor as the node
node = insertionAnchor
} else {
- node = insertionParent ? insertionParent.lastChild : currentHydrationNode
+ node = insertionParent
+ ? insertionParent.$nc || insertionParent.lastChild
+ : currentHydrationNode
- // if current node is fragment start anchor, find the next one
- if (node && isComment(node, '[')) {
- node = node.nextSibling
- }
// if the last child is a vapor fragment end anchor, find the previous one
- else if (hasFragmentAnchor && node && isVaporFragmentEndAnchor(node)) {
+ if (hasFragmentAnchor && node && isVaporFragmentEndAnchor(node)) {
node = node.previousSibling
if (__DEV__ && !node) {
// TODO warning, should not happen
@@ -135,6 +134,10 @@ function locateHydrationNodeImpl(hasFragmentAnchor?: boolean) {
}
}
}
+
+ if (insertionParent && node) {
+ insertionParent.$nc = node!.previousSibling
+ }
}
if (__DEV__ && !node) {
diff --git a/packages/runtime-vapor/src/dom/node.ts b/packages/runtime-vapor/src/dom/node.ts
index 18cfd3fc9..b19229445 100644
--- a/packages/runtime-vapor/src/dom/node.ts
+++ b/packages/runtime-vapor/src/dom/node.ts
@@ -22,10 +22,42 @@ export function querySelector(selectors: string): Element | null {
}
/*! #__NO_SIDE_EFFECTS__ */
-export function child(node: ParentNode): Node {
+export function _child(node: ParentNode): Node {
return node.firstChild!
}
+/*! #__NO_SIDE_EFFECTS__ */
+export function __child(node: ParentNode): Node {
+ /**
+ * During hydration, the first child of a node not be the expected
+ * if the first child is slot
+ *
+ * for template code: `div>{{ data }}`
+ * - slot: 'slot',
+ * - data: 'hi',
+ *
+ * client side:
+ * const n2 = _template("
")()
+ * const n1 = _child(n2) -> the text node
+ * _setInsertionState(n2, 0) -> slot fragment
+ *
+ * during hydration:
+ * const n2 = _template("slotHi
")()
+ * const n1 = _child(n2) -> should be `Hi` instead of the slot fragment
+ * _setInsertionState(n2, 0) -> slot fragment
+ */
+ let n = node.firstChild!
+
+ if (isComment(n, '[')) {
+ n = locateEndAnchor(n)!.nextSibling!
+ }
+
+ while (n && isVaporFragmentEndAnchor(n)) {
+ n = n.nextSibling!
+ }
+ return n
+}
+
/*! #__NO_SIDE_EFFECTS__ */
export function _nthChild(node: Node, i: number): Node {
return node.childNodes[i]
@@ -56,9 +88,13 @@ export function __next(node: Node): Node {
return n
}
+type ChildFn = (node: ParentNode) => Node
type NextFn = (node: Node) => Node
type NthChildFn = (node: Node, i: number) => Node
+interface DelegatedChildFunction extends ChildFn {
+ impl: ChildFn
+}
interface DelegatedNextFunction extends NextFn {
impl: NextFn
}
@@ -66,6 +102,12 @@ interface DelegatedNthChildFunction extends NthChildFn {
impl: NthChildFn
}
+/*! #__NO_SIDE_EFFECTS__ */
+export const child: DelegatedChildFunction = node => {
+ return child.impl(node)
+}
+child.impl = _child
+
/*! #__NO_SIDE_EFFECTS__ */
export const next: DelegatedNextFunction = node => {
return next.impl(node)
@@ -90,11 +132,13 @@ nthChild.impl = _nthChild
// of `next` and `nthChild`. After hydration is complete, their implementations
// are restored to the original versions.
export function enableHydrationNodeLookup(): void {
+ child.impl = __child
next.impl = __next
nthChild.impl = __nthChild
}
export function disableHydrationNodeLookup(): void {
+ child.impl = _child
next.impl = _next
nthChild.impl = _nthChild
}
@@ -112,15 +156,10 @@ function isNonHydrationNode(node: Node) {
)
}
-export function nextVaporFragmentAnchor(
+export function findVaporFragmentAnchor(
node: Node,
anchorLabel: string,
): Comment | null {
- node = handleWrappedNode(node)
- if (isComment(node, anchorLabel)) {
- return node as Comment
- }
-
let n = node.nextSibling
while (n) {
if (isComment(n, anchorLabel)) return n
diff --git a/packages/runtime-vapor/src/insertionState.ts b/packages/runtime-vapor/src/insertionState.ts
index c8c7ffbcd..b33c820e0 100644
--- a/packages/runtime-vapor/src/insertionState.ts
+++ b/packages/runtime-vapor/src/insertionState.ts
@@ -1,4 +1,9 @@
-export let insertionParent: ParentNode | undefined
+export let insertionParent:
+ | (ParentNode & {
+ // the next child node to be hydrated
+ $nc?: Node | null
+ })
+ | undefined
export let insertionAnchor: Node | 0 | undefined
/**
diff --git a/packages/server-renderer/src/helpers/ssrRenderSlot.ts b/packages/server-renderer/src/helpers/ssrRenderSlot.ts
index b8a57ae8d..0733c8233 100644
--- a/packages/server-renderer/src/helpers/ssrRenderSlot.ts
+++ b/packages/server-renderer/src/helpers/ssrRenderSlot.ts
@@ -104,7 +104,7 @@ export function ssrRenderSlotInner(
if (
transition &&
slotBuffer[0] === '' &&
- slotBuffer[end - 1] === ''
+ (slotBuffer[end - 1] as string).startsWith('')
) {
start++
end--