wip: update
This commit is contained in:
parent
e5399c3418
commit
4253b0ce3e
|
@ -1963,8 +1963,56 @@ describe('Vapor Mode hydration', () => {
|
||||||
data,
|
data,
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
expect(container.innerHTML).toBe(
|
||||||
`"<div><!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->hi</div>"`,
|
`<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` +
|
||||||
|
`<!--]-->`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1985,8 +2033,20 @@ describe('Vapor Mode hydration', () => {
|
||||||
data,
|
data,
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
expect(container.innerHTML).toBe(
|
||||||
`"<div><!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}--><div>hi</div></div>"`,
|
`<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>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2024,6 +2084,7 @@ describe('Vapor Mode hydration', () => {
|
||||||
`<div>bar</div>` +
|
`<div>bar</div>` +
|
||||||
`</div>`,
|
`</div>`,
|
||||||
)
|
)
|
||||||
|
|
||||||
data.msg2 = 'hello'
|
data.msg2 = 'hello'
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(container.innerHTML).toBe(
|
expect(container.innerHTML).toBe(
|
||||||
|
|
|
@ -22,7 +22,10 @@ export function createDynamicComponent(
|
||||||
const _insertionAnchor = insertionAnchor
|
const _insertionAnchor = insertionAnchor
|
||||||
if (!isHydrating) resetInsertionState()
|
if (!isHydrating) resetInsertionState()
|
||||||
|
|
||||||
const frag = new DynamicFragment(DYNAMIC_COMPONENT_ANCHOR_LABEL)
|
const frag =
|
||||||
|
isHydrating || __DEV__
|
||||||
|
? new DynamicFragment(DYNAMIC_COMPONENT_ANCHOR_LABEL)
|
||||||
|
: new DynamicFragment()
|
||||||
renderEffect(() => {
|
renderEffect(() => {
|
||||||
const value = getter()
|
const value = getter()
|
||||||
frag.update(
|
frag.update(
|
||||||
|
|
|
@ -16,11 +16,7 @@ import {
|
||||||
isObject,
|
isObject,
|
||||||
isString,
|
isString,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import {
|
import { createComment, createTextNode } from './dom/node'
|
||||||
createComment,
|
|
||||||
createTextNode,
|
|
||||||
findVaporFragmentAnchor,
|
|
||||||
} from './dom/node'
|
|
||||||
import {
|
import {
|
||||||
type Block,
|
type Block,
|
||||||
VaporFragment,
|
VaporFragment,
|
||||||
|
@ -34,6 +30,7 @@ import { renderEffect } from './renderEffect'
|
||||||
import { VaporVForFlags } from '../../shared/src/vaporFlags'
|
import { VaporVForFlags } from '../../shared/src/vaporFlags'
|
||||||
import {
|
import {
|
||||||
currentHydrationNode,
|
currentHydrationNode,
|
||||||
|
findVaporFragmentAnchor,
|
||||||
isHydrating,
|
isHydrating,
|
||||||
locateHydrationNode,
|
locateHydrationNode,
|
||||||
} from './dom/hydration'
|
} from './dom/hydration'
|
||||||
|
|
|
@ -22,7 +22,10 @@ export function createIf(
|
||||||
if (once) {
|
if (once) {
|
||||||
frag = condition() ? b1() : b2 ? b2() : []
|
frag = condition() ? b1() : b2 ? b2() : []
|
||||||
} else {
|
} 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))
|
renderEffect(() => (frag as DynamicFragment).update(condition() ? b1 : b2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,11 @@ import {
|
||||||
mountComponent,
|
mountComponent,
|
||||||
unmountComponent,
|
unmountComponent,
|
||||||
} from './component'
|
} from './component'
|
||||||
import {
|
import { createComment, createTextNode } from './dom/node'
|
||||||
createComment,
|
|
||||||
createTextNode,
|
|
||||||
findVaporFragmentAnchor,
|
|
||||||
} from './dom/node'
|
|
||||||
import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
|
import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
|
||||||
import {
|
import {
|
||||||
currentHydrationNode,
|
currentHydrationNode,
|
||||||
|
findVaporFragmentAnchor,
|
||||||
isComment,
|
isComment,
|
||||||
isHydrating,
|
isHydrating,
|
||||||
locateHydrationNode,
|
locateHydrationNode,
|
||||||
|
@ -92,13 +89,11 @@ export class DynamicFragment extends VaporFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrate(label: string): void {
|
hydrate(label: string): void {
|
||||||
// for v-if="false" the hydrationNode will be a empty comment node
|
// for `v-if="false"` the node will be an empty comment node use it as the anchor.
|
||||||
// use it as anchor.
|
// otherwise, find next sibling vapor fragment anchor
|
||||||
// otherwise, use the next sibling comment node as anchor
|
|
||||||
if (isComment(currentHydrationNode!, '')) {
|
if (isComment(currentHydrationNode!, '')) {
|
||||||
this.anchor = currentHydrationNode
|
this.anchor = currentHydrationNode
|
||||||
} else {
|
} else {
|
||||||
// find next sibling dynamic fragment end anchor
|
|
||||||
const anchor = findVaporFragmentAnchor(currentHydrationNode!, label)!
|
const anchor = findVaporFragmentAnchor(currentHydrationNode!, label)!
|
||||||
if (anchor) {
|
if (anchor) {
|
||||||
this.anchor = anchor
|
this.anchor = anchor
|
||||||
|
|
|
@ -124,7 +124,10 @@ export function createSlot(
|
||||||
fallback,
|
fallback,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
fragment = new DynamicFragment(SLOT_ANCHOR_LABEL)
|
fragment =
|
||||||
|
isHydrating || __DEV__
|
||||||
|
? new DynamicFragment(SLOT_ANCHOR_LABEL)
|
||||||
|
: new DynamicFragment()
|
||||||
const isDynamicName = isFunction(name)
|
const isDynamicName = isFunction(name)
|
||||||
const renderSlot = () => {
|
const renderSlot = () => {
|
||||||
const slot = getSlot(rawSlots, isFunction(name) ? name() : name)
|
const slot = getSlot(rawSlots, isFunction(name) ? name() : name)
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
enableHydrationNodeLookup,
|
enableHydrationNodeLookup,
|
||||||
next,
|
next,
|
||||||
} from './node'
|
} from './node'
|
||||||
import { isVaporFragmentEndAnchor } from '@vue/shared'
|
import { isDynamicAnchor, isVaporFragmentEndAnchor } from '@vue/shared'
|
||||||
|
|
||||||
export let isHydrating = false
|
export let isHydrating = false
|
||||||
export let currentHydrationNode: Node | null = null
|
export let currentHydrationNode: Node | null = null
|
||||||
|
@ -191,3 +191,29 @@ export function locateEndAnchor(
|
||||||
}
|
}
|
||||||
return 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
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { isComment, isEmptyText, locateEndAnchor } from './hydration'
|
import { isComment, isNonHydrationNode, locateEndAnchor } from './hydration'
|
||||||
import {
|
import {
|
||||||
DYNAMIC_END_ANCHOR_LABEL,
|
DYNAMIC_END_ANCHOR_LABEL,
|
||||||
DYNAMIC_START_ANCHOR_LABEL,
|
DYNAMIC_START_ANCHOR_LABEL,
|
||||||
isDynamicAnchor,
|
|
||||||
isVaporFragmentEndAnchor,
|
isVaporFragmentEndAnchor,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
|
|
||||||
|
@ -42,7 +41,7 @@ export function __child(node: ParentNode): Node {
|
||||||
* _setInsertionState(n2, 0) -> slot fragment
|
* _setInsertionState(n2, 0) -> slot fragment
|
||||||
*
|
*
|
||||||
* during hydration:
|
* 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
|
* const n1 = _child(n2) -> should be `Hi` instead of the slot fragment
|
||||||
* _setInsertionState(n2, 0) -> slot fragment
|
* _setInsertionState(n2, 0) -> slot fragment
|
||||||
*/
|
*/
|
||||||
|
@ -79,7 +78,19 @@ function _next(node: Node): Node {
|
||||||
|
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export function __next(node: Node): Node {
|
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!
|
let n = node.nextSibling!
|
||||||
while (n && isNonHydrationNode(n)) {
|
while (n && isNonHydrationNode(n)) {
|
||||||
|
@ -142,47 +153,3 @@ export function disableHydrationNodeLookup(): void {
|
||||||
next.impl = _next
|
next.impl = _next
|
||||||
nthChild.impl = _nthChild
|
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
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue