diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts
index b0878e139..879257e4d 100644
--- a/packages/runtime-vapor/__tests__/hydration.spec.ts
+++ b/packages/runtime-vapor/__tests__/hydration.spec.ts
@@ -1963,8 +1963,56 @@ describe('Vapor Mode hydration', () => {
data,
)
- expect(container.innerHTML).toMatchInlineSnapshot(
- `"
foohi
"`,
+ expect(container.innerHTML).toBe(
+ `` +
+ `foo` +
+ `hi` +
+ `
`,
+ )
+
+ data.msg = 'bar'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `` +
+ `foo` +
+ `bar` +
+ `
`,
+ )
+ })
+
+ test('mixed root slot and text node', async () => {
+ const data = reactive({
+ text: 'foo',
+ msg: 'hi',
+ })
+ const { container } = await testHydration(
+ `
+
+ {{data.text}}
+
+ `,
+ {
+ Child: `{{data.text}}{{data.msg}}`,
+ },
+ data,
+ )
+
+ expect(container.innerHTML).toBe(
+ `` +
+ `foo` +
+ `foo` +
+ `hi` +
+ ``,
+ )
+
+ data.msg = 'bar'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `` +
+ `foo` +
+ `foo` +
+ `bar` +
+ ``,
)
})
@@ -1985,8 +2033,20 @@ describe('Vapor Mode hydration', () => {
data,
)
- expect(container.innerHTML).toMatchInlineSnapshot(
- `""`,
+ expect(container.innerHTML).toBe(
+ `` +
+ `
foo` +
+ `
hi
` +
+ `
`,
+ )
+
+ data.msg = 'bar'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `` +
+ `
foo` +
+ `
bar
` +
+ `
`,
)
})
@@ -2024,6 +2084,7 @@ describe('Vapor Mode hydration', () => {
`bar
` +
``,
)
+
data.msg2 = 'hello'
await nextTick()
expect(container.innerHTML).toBe(
diff --git a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts
index e272f0d22..7ae689a0a 100644
--- a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts
+++ b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts
@@ -22,7 +22,10 @@ export function createDynamicComponent(
const _insertionAnchor = insertionAnchor
if (!isHydrating) resetInsertionState()
- const frag = new DynamicFragment(DYNAMIC_COMPONENT_ANCHOR_LABEL)
+ const frag =
+ isHydrating || __DEV__
+ ? new DynamicFragment(DYNAMIC_COMPONENT_ANCHOR_LABEL)
+ : new DynamicFragment()
renderEffect(() => {
const value = getter()
frag.update(
diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts
index 704b01973..06a2bf752 100644
--- a/packages/runtime-vapor/src/apiCreateFor.ts
+++ b/packages/runtime-vapor/src/apiCreateFor.ts
@@ -16,11 +16,7 @@ import {
isObject,
isString,
} from '@vue/shared'
-import {
- createComment,
- createTextNode,
- findVaporFragmentAnchor,
-} from './dom/node'
+import { createComment, createTextNode } from './dom/node'
import {
type Block,
VaporFragment,
@@ -34,6 +30,7 @@ import { renderEffect } from './renderEffect'
import { VaporVForFlags } from '../../shared/src/vaporFlags'
import {
currentHydrationNode,
+ findVaporFragmentAnchor,
isHydrating,
locateHydrationNode,
} from './dom/hydration'
diff --git a/packages/runtime-vapor/src/apiCreateIf.ts b/packages/runtime-vapor/src/apiCreateIf.ts
index bec4a3504..3e370592b 100644
--- a/packages/runtime-vapor/src/apiCreateIf.ts
+++ b/packages/runtime-vapor/src/apiCreateIf.ts
@@ -22,7 +22,10 @@ export function createIf(
if (once) {
frag = condition() ? b1() : b2 ? b2() : []
} else {
- frag = new DynamicFragment(IF_ANCHOR_LABEL)
+ frag =
+ isHydrating || __DEV__
+ ? new DynamicFragment(IF_ANCHOR_LABEL)
+ : new DynamicFragment()
renderEffect(() => (frag as DynamicFragment).update(condition() ? b1 : b2))
}
diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts
index 987949646..bf77b9b09 100644
--- a/packages/runtime-vapor/src/block.ts
+++ b/packages/runtime-vapor/src/block.ts
@@ -5,14 +5,11 @@ import {
mountComponent,
unmountComponent,
} from './component'
-import {
- createComment,
- createTextNode,
- findVaporFragmentAnchor,
-} from './dom/node'
+import { createComment, createTextNode } from './dom/node'
import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
import {
currentHydrationNode,
+ findVaporFragmentAnchor,
isComment,
isHydrating,
locateHydrationNode,
@@ -92,13 +89,11 @@ export class DynamicFragment extends VaporFragment {
}
hydrate(label: string): void {
- // for v-if="false" the hydrationNode will be a empty comment node
- // use it as anchor.
- // otherwise, use the next sibling comment node as anchor
+ // for `v-if="false"` the node will be an empty comment node use it as the anchor.
+ // otherwise, find next sibling vapor fragment anchor
if (isComment(currentHydrationNode!, '')) {
this.anchor = currentHydrationNode
} else {
- // find next sibling dynamic fragment end anchor
const anchor = findVaporFragmentAnchor(currentHydrationNode!, label)!
if (anchor) {
this.anchor = anchor
diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
index 093ed7fb0..2b2de6072 100644
--- a/packages/runtime-vapor/src/componentSlots.ts
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -124,7 +124,10 @@ export function createSlot(
fallback,
)
} else {
- fragment = new DynamicFragment(SLOT_ANCHOR_LABEL)
+ fragment =
+ isHydrating || __DEV__
+ ? new DynamicFragment(SLOT_ANCHOR_LABEL)
+ : new DynamicFragment()
const isDynamicName = isFunction(name)
const renderSlot = () => {
const slot = getSlot(rawSlots, isFunction(name) ? name() : name)
diff --git a/packages/runtime-vapor/src/dom/hydration.ts b/packages/runtime-vapor/src/dom/hydration.ts
index 9b2e0efa7..d82aa33f9 100644
--- a/packages/runtime-vapor/src/dom/hydration.ts
+++ b/packages/runtime-vapor/src/dom/hydration.ts
@@ -11,7 +11,7 @@ import {
enableHydrationNodeLookup,
next,
} from './node'
-import { isVaporFragmentEndAnchor } from '@vue/shared'
+import { isDynamicAnchor, isVaporFragmentEndAnchor } from '@vue/shared'
export let isHydrating = false
export let currentHydrationNode: Node | null = null
@@ -191,3 +191,29 @@ export function locateEndAnchor(
}
return null
}
+
+export function isNonHydrationNode(node: Node): boolean {
+ return (
+ // empty text nodes
+ isEmptyText(node) ||
+ // dynamic node anchors (, )
+ isDynamicAnchor(node) ||
+ // fragment end anchor (``)
+ isComment(node, ']') ||
+ // vapor fragment end anchors
+ isVaporFragmentEndAnchor(node)
+ )
+}
+
+export function findVaporFragmentAnchor(
+ node: Node,
+ anchorLabel: string,
+): Comment | null {
+ let n = node.nextSibling
+ while (n) {
+ if (isComment(n, anchorLabel)) return n
+ n = n.nextSibling
+ }
+
+ return null
+}
diff --git a/packages/runtime-vapor/src/dom/node.ts b/packages/runtime-vapor/src/dom/node.ts
index b19229445..2384697ed 100644
--- a/packages/runtime-vapor/src/dom/node.ts
+++ b/packages/runtime-vapor/src/dom/node.ts
@@ -1,8 +1,7 @@
-import { isComment, isEmptyText, locateEndAnchor } from './hydration'
+import { isComment, isNonHydrationNode, locateEndAnchor } from './hydration'
import {
DYNAMIC_END_ANCHOR_LABEL,
DYNAMIC_START_ANCHOR_LABEL,
- isDynamicAnchor,
isVaporFragmentEndAnchor,
} from '@vue/shared'
@@ -42,7 +41,7 @@ export function __child(node: ParentNode): Node {
* _setInsertionState(n2, 0) -> slot fragment
*
* during hydration:
- * const n2 = _template("slotHi
")()
+ * const n2 = slotHi
// server output
* const n1 = _child(n2) -> should be `Hi` instead of the slot fragment
* _setInsertionState(n2, 0) -> slot fragment
*/
@@ -79,7 +78,19 @@ function _next(node: Node): Node {
/*! #__NO_SIDE_EFFECTS__ */
export function __next(node: Node): Node {
- node = handleWrappedNode(node)
+ // process dynamic node (...) as a single node
+ if (isComment(node, DYNAMIC_START_ANCHOR_LABEL)) {
+ node = locateEndAnchor(
+ node,
+ DYNAMIC_START_ANCHOR_LABEL,
+ DYNAMIC_END_ANCHOR_LABEL,
+ )!
+ }
+
+ // process fragment (...) as a single node
+ else if (isComment(node, '[')) {
+ node = locateEndAnchor(node)!
+ }
let n = node.nextSibling!
while (n && isNonHydrationNode(n)) {
@@ -142,47 +153,3 @@ export function disableHydrationNodeLookup(): void {
next.impl = _next
nthChild.impl = _nthChild
}
-
-function isNonHydrationNode(node: Node) {
- return (
- // empty text nodes, no need to hydrate
- isEmptyText(node) ||
- // dynamic node anchors (, )
- isDynamicAnchor(node) ||
- // fragment end anchor (``)
- isComment(node, ']') ||
- // vapor fragment end anchors
- isVaporFragmentEndAnchor(node)
- )
-}
-
-export function findVaporFragmentAnchor(
- node: Node,
- anchorLabel: string,
-): Comment | null {
- let n = node.nextSibling
- while (n) {
- if (isComment(n, anchorLabel)) return n
- n = n.nextSibling
- }
-
- return null
-}
-
-function handleWrappedNode(node: Node): Node {
- // process dynamic node (...) as a single one
- if (isComment(node, DYNAMIC_START_ANCHOR_LABEL)) {
- return locateEndAnchor(
- node,
- DYNAMIC_START_ANCHOR_LABEL,
- DYNAMIC_END_ANCHOR_LABEL,
- )!
- }
-
- // process fragment (...) as a single one
- else if (isComment(node, '[')) {
- return locateEndAnchor(node)!
- }
-
- return node
-}