data,
)
- expect(container.innerHTML).toMatchInlineSnapshot(
- `"<div><!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->hi</div>"`,
+ expect(container.innerHTML).toBe(
+ `<div>` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `hi` +
+ `</div>`,
+ )
+
+ data.msg = 'bar'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<div>` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `bar` +
+ `</div>`,
+ )
+ })
+
+ test('mixed root slot and text node', async () => {
+ const data = reactive({
+ text: 'foo',
+ msg: 'hi',
+ })
+ const { container } = await testHydration(
+ `<template>
+ <components.Child>
+ <span>{{data.text}}</span>
+ </components.Child>
+ </template>`,
+ {
+ Child: `<template>{{data.text}}<slot/>{{data.msg}}</template>`,
+ },
+ data,
+ )
+
+ expect(container.innerHTML).toBe(
+ `<!--[-->` +
+ `foo` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `hi` +
+ `<!--]-->`,
+ )
+
+ data.msg = 'bar'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<!--[-->` +
+ `foo` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `bar` +
+ `<!--]-->`,
)
})
data,
)
- expect(container.innerHTML).toMatchInlineSnapshot(
- `"<div><!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}--><div>hi</div></div>"`,
+ expect(container.innerHTML).toBe(
+ `<div>` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<div>hi</div>` +
+ `</div>`,
+ )
+
+ data.msg = 'bar'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<div>` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<div>bar</div>` +
+ `</div>`,
)
})
`<div>bar</div>` +
`</div>`,
)
+
data.msg2 = 'hello'
await nextTick()
expect(container.innerHTML).toBe(
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(
isObject,
isString,
} from '@vue/shared'
-import {
- createComment,
- createTextNode,
- findVaporFragmentAnchor,
-} from './dom/node'
+import { createComment, createTextNode } from './dom/node'
import {
type Block,
VaporFragment,
import { VaporVForFlags } from '../../shared/src/vaporFlags'
import {
currentHydrationNode,
+ findVaporFragmentAnchor,
isHydrating,
locateHydrationNode,
} from './dom/hydration'
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))
}
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,
}
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
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)
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
}
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
+}
-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'
* _setInsertionState(n2, 0) -> slot fragment
*
* during hydration:
- * const n2 = _template("<div><!--[-->slot<!--]--><!--slot-->Hi</div>")()
+ * const n2 = <div><!--[-->slot<!--]--><!--slot-->Hi</div> // server output
* const n1 = _child(n2) -> should be `Hi` instead of the slot fragment
* _setInsertionState(n2, 0) -> slot fragment
*/
/*! #__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)) {
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
-}