import * as VueServerRenderer from '@vue/server-renderer'
import { isString } from '@vue/shared'
import type { VaporComponentInstance } from '../src/component'
+import type { TeleportFragment } from '../src/components/Teleport'
const formatHtml = (raw: string) => {
return raw
})
})
+ describe('teleport', () => {
+ test('basic', async () => {
+ const data = ref({
+ msg: ref('foo'),
+ disabled: ref(false),
+ fn: vi.fn(),
+ })
+
+ const teleportContainer = document.createElement('div')
+ teleportContainer.id = 'teleport'
+ teleportContainer.innerHTML =
+ `<!--teleport start anchor-->` +
+ `<span>foo</span>` +
+ `<span class="foo"></span>` +
+ `<!--teleport anchor-->`
+ document.body.appendChild(teleportContainer)
+
+ const { block, container } = await mountWithHydration(
+ '<!--teleport start--><!--teleport end-->',
+ `<teleport to="#teleport" :disabled="data.disabled">
+ <span>{{data.msg}}</span>
+ <span :class="data.msg" @click="data.fn"></span>
+ </teleport>`,
+ data,
+ )
+
+ const teleport = block as TeleportFragment
+ expect(teleport.anchor).toBe(container.lastChild)
+ expect(teleport.target).toBe(teleportContainer)
+ expect(teleport.targetStart).toBe(teleportContainer.childNodes[0])
+ expect((teleport.nodes as Node[])[0]).toBe(
+ teleportContainer.childNodes[1],
+ )
+ expect((teleport.nodes as Node[])[1]).toBe(
+ teleportContainer.childNodes[2],
+ )
+ expect(teleport.targetAnchor).toBe(teleportContainer.childNodes[3])
+
+ expect(container.innerHTML).toMatchInlineSnapshot(
+ `"<!--teleport start--><!--teleport end-->"`,
+ )
+
+ // event handler
+ triggerEvent('click', teleportContainer.querySelector('.foo')!)
+ expect(data.value.fn).toHaveBeenCalled()
+
+ data.value.msg = 'bar'
+ await nextTick()
+ expect(formatHtml(teleportContainer.innerHTML)).toBe(
+ `<!--teleport start anchor-->` +
+ `<span>bar</span>` +
+ `<span class="bar"></span>` +
+ `<!--teleport anchor-->`,
+ )
+
+ data.value.disabled = true
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<!--teleport start-->` +
+ `<span>bar</span>` +
+ `<span class="bar"></span>` +
+ `<!--teleport end-->`,
+ )
+ expect(formatHtml(teleportContainer.innerHTML)).toMatchInlineSnapshot(
+ `"<!--teleport start anchor--><!--teleport anchor-->"`,
+ )
+
+ data.value.msg = 'baz'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<!--teleport start-->` +
+ `<span>baz</span>` +
+ `<span class="baz"></span>` +
+ `<!--teleport end-->`,
+ )
+
+ data.value.disabled = false
+ await nextTick()
+ expect(container.innerHTML).toMatchInlineSnapshot(
+ `"<!--teleport start--><!--teleport end-->"`,
+ )
+ expect(formatHtml(teleportContainer.innerHTML)).toBe(
+ `<!--teleport start anchor-->` +
+ `<span>baz</span>` +
+ `<span class="baz"></span>` +
+ `<!--teleport anchor-->`,
+ )
+ })
+
+ test('multiple + integration', async () => {
+ const data = ref({
+ msg: ref('foo'),
+ fn1: vi.fn(),
+ fn2: vi.fn(),
+ })
+
+ const code = `
+ <teleport to="#teleport2">
+ <span>{{data.msg}}</span>
+ <span :class="data.msg" @click="data.fn1"></span>
+ </teleport>
+ <teleport to="#teleport2">
+ <span>{{data.msg}}2</span>
+ <span :class="data.msg + 2" @click="data.fn2"></span>
+ </teleport>`
+
+ const SSRComp = compileVaporComponent(code, data, undefined, true)
+ const teleportContainer = document.createElement('div')
+ teleportContainer.id = 'teleport2'
+ const ctx = {} as any
+ const mainHtml = await VueServerRenderer.renderToString(
+ runtimeDom.createSSRApp(SSRComp),
+ ctx,
+ )
+ expect(mainHtml).toBe(
+ `<!--[-->` +
+ `<!--teleport start--><!--teleport end-->` +
+ `<!--teleport start--><!--teleport end-->` +
+ `<!--]-->`,
+ )
+
+ const teleportHtml = ctx.teleports!['#teleport2']
+ expect(teleportHtml).toBe(
+ `<!--teleport start anchor-->` +
+ `<span>foo</span><span class="foo"></span>` +
+ `<!--teleport anchor-->` +
+ `<!--teleport start anchor-->` +
+ `<span>foo2</span><span class="foo2"></span>` +
+ `<!--teleport anchor-->`,
+ )
+
+ teleportContainer.innerHTML = teleportHtml
+ document.body.appendChild(teleportContainer)
+
+ const { block, container } = await mountWithHydration(
+ mainHtml,
+ code,
+ data,
+ )
+
+ const teleports = block as any as TeleportFragment[]
+ const teleport1 = teleports[0]
+ const teleport2 = teleports[1]
+ expect(teleport1.anchor).toBe(container.childNodes[2])
+ expect(teleport2.anchor).toBe(container.childNodes[4])
+
+ expect(teleport1.target).toBe(teleportContainer)
+ expect(teleport1.targetStart).toBe(teleportContainer.childNodes[0])
+ expect((teleport1.nodes as Node[])[0]).toBe(
+ teleportContainer.childNodes[1],
+ )
+ expect(teleport1.targetAnchor).toBe(teleportContainer.childNodes[3])
+
+ expect(teleport2.target).toBe(teleportContainer)
+ expect(teleport2.targetStart).toBe(teleportContainer.childNodes[4])
+ expect((teleport2.nodes as Node[])[0]).toBe(
+ teleportContainer.childNodes[5],
+ )
+ expect(teleport2.targetAnchor).toBe(teleportContainer.childNodes[7])
+
+ expect(container.innerHTML).toBe(
+ `<!--[-->` +
+ `<!--teleport start--><!--teleport end-->` +
+ `<!--teleport start--><!--teleport end-->` +
+ `<!--]-->`,
+ )
+
+ // event handler
+ triggerEvent('click', teleportContainer.querySelector('.foo')!)
+ expect(data.value.fn1).toHaveBeenCalled()
+
+ triggerEvent('click', teleportContainer.querySelector('.foo2')!)
+ expect(data.value.fn2).toHaveBeenCalled()
+
+ data.value.msg = 'bar'
+ await nextTick()
+ expect(teleportContainer.innerHTML).toBe(
+ `<!--teleport start anchor-->` +
+ `<span>bar</span>` +
+ `<span class="bar"></span>` +
+ `<!--teleport anchor-->` +
+ `<!--teleport start anchor-->` +
+ `<span>bar2</span>` +
+ `<span class="bar2"></span>` +
+ `<!--teleport anchor-->`,
+ )
+ })
+
+ test('disabled', async () => {
+ const data = ref({
+ msg: ref('foo'),
+ fn1: vi.fn(),
+ fn2: vi.fn(),
+ })
+
+ const code = `
+ <div>foo</div>
+ <teleport to="#teleport3" disabled="true">
+ <span>{{data.msg}}</span>
+ <span :class="data.msg" @click="data.fn1"></span>
+ </teleport>
+ <div :class="data.msg + 2" @click="data.fn2">bar</div>
+ `
+
+ const SSRComp = compileVaporComponent(code, data, undefined, true)
+ const teleportContainer = document.createElement('div')
+ teleportContainer.id = 'teleport3'
+ const ctx = {} as any
+ const mainHtml = await VueServerRenderer.renderToString(
+ runtimeDom.createSSRApp(SSRComp),
+ ctx,
+ )
+ expect(mainHtml).toBe(
+ `<!--[-->` +
+ `<div>foo</div>` +
+ `<!--teleport start-->` +
+ `<span>foo</span>` +
+ `<span class="foo"></span>` +
+ `<!--teleport end-->` +
+ `<div class="foo2">bar</div>` +
+ `<!--]-->`,
+ )
+
+ const teleportHtml = ctx.teleports!['#teleport3']
+ expect(teleportHtml).toMatchInlineSnapshot(
+ `"<!--teleport start anchor--><!--teleport anchor-->"`,
+ )
+
+ teleportContainer.innerHTML = teleportHtml
+ document.body.appendChild(teleportContainer)
+
+ const { block, container } = await mountWithHydration(
+ mainHtml,
+ code,
+ data,
+ )
+
+ const blocks = block as any[]
+ expect(blocks[0]).toBe(container.childNodes[1])
+
+ const teleport = blocks[1] as TeleportFragment
+ expect((teleport.nodes as Node[])[0]).toBe(container.childNodes[3])
+ expect((teleport.nodes as Node[])[1]).toBe(container.childNodes[4])
+ expect(teleport.anchor).toBe(container.childNodes[5])
+ expect(teleport.target).toBe(teleportContainer)
+ expect(teleport.targetStart).toBe(teleportContainer.childNodes[0])
+ expect(teleport.targetAnchor).toBe(teleportContainer.childNodes[1])
+ expect(blocks[2]).toBe(container.childNodes[6])
+
+ expect(container.innerHTML).toBe(
+ `<!--[-->` +
+ `<div>foo</div>` +
+ `<!--teleport start-->` +
+ `<span>foo</span>` +
+ `<span class="foo"></span>` +
+ `<!--teleport end-->` +
+ `<div class="foo2">bar</div>` +
+ `<!--]-->`,
+ )
+
+ // event handler
+ triggerEvent('click', container.querySelector('.foo')!)
+ expect(data.value.fn1).toHaveBeenCalled()
+
+ triggerEvent('click', container.querySelector('.foo2')!)
+ expect(data.value.fn2).toHaveBeenCalled()
+
+ data.value.msg = 'bar'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<!--[-->` +
+ `<div>foo</div>` +
+ `<!--teleport start-->` +
+ `<span>bar</span>` +
+ `<span class="bar"></span>` +
+ `<!--teleport end-->` +
+ `<div class="bar2">bar</div>` +
+ `<!--]-->`,
+ )
+ })
+
+ test('disabled + as component root', async () => {
+ const { container } = await mountWithHydration(
+ `<!--[-->` +
+ `<div>Parent fragment</div>` +
+ `<!--teleport start--><div>Teleport content</div><!--teleport end-->` +
+ `<!--]-->`,
+ `
+ <div>Parent fragment</div>
+ <teleport to="body" disabled>
+ <div>Teleport content</div>
+ </teleport>
+ `,
+ )
+ expect(container.innerHTML).toBe(
+ `<!--[-->` +
+ `<div>Parent fragment</div>` +
+ `<!--teleport start-->` +
+ `<div>Teleport content</div>` +
+ `<!--teleport end-->` +
+ `<!--]-->`,
+ )
+ expect(`mismatch`).not.toHaveBeenWarned()
+ })
+
+ test('as component root', async () => {
+ const teleportContainer = document.createElement('div')
+ teleportContainer.id = 'teleport4'
+ teleportContainer.innerHTML = `<!--teleport start anchor-->hello<!--teleport anchor-->`
+ document.body.appendChild(teleportContainer)
+
+ const { block, container } = await mountWithHydration(
+ '<!--teleport start--><!--teleport end-->',
+ `<components.Wrapper></components.Wrapper>`,
+ undefined,
+ {
+ Wrapper: compileVaporComponent(
+ `<teleport to="#teleport4">hello</teleport>`,
+ ),
+ },
+ )
+
+ const teleport = (block as VaporComponentInstance)
+ .block as TeleportFragment
+ expect(teleport.anchor).toBe(container.childNodes[1])
+ expect(teleport.target).toBe(teleportContainer)
+ expect(teleport.targetStart).toBe(teleportContainer.childNodes[0])
+ expect(teleport.nodes).toBe(teleportContainer.childNodes[1])
+ expect(teleport.targetAnchor).toBe(teleportContainer.childNodes[2])
+ })
+
+ test('nested', async () => {
+ const teleportContainer = document.createElement('div')
+ teleportContainer.id = 'teleport5'
+ teleportContainer.innerHTML =
+ `<!--teleport start anchor-->` +
+ `<!--teleport start--><!--teleport end-->` +
+ `<!--teleport anchor-->` +
+ `<!--teleport start anchor-->` +
+ `<div>child</div>` +
+ `<!--teleport anchor-->`
+ document.body.appendChild(teleportContainer)
+
+ const { block, container } = await mountWithHydration(
+ '<!--teleport start--><!--teleport end-->',
+ `<teleport to="#teleport5">
+ <teleport to="#teleport5"><div>child</div></teleport>
+ </teleport>`,
+ )
+
+ const teleport = block as TeleportFragment
+ expect(teleport.anchor).toBe(container.childNodes[1])
+ expect(teleport.targetStart).toBe(teleportContainer.childNodes[0])
+ expect(teleport.targetAnchor).toBe(teleportContainer.childNodes[3])
+
+ const childTeleport = teleport.nodes as TeleportFragment
+ expect(childTeleport.anchor).toBe(teleportContainer.childNodes[2])
+ expect(childTeleport.targetStart).toBe(teleportContainer.childNodes[4])
+ expect(childTeleport.targetAnchor).toBe(teleportContainer.childNodes[6])
+ expect(childTeleport.nodes).toBe(teleportContainer.childNodes[5])
+ })
+
+ test('unmount (full integration)', async () => {
+ const targetId = 'teleport6'
+ const data = ref({
+ toggle: ref(true),
+ })
+
+ const template1 = `<Teleport to="#${targetId}"><span>Teleported Comp1</span></Teleport>`
+ const Comp1 = compileVaporComponent(template1)
+ const SSRComp1 = compileVaporComponent(
+ template1,
+ undefined,
+ undefined,
+ true,
+ )
+
+ const template2 = `<div>Comp2</div>`
+ const Comp2 = compileVaporComponent(template2)
+ const SSRComp2 = compileVaporComponent(
+ template2,
+ undefined,
+ undefined,
+ true,
+ )
+
+ const appCode = `
+ <div>
+ <components.Comp1 v-if="data.toggle"/>
+ <components.Comp2 v-else/>
+ </div>
+ `
+
+ const SSRApp = compileVaporComponent(
+ appCode,
+ data,
+ {
+ Comp1: SSRComp1,
+ Comp2: SSRComp2,
+ },
+ true,
+ )
+
+ const teleportContainer = document.createElement('div')
+ teleportContainer.id = targetId
+ document.body.appendChild(teleportContainer)
+
+ const ctx = {} as any
+ const mainHtml = await VueServerRenderer.renderToString(
+ runtimeDom.createSSRApp(SSRApp),
+ ctx,
+ )
+ expect(mainHtml).toBe(
+ '<div><!--teleport start--><!--teleport end--></div>',
+ )
+ teleportContainer.innerHTML = ctx.teleports![`#${targetId}`]
+
+ const { container } = await mountWithHydration(mainHtml, appCode, data, {
+ Comp1,
+ Comp2,
+ })
+
+ expect(container.innerHTML).toBe(
+ '<div><!--teleport start--><!--teleport end--><!--if--></div>',
+ )
+ expect(teleportContainer.innerHTML).toBe(
+ `<!--teleport start anchor-->` +
+ `<span>Teleported Comp1</span>` +
+ `<!--teleport anchor-->`,
+ )
+ expect(`mismatch`).not.toHaveBeenWarned()
+
+ data.value.toggle = false
+ await nextTick()
+ expect(container.innerHTML).toBe('<div><div>Comp2</div><!--if--></div>')
+ expect(teleportContainer.innerHTML).toBe('')
+ })
+
+ test('unmount (mismatch + full integration)', async () => {
+ const targetId = 'teleport7'
+ const data = ref({
+ toggle: ref(true),
+ })
+
+ const template1 = `<Teleport to="#${targetId}"><span>Teleported Comp1</span></Teleport>`
+ const Comp1 = compileVaporComponent(template1)
+ const SSRComp1 = compileVaporComponent(
+ template1,
+ undefined,
+ undefined,
+ true,
+ )
+
+ const template2 = `<div>Comp2</div>`
+ const Comp2 = compileVaporComponent(template2)
+ const SSRComp2 = compileVaporComponent(
+ template2,
+ undefined,
+ undefined,
+ true,
+ )
+
+ const appCode = `
+ <div>
+ <components.Comp1 v-if="data.toggle"/>
+ <components.Comp2 v-else/>
+ </div>
+ `
+
+ const SSRApp = compileVaporComponent(
+ appCode,
+ data,
+ {
+ Comp1: SSRComp1,
+ Comp2: SSRComp2,
+ },
+ true,
+ )
+
+ const teleportContainer = document.createElement('div')
+ teleportContainer.id = targetId
+ document.body.appendChild(teleportContainer)
+
+ const mainHtml = await VueServerRenderer.renderToString(
+ runtimeDom.createSSRApp(SSRApp),
+ )
+ expect(mainHtml).toBe(
+ '<div><!--teleport start--><!--teleport end--></div>',
+ )
+ expect(teleportContainer.innerHTML).toBe('')
+
+ const { container } = await mountWithHydration(mainHtml, appCode, data, {
+ Comp1,
+ Comp2,
+ })
+
+ expect(container.innerHTML).toBe(
+ '<div><!--teleport start--><!--teleport end--><!--if--></div>',
+ )
+ expect(teleportContainer.innerHTML).toBe(`<span>Teleported Comp1</span>`)
+ expect(`Hydration children mismatch`).toHaveBeenWarned()
+
+ data.value.toggle = false
+ await nextTick()
+ expect(container.innerHTML).toBe('<div><div>Comp2</div><!--if--></div>')
+ expect(teleportContainer.innerHTML).toBe('')
+ })
+
+ test('target change (mismatch + full integration)', async () => {
+ const targetId1 = 'teleport8-1'
+ const targetId2 = 'teleport8-2'
+ const data = ref({
+ target: ref(targetId1),
+ msg: ref('foo'),
+ })
+
+ const template = `<Teleport :to="'#' + data.target"><span>{{data.msg}}</span></Teleport>`
+ const Comp = compileVaporComponent(template, data)
+ const SSRComp = compileVaporComponent(template, data, undefined, true)
+
+ const teleportContainer1 = document.createElement('div')
+ teleportContainer1.id = targetId1
+ const teleportContainer2 = document.createElement('div')
+ teleportContainer2.id = targetId2
+ document.body.appendChild(teleportContainer1)
+ document.body.appendChild(teleportContainer2)
+
+ // server render
+ const mainHtml = await VueServerRenderer.renderToString(
+ runtimeDom.createSSRApp(SSRComp),
+ )
+ expect(mainHtml).toBe(`<!--teleport start--><!--teleport end-->`)
+ expect(teleportContainer1.innerHTML).toBe('')
+ expect(teleportContainer2.innerHTML).toBe('')
+
+ // hydrate
+ const { container } = await mountWithHydration(mainHtml, template, data, {
+ Comp,
+ })
+
+ expect(container.innerHTML).toBe(
+ `<!--teleport start--><!--teleport end-->`,
+ )
+ expect(teleportContainer1.innerHTML).toBe(`<span>foo</span>`)
+ expect(teleportContainer2.innerHTML).toBe('')
+ expect(`Hydration children mismatch`).toHaveBeenWarned()
+
+ data.value.target = targetId2
+ data.value.msg = 'bar'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<!--teleport start--><!--teleport end-->`,
+ )
+ expect(teleportContainer1.innerHTML).toBe('')
+ expect(teleportContainer2.innerHTML).toBe(`<span>bar</span>`)
+ })
+
+ test('with disabled teleport + undefined target', async () => {
+ const data = ref({
+ msg: ref('foo'),
+ })
+
+ const { container } = await mountWithHydration(
+ '<!--teleport start--><span>foo</span><!--teleport end-->',
+ `<teleport :to="undefined" :disabled="true">
+ <span>{{data.msg}}</span>
+ </teleport>`,
+ data,
+ )
+
+ expect(container.innerHTML).toBe(
+ `<!--teleport start--><span>foo</span><!--teleport end-->`,
+ )
+
+ data.value.msg = 'bar'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<!--teleport start--><span>bar</span><!--teleport end-->`,
+ )
+ })
+ })
+
+ describe.todo('Suspense')
+
describe('force hydrate prop', async () => {
test('force hydrate prop with `.prop` modifier', async () => {
const { container } = await mountWithHydration(
// vapor custom element not implemented yet
test.todo('force hydrate custom element with dynamic props', () => {})
})
-
- describe.todo('Suspense')
})
describe('mismatch handling', () => {
import {
+ MismatchTypes,
type TeleportProps,
+ type TeleportTargetElement,
+ isMismatchAllowed,
isTeleportDeferred,
isTeleportDisabled,
queuePostFlushCb,
import { renderEffect } from '../renderEffect'
import { extend, isArray } from '@vue/shared'
import { VaporFragment } from '../fragment'
+import {
+ advanceHydrationNode,
+ currentHydrationNode,
+ isComment,
+ isHydrating,
+ logMismatchError,
+ runWithoutHydration,
+ setCurrentHydrationNode,
+} from '../dom/hydration'
export const VaporTeleportImpl = {
name: 'VaporTeleport',
super([])
this.rawProps = props
this.rawSlots = slots
- this.anchor = __DEV__ ? createComment('teleport end') : createTextNode()
+ this.anchor = isHydrating
+ ? undefined
+ : __DEV__
+ ? createComment('teleport end')
+ : createTextNode()
renderEffect(() => {
// access the props to trigger tracking
this.handlePropsUpdate()
})
- this.initChildren()
+ if (!isHydrating) {
+ this.initChildren()
+ }
}
get parent(): ParentNode | null {
)
})
- // for hmr
if (__DEV__) {
const nodes = this.nodes
if (isVaporComponent(nodes)) {
private handleChildrenUpdate(children: Block): void {
// not mounted yet
- if (!this.parent) {
+ if (!this.parent || isHydrating) {
this.nodes = children
return
}
private handlePropsUpdate(): void {
// not mounted yet
- if (!this.parent) return
+ if (!this.parent || isHydrating) return
const mount = (parent: ParentNode, anchor: Node | null) => {
insert(
}
insert = (container: ParentNode, anchor: Node | null): void => {
+ if (isHydrating) return
+
// insert anchors in the main view
this.placeholder = __DEV__
? createComment('teleport start')
this.mountAnchor = undefined
}
+ private hydrateDisabledTeleport(targetNode: Node | null): void {
+ let nextNode = this.placeholder!.nextSibling!
+ setCurrentHydrationNode(nextNode)
+ this.mountAnchor = this.anchor = locateTeleportEndAnchor(nextNode)!
+ this.mountContainer = this.anchor.parentNode
+ this.targetStart = targetNode
+ this.targetAnchor = targetNode && targetNode.nextSibling
+ this.initChildren()
+ }
+
+ private mount(target: Node): void {
+ target.appendChild((this.targetStart = createTextNode('')))
+ target.appendChild(
+ (this.mountAnchor = this.targetAnchor = createTextNode('')),
+ )
+
+ if (!isMismatchAllowed(target as Element, MismatchTypes.CHILDREN)) {
+ if (__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) {
+ warn(
+ `Hydration children mismatch on`,
+ target,
+ `\nServer rendered element contains fewer child nodes than client nodes.`,
+ )
+ }
+ logMismatchError()
+ }
+
+ runWithoutHydration(this.initChildren.bind(this))
+ }
+
hydrate = (): void => {
- //TODO
+ const target = (this.target = resolveTeleportTarget(
+ this.resolvedProps!,
+ querySelector,
+ ))
+ const disabled = isTeleportDisabled(this.resolvedProps!)
+ this.placeholder = currentHydrationNode!
+ if (target) {
+ const targetNode =
+ (target as TeleportTargetElement)._lpa || target.firstChild
+ if (disabled) {
+ this.hydrateDisabledTeleport(targetNode)
+ } else {
+ this.anchor = locateTeleportEndAnchor()!
+ this.mountContainer = target
+ let targetAnchor = targetNode
+ while (targetAnchor) {
+ if (targetAnchor && targetAnchor.nodeType === 8) {
+ if ((targetAnchor as Comment).data === 'teleport start anchor') {
+ this.targetStart = targetAnchor
+ } else if ((targetAnchor as Comment).data === 'teleport anchor') {
+ this.mountAnchor = this.targetAnchor = targetAnchor
+ ;(target as TeleportTargetElement)._lpa =
+ this.targetAnchor && this.targetAnchor.nextSibling
+ break
+ }
+ }
+ targetAnchor = targetAnchor.nextSibling
+ }
+
+ if (targetNode) {
+ setCurrentHydrationNode(targetNode.nextSibling)
+ }
+
+ // if the HTML corresponding to Teleport is not embedded in the
+ // correct position on the final page during SSR. the targetAnchor will
+ // always be null, we need to manually add targetAnchor to ensure
+ // Teleport it can properly unmount or move
+ if (!this.targetAnchor) {
+ this.mount(target)
+ } else {
+ this.initChildren()
+ }
+ }
+ } else if (disabled) {
+ this.hydrateDisabledTeleport(currentHydrationNode!)
+ }
+
+ advanceHydrationNode(this.anchor!)
}
}
): value is typeof VaporTeleportImpl {
return value === VaporTeleportImpl
}
+
+function locateTeleportEndAnchor(
+ node: Node = currentHydrationNode!,
+): Node | null {
+ while (node) {
+ if (isComment(node, 'teleport end')) {
+ return node
+ }
+ node = node.nextSibling as Node
+ }
+ return null
+}