mirror of https://github.com/vuejs/core.git
wip: hydration
This commit is contained in:
parent
bba71becba
commit
196d551437
|
@ -24,10 +24,10 @@ export function genSelf(
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
): CodeFragment[] {
|
): CodeFragment[] {
|
||||||
const [frag, push] = buildCodeFragment()
|
const [frag, push] = buildCodeFragment()
|
||||||
const { id, template, operation } = dynamic
|
const { id, template, operation, dynamicChildOffset } = dynamic
|
||||||
|
|
||||||
if (id !== undefined && template !== undefined) {
|
if (id !== undefined && template !== undefined) {
|
||||||
push(NEWLINE, `const n${id} = t${template}()`)
|
push(NEWLINE, `const n${id} = t${template}(${dynamicChildOffset || ''})`)
|
||||||
push(...genDirectivesForElement(id, context))
|
push(...genDirectivesForElement(id, context))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -266,6 +266,7 @@ export interface IRDynamicInfo {
|
||||||
children: IRDynamicInfo[]
|
children: IRDynamicInfo[]
|
||||||
template?: number
|
template?: number
|
||||||
hasDynamicChild?: boolean
|
hasDynamicChild?: boolean
|
||||||
|
dynamicChildOffset?: number
|
||||||
operation?: OperationNode
|
operation?: OperationNode
|
||||||
needsKey?: boolean
|
needsKey?: boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ export const transformChildren: NodeTransform = (node, context) => {
|
||||||
|
|
||||||
function processDynamicChildren(context: TransformContext<ElementNode>) {
|
function processDynamicChildren(context: TransformContext<ElementNode>) {
|
||||||
let prevDynamics: IRDynamicInfo[] = []
|
let prevDynamics: IRDynamicInfo[] = []
|
||||||
let hasStaticTemplate = false
|
let staticCount = 0
|
||||||
const children = context.dynamic.children
|
const children = context.dynamic.children
|
||||||
|
|
||||||
for (const [index, child] of children.entries()) {
|
for (const [index, child] of children.entries()) {
|
||||||
|
@ -69,7 +69,7 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
|
||||||
|
|
||||||
if (!(child.flags & DynamicFlag.NON_TEMPLATE)) {
|
if (!(child.flags & DynamicFlag.NON_TEMPLATE)) {
|
||||||
if (prevDynamics.length) {
|
if (prevDynamics.length) {
|
||||||
if (hasStaticTemplate) {
|
if (staticCount) {
|
||||||
// each dynamic child gets its own placeholder node.
|
// each dynamic child gets its own placeholder node.
|
||||||
// this makes it easier to locate the corresponding node during hydration.
|
// this makes it easier to locate the corresponding node during hydration.
|
||||||
for (let i = 0; i < prevDynamics.length; i++) {
|
for (let i = 0; i < prevDynamics.length; i++) {
|
||||||
|
@ -92,12 +92,13 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
|
||||||
}
|
}
|
||||||
prevDynamics = []
|
prevDynamics = []
|
||||||
}
|
}
|
||||||
hasStaticTemplate = true
|
staticCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevDynamics.length) {
|
if (prevDynamics.length) {
|
||||||
registerInsertion(prevDynamics, context)
|
registerInsertion(prevDynamics, context, undefined)
|
||||||
|
context.dynamic.dynamicChildOffset = staticCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -476,6 +476,34 @@ describe('Vapor Mode hydration', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('consecutive components with insertion parent', async () => {
|
||||||
|
const data = reactive({ foo: 'foo', bar: 'bar' })
|
||||||
|
const { container } = await testHydration(
|
||||||
|
`<template>
|
||||||
|
<div>
|
||||||
|
<components.Child1/>
|
||||||
|
<components.Child2/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
Child1: `<template><span>{{ data.foo }}</span></template>`,
|
||||||
|
Child2: `<template><span>{{ data.bar }}</span></template>`,
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
expect(container.innerHTML).toBe(
|
||||||
|
`<div><span>foo</span><span>bar</span></div>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
data.foo = 'foo1'
|
||||||
|
data.bar = 'bar1'
|
||||||
|
await nextTick()
|
||||||
|
expect(container.innerHTML).toBe(
|
||||||
|
`<div><span>foo1</span><span>bar1</span></div>`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('nested consecutive components with anchor insertion', async () => {
|
test('nested consecutive components with anchor insertion', async () => {
|
||||||
const { container, data } = await testHydration(
|
const { container, data } = await testHydration(
|
||||||
`
|
`
|
||||||
|
@ -1314,6 +1342,38 @@ describe('Vapor Mode hydration', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('consecutive component with insertion parent', async () => {
|
||||||
|
const data = reactive({
|
||||||
|
show: true,
|
||||||
|
foo: 'foo',
|
||||||
|
bar: 'bar',
|
||||||
|
})
|
||||||
|
const { container } = await testHydration(
|
||||||
|
`<template>
|
||||||
|
<div v-if="data.show">
|
||||||
|
<components.Child/>
|
||||||
|
<components.Child2/>
|
||||||
|
</div>
|
||||||
|
</template>`,
|
||||||
|
{
|
||||||
|
Child: `<template><span>{{data.foo}}</span></template>`,
|
||||||
|
Child2: `<template><span>{{data.bar}}</span></template>`,
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
expect(container.innerHTML).toBe(
|
||||||
|
`<div>` +
|
||||||
|
`<span>foo</span>` +
|
||||||
|
`<span>bar</span>` +
|
||||||
|
`</div>` +
|
||||||
|
`<!--${anchorLabel}-->`,
|
||||||
|
)
|
||||||
|
|
||||||
|
data.show = false
|
||||||
|
await nextTick()
|
||||||
|
expect(container.innerHTML).toBe(`<!--${anchorLabel}-->`)
|
||||||
|
})
|
||||||
|
|
||||||
test('consecutive v-if on component with anchor insertion', async () => {
|
test('consecutive v-if on component with anchor insertion', async () => {
|
||||||
const data = ref(true)
|
const data = ref(true)
|
||||||
const { container } = await testHydration(
|
const { container } = await testHydration(
|
||||||
|
@ -2354,6 +2414,31 @@ describe('Vapor Mode hydration', () => {
|
||||||
`</div>`,
|
`</div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('slot fallback', async () => {
|
||||||
|
const data = reactive({
|
||||||
|
foo: 'foo',
|
||||||
|
})
|
||||||
|
const { container } = await testHydration(
|
||||||
|
`<template>
|
||||||
|
<components.Child>
|
||||||
|
</components.Child>
|
||||||
|
</template>`,
|
||||||
|
{
|
||||||
|
Child: `<template><slot><span>{{data.foo}}</span></slot></template>`,
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
expect(container.innerHTML).toBe(
|
||||||
|
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->`,
|
||||||
|
)
|
||||||
|
|
||||||
|
data.foo = 'bar'
|
||||||
|
await nextTick()
|
||||||
|
expect(container.innerHTML).toBe(
|
||||||
|
`<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->`,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.todo('transition', async () => {
|
describe.todo('transition', async () => {
|
||||||
|
@ -3912,6 +3997,79 @@ describe('VDOM hydration interop', () => {
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot(`"false"`)
|
expect(container.innerHTML).toMatchInlineSnapshot(`"false"`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('nested components (VDOM -> Vapor -> VDOM (with slot fallback))', async () => {
|
||||||
|
const data = ref(true)
|
||||||
|
const { container } = await testHydrationInterop(
|
||||||
|
`<script setup>const data = _data; const components = _components;</script>
|
||||||
|
<template>
|
||||||
|
<components.VaporChild/>
|
||||||
|
</template>`,
|
||||||
|
{
|
||||||
|
VaporChild: {
|
||||||
|
code: `<template><components.VdomChild/></template>`,
|
||||||
|
vapor: true,
|
||||||
|
},
|
||||||
|
VdomChild: {
|
||||||
|
code: `<script setup>const data = _data;</script>
|
||||||
|
<template><slot><span>{{data}}</span></slot></template>`,
|
||||||
|
vapor: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<!--[--><span>true</span><!--]--><!--slot-->"`,
|
||||||
|
)
|
||||||
|
|
||||||
|
data.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<!--[--><span>false</span><!--]--><!--slot-->"`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.todo(
|
||||||
|
'nested components (VDOM -> Vapor(with slot fallback) -> VDOM)',
|
||||||
|
async () => {
|
||||||
|
const data = ref(true)
|
||||||
|
const { container } = await testHydrationInterop(
|
||||||
|
`<script setup>const data = _data; const components = _components;</script>
|
||||||
|
<template>
|
||||||
|
<components.VaporChild/>
|
||||||
|
</template>`,
|
||||||
|
{
|
||||||
|
VaporChild: {
|
||||||
|
code: `<template>
|
||||||
|
<components.VdomChild>
|
||||||
|
<template #default>
|
||||||
|
<span>{{data}} vapor fallback</span>
|
||||||
|
</template>
|
||||||
|
</components.VdomChild>
|
||||||
|
</template>`,
|
||||||
|
vapor: true,
|
||||||
|
},
|
||||||
|
VdomChild: {
|
||||||
|
code: `<script setup>const data = _data;</script>
|
||||||
|
<template><slot><span>vdom fallback</span></slot></template>`,
|
||||||
|
vapor: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<!--[--><span>true vapor fallback</span><!--]--><!--slot-->"`,
|
||||||
|
)
|
||||||
|
|
||||||
|
data.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<!--[--><span>false vapor fallback</span><!--]--><!--slot-->"`,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
test('vapor slot render vdom component', async () => {
|
test('vapor slot render vdom component', async () => {
|
||||||
const data = ref(true)
|
const data = ref(true)
|
||||||
const { container } = await testHydrationInterop(
|
const { container } = await testHydrationInterop(
|
||||||
|
|
|
@ -86,7 +86,7 @@ export const createFor = (
|
||||||
const _insertionParent = insertionParent
|
const _insertionParent = insertionParent
|
||||||
const _insertionAnchor = insertionAnchor
|
const _insertionAnchor = insertionAnchor
|
||||||
if (isHydrating) {
|
if (isHydrating) {
|
||||||
locateHydrationNode(true)
|
locateHydrationNode()
|
||||||
} else {
|
} else {
|
||||||
resetInsertionState()
|
resetInsertionState()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,12 @@ import {
|
||||||
setInsertionState,
|
setInsertionState,
|
||||||
} from '../insertionState'
|
} from '../insertionState'
|
||||||
import {
|
import {
|
||||||
|
_nthChild,
|
||||||
disableHydrationNodeLookup,
|
disableHydrationNodeLookup,
|
||||||
enableHydrationNodeLookup,
|
enableHydrationNodeLookup,
|
||||||
next,
|
next,
|
||||||
} from './node'
|
} from './node'
|
||||||
import { isVaporAnchors, isVaporFragmentAnchor } from '@vue/shared'
|
import { isVaporAnchors } from '@vue/shared'
|
||||||
|
|
||||||
export let isHydrating = false
|
export let isHydrating = false
|
||||||
export let currentHydrationNode: Node | null = null
|
export let currentHydrationNode: Node | null = null
|
||||||
|
@ -29,9 +30,9 @@ function performHydration<T>(
|
||||||
if (!isOptimized) {
|
if (!isOptimized) {
|
||||||
adoptTemplate = adoptTemplateImpl
|
adoptTemplate = adoptTemplateImpl
|
||||||
locateHydrationNode = locateHydrationNodeImpl
|
locateHydrationNode = locateHydrationNodeImpl
|
||||||
|
|
||||||
// optimize anchor cache lookup
|
// optimize anchor cache lookup
|
||||||
;(Comment.prototype as any).$fs = undefined
|
;(Comment.prototype as any).$fe = undefined
|
||||||
|
;(Node.prototype as any).$dp = undefined
|
||||||
isOptimized = true
|
isOptimized = true
|
||||||
}
|
}
|
||||||
enableHydrationNodeLookup()
|
enableHydrationNodeLookup()
|
||||||
|
@ -58,12 +59,20 @@ export function hydrateNode(node: Node, fn: () => void): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
export let adoptTemplate: (node: Node, template: string) => Node | null
|
export let adoptTemplate: (node: Node, template: string) => Node | null
|
||||||
export let locateHydrationNode: (hasFragmentAnchor?: boolean) => void
|
export let locateHydrationNode: () => void
|
||||||
|
|
||||||
|
let inVNodeHydration = 0
|
||||||
|
export function setInVNodeHydration(): void {
|
||||||
|
inVNodeHydration++
|
||||||
|
}
|
||||||
|
export function unsetInVNodeHydration(): void {
|
||||||
|
inVNodeHydration--
|
||||||
|
}
|
||||||
|
|
||||||
type Anchor = Comment & {
|
type Anchor = Comment & {
|
||||||
// cached matching fragment start to avoid repeated traversal
|
// cached matching fragment end to avoid repeated traversal
|
||||||
// on nested fragments
|
// on nested fragments
|
||||||
$fs?: Anchor
|
$fe?: Anchor
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isComment = (node: Node, data: string): node is Anchor =>
|
export const isComment = (node: Node, data: string): node is Anchor =>
|
||||||
|
@ -99,9 +108,9 @@ function adoptTemplateImpl(node: Node, template: string): Node | null {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
const hydrationPositionMap = new WeakMap<ParentNode, Node>()
|
const nextHydrationNodeMap = new WeakMap<ParentNode, Node>()
|
||||||
|
|
||||||
function locateHydrationNodeImpl(hasFragmentAnchor?: boolean) {
|
function locateHydrationNodeImpl() {
|
||||||
let node: Node | null
|
let node: Node | null
|
||||||
// prepend / firstChild
|
// prepend / firstChild
|
||||||
if (insertionAnchor === 0) {
|
if (insertionAnchor === 0) {
|
||||||
|
@ -113,51 +122,31 @@ function locateHydrationNodeImpl(hasFragmentAnchor?: boolean) {
|
||||||
node = insertionAnchor
|
node = insertionAnchor
|
||||||
} else {
|
} else {
|
||||||
node = insertionParent
|
node = insertionParent
|
||||||
? hydrationPositionMap.get(insertionParent) || insertionParent.lastChild
|
? nextHydrationNodeMap.get(insertionParent) ||
|
||||||
|
(insertionParent.$dp !== undefined
|
||||||
|
? _nthChild(insertionParent, insertionParent.$dp)
|
||||||
|
: currentHydrationNode)
|
||||||
: currentHydrationNode
|
: currentHydrationNode
|
||||||
|
|
||||||
// if node is a vapor fragment anchor, find the previous one
|
let nextNode: Node | null = null
|
||||||
if (hasFragmentAnchor && node && isVaporFragmentAnchor(node)) {
|
while (node) {
|
||||||
node = node.previousSibling
|
const isFragStart = isComment(node, '[')
|
||||||
if (__DEV__ && !node) {
|
if (isFragStart)
|
||||||
// this should not happen
|
nextNode = locateEndAnchor(node as Anchor, '[', ']')!.nextSibling
|
||||||
throw new Error(`vapor fragment anchor previous node was not found.`)
|
if (
|
||||||
}
|
isNonHydrationNode(node) ||
|
||||||
}
|
// don't skip fragment start for vnode hydration
|
||||||
|
(!inVNodeHydration && isFragStart)
|
||||||
if (node && isComment(node, ']')) {
|
) {
|
||||||
// fragment backward search
|
node = node.nextSibling!
|
||||||
if (node.$fs) {
|
|
||||||
// already cached matching fragment start
|
|
||||||
node = node.$fs
|
|
||||||
} else {
|
} else {
|
||||||
let cur: Node | null = node
|
break
|
||||||
let curFragEnd = node
|
|
||||||
let fragDepth = 0
|
|
||||||
node = null
|
|
||||||
while (cur) {
|
|
||||||
cur = cur.previousSibling
|
|
||||||
if (cur) {
|
|
||||||
if (isComment(cur, '[')) {
|
|
||||||
curFragEnd.$fs = cur
|
|
||||||
if (!fragDepth) {
|
|
||||||
node = cur
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
fragDepth--
|
|
||||||
}
|
|
||||||
} else if (isComment(cur, ']')) {
|
|
||||||
curFragEnd = cur
|
|
||||||
fragDepth++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (insertionParent && node) {
|
if (insertionParent && node) {
|
||||||
const prev = node.previousSibling
|
const next = nextNode || node.nextSibling
|
||||||
if (prev) hydrationPositionMap.set(insertionParent, prev)
|
if (next) nextHydrationNodeMap.set(insertionParent, next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,24 +160,28 @@ function locateHydrationNodeImpl(hasFragmentAnchor?: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function locateEndAnchor(
|
export function locateEndAnchor(
|
||||||
node: Node | null,
|
node: Anchor,
|
||||||
open = '[',
|
open = '[',
|
||||||
close = ']',
|
close = ']',
|
||||||
): Node | null {
|
): Node | null {
|
||||||
let match = 0
|
// already cached matching end
|
||||||
while (node) {
|
if (node.$fe) {
|
||||||
node = node.nextSibling
|
return node.$fe
|
||||||
if (node && node.nodeType === 8) {
|
}
|
||||||
if ((node as Comment).data === open) match++
|
|
||||||
if ((node as Comment).data === close) {
|
const stack: Anchor[] = [node]
|
||||||
if (match === 0) {
|
while ((node = node.nextSibling as Anchor) && stack.length > 0) {
|
||||||
return node
|
if (node.nodeType === 8) {
|
||||||
} else {
|
if (node.data === open) {
|
||||||
match--
|
stack.push(node)
|
||||||
}
|
} else if (node.data === close) {
|
||||||
|
const matchingOpen = stack.pop()!
|
||||||
|
matchingOpen.$fe = node
|
||||||
|
if (stack.length === 0) return node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,16 @@ let t: HTMLTemplateElement
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export function template(html: string, root?: boolean) {
|
export function template(html: string, root?: boolean) {
|
||||||
let node: Node
|
let node: Node
|
||||||
return (): Node & { $root?: true } => {
|
return (n?: number): Node & { $root?: true } => {
|
||||||
if (isHydrating) {
|
if (isHydrating) {
|
||||||
if (__DEV__ && !currentHydrationNode) {
|
if (__DEV__ && !currentHydrationNode) {
|
||||||
// TODO this should not happen
|
// TODO this should not happen
|
||||||
throw new Error('No current hydration node')
|
throw new Error('No current hydration node')
|
||||||
}
|
}
|
||||||
return adoptTemplate(currentHydrationNode!, html)!
|
node = adoptTemplate(currentHydrationNode!, html)!
|
||||||
|
// dynamic node position, default is 0
|
||||||
|
;(node as any).$dp = n || 0
|
||||||
|
return node
|
||||||
}
|
}
|
||||||
// fast path for text nodes
|
// fast path for text nodes
|
||||||
if (html[0] !== '<') {
|
if (html[0] !== '<') {
|
||||||
|
|
|
@ -60,7 +60,7 @@ export class DynamicFragment extends VaporFragment {
|
||||||
constructor(anchorLabel?: string) {
|
constructor(anchorLabel?: string) {
|
||||||
super([])
|
super([])
|
||||||
if (isHydrating) {
|
if (isHydrating) {
|
||||||
locateHydrationNode(true)
|
locateHydrationNode()
|
||||||
this.hydrate(anchorLabel!)
|
this.hydrate(anchorLabel!)
|
||||||
} else {
|
} else {
|
||||||
this.anchor =
|
this.anchor =
|
||||||
|
|
|
@ -1,4 +1,16 @@
|
||||||
export let insertionParent: ParentNode | undefined
|
export let insertionParent:
|
||||||
|
| (ParentNode & {
|
||||||
|
// dynamic node position - hydration only
|
||||||
|
// indicates the position where dynamic nodes begin within the parent
|
||||||
|
// during hydration, static nodes before this index are skipped
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// const t0 = _template("<div><span></span><span></span></div>", true)
|
||||||
|
// const n4 = t0(2) // n4.$dp = 2
|
||||||
|
// The first 2 nodes are static, dynamic nodes start from index 2
|
||||||
|
$dp?: number
|
||||||
|
})
|
||||||
|
| undefined
|
||||||
export let insertionAnchor: Node | 0 | undefined
|
export let insertionAnchor: Node | 0 | undefined
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -55,6 +55,9 @@ import {
|
||||||
currentHydrationNode,
|
currentHydrationNode,
|
||||||
isHydrating,
|
isHydrating,
|
||||||
locateHydrationNode,
|
locateHydrationNode,
|
||||||
|
setCurrentHydrationNode,
|
||||||
|
setInVNodeHydration,
|
||||||
|
unsetInVNodeHydration,
|
||||||
hydrateNode as vaporHydrateNode,
|
hydrateNode as vaporHydrateNode,
|
||||||
} from './dom/hydration'
|
} from './dom/hydration'
|
||||||
import { DynamicFragment, VaporFragment, isFragment } from './fragment'
|
import { DynamicFragment, VaporFragment, isFragment } from './fragment'
|
||||||
|
@ -170,7 +173,11 @@ const vaporInteropImpl: Omit<
|
||||||
setVaporTransitionHooks(component as any, hooks as VaporTransitionHooks)
|
setVaporTransitionHooks(component as any, hooks as VaporTransitionHooks)
|
||||||
},
|
},
|
||||||
|
|
||||||
hydrate: vaporHydrateNode,
|
hydrate(node: Node, fn: () => void) {
|
||||||
|
setInVNodeHydration()
|
||||||
|
vaporHydrateNode(node, fn)
|
||||||
|
unsetInVNodeHydration()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const vaporSlotPropsProxyHandler: ProxyHandler<
|
const vaporSlotPropsProxyHandler: ProxyHandler<
|
||||||
|
@ -261,17 +268,7 @@ function createVDOMComponent(
|
||||||
if (transition) setVNodeTransitionHooks(vnode, transition)
|
if (transition) setVNodeTransitionHooks(vnode, transition)
|
||||||
|
|
||||||
if (isHydrating) {
|
if (isHydrating) {
|
||||||
;(
|
hydrateVNode(vnode, parentInstance as any)
|
||||||
vdomHydrateNode ||
|
|
||||||
(vdomHydrateNode = ensureHydrationRenderer().hydrateNode!)
|
|
||||||
)(
|
|
||||||
currentHydrationNode!,
|
|
||||||
vnode,
|
|
||||||
parentInstance as any,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
internals.mt(
|
internals.mt(
|
||||||
vnode,
|
vnode,
|
||||||
|
@ -344,42 +341,33 @@ function renderVDOMSlot(
|
||||||
ensureVaporSlotFallback(children, fallback as any)
|
ensureVaporSlotFallback(children, fallback as any)
|
||||||
isValidSlot = children.length > 0
|
isValidSlot = children.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isValidSlot) {
|
if (isValidSlot) {
|
||||||
if (isHydrating) {
|
if (isHydrating) {
|
||||||
locateHydrationNode(true)
|
hydrateVNode(vnode!, parentComponent as any)
|
||||||
;(
|
} else {
|
||||||
vdomHydrateNode ||
|
if (fallbackNodes) {
|
||||||
(vdomHydrateNode = ensureHydrationRenderer().hydrateNode!)
|
remove(fallbackNodes, parentNode)
|
||||||
)(
|
fallbackNodes = undefined
|
||||||
currentHydrationNode!,
|
}
|
||||||
|
internals.p(
|
||||||
|
oldVNode,
|
||||||
vnode!,
|
vnode!,
|
||||||
|
parentNode,
|
||||||
|
anchor,
|
||||||
parentComponent as any,
|
parentComponent as any,
|
||||||
null,
|
null,
|
||||||
|
undefined,
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
} else if (fallbackNodes) {
|
|
||||||
remove(fallbackNodes, parentNode)
|
|
||||||
fallbackNodes = undefined
|
|
||||||
}
|
}
|
||||||
internals.p(
|
|
||||||
oldVNode,
|
|
||||||
vnode!,
|
|
||||||
parentNode,
|
|
||||||
anchor,
|
|
||||||
parentComponent as any,
|
|
||||||
null,
|
|
||||||
undefined,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
oldVNode = vnode!
|
oldVNode = vnode!
|
||||||
} else {
|
} else {
|
||||||
// for forwarded slot without its own fallback, use the fallback
|
// for forwarded slot without its own fallback, use the fallback
|
||||||
// provided by the slot outlet.
|
// provided by the slot outlet.
|
||||||
// re-fetch `frag.fallback` as it may have been updated at `createSlot`
|
// re-fetch `frag.fallback` as it may have been updated at `createSlot`
|
||||||
fallback = frag.fallback
|
fallback = frag.fallback
|
||||||
|
|
||||||
if (fallback && !fallbackNodes) {
|
if (fallback && !fallbackNodes) {
|
||||||
// mount fallback
|
// mount fallback
|
||||||
if (oldVNode) {
|
if (oldVNode) {
|
||||||
|
@ -451,7 +439,12 @@ const createFallback =
|
||||||
const frag = new VaporFragment([])
|
const frag = new VaporFragment([])
|
||||||
frag.insert = (parentNode, anchor) => {
|
frag.insert = (parentNode, anchor) => {
|
||||||
fallbackNodes.forEach(vnode => {
|
fallbackNodes.forEach(vnode => {
|
||||||
internals.p(null, vnode, parentNode, anchor, parentComponent)
|
// hydrate fallback
|
||||||
|
if (isHydrating) {
|
||||||
|
hydrateVNode(vnode, parentComponent as any)
|
||||||
|
} else {
|
||||||
|
internals.p(null, vnode, parentNode, anchor, parentComponent)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
frag.remove = parentNode => {
|
frag.remove = parentNode => {
|
||||||
|
@ -465,3 +458,24 @@ const createFallback =
|
||||||
// vapor slot
|
// vapor slot
|
||||||
return fallbackNodes as Block
|
return fallbackNodes as Block
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hydrateVNode(
|
||||||
|
vnode: VNode,
|
||||||
|
parentComponent: ComponentInternalInstance | null,
|
||||||
|
) {
|
||||||
|
// keep fragment start anchor, hydrateNode uses it to
|
||||||
|
// determine if node is a fragmentStart
|
||||||
|
setInVNodeHydration()
|
||||||
|
locateHydrationNode()
|
||||||
|
if (!vdomHydrateNode) vdomHydrateNode = ensureHydrationRenderer().hydrateNode!
|
||||||
|
const nextNode = vdomHydrateNode(
|
||||||
|
currentHydrationNode!,
|
||||||
|
vnode,
|
||||||
|
parentComponent,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
unsetInVNodeHydration()
|
||||||
|
setCurrentHydrationNode(nextNode)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue