mirror of https://github.com/vuejs/core.git
wip: hydration for slots
This commit is contained in:
parent
2f002649d1
commit
700f49ee96
|
@ -246,7 +246,7 @@ describe('ssr: components', () => {
|
|||
_ssrRenderList(list, (i) => {
|
||||
_push(\`<span\${_scopeId}></span>\`)
|
||||
})
|
||||
_push(\`<!--]--></div>\`)
|
||||
_push(\`<!--]--><!--for--></div>\`)
|
||||
_push(\`<!--if-->\`)
|
||||
} else {
|
||||
_push(\`<!---->\`)
|
||||
|
@ -270,7 +270,7 @@ describe('ssr: components', () => {
|
|||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<span\${_scopeId}></span>\`)
|
||||
})
|
||||
_push(\`<!--]--></div>\`)
|
||||
_push(\`<!--]--><!--for--></div>\`)
|
||||
_push(\`<!--if-->\`)
|
||||
} else {
|
||||
_push(\`<!---->\`)
|
||||
|
|
|
@ -10,7 +10,7 @@ describe('ssr: v-for', () => {
|
|||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`<!--]-->\`)
|
||||
_push(\`<!--]--><!--for-->\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
@ -25,7 +25,7 @@ describe('ssr: v-for', () => {
|
|||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<div>foo<span>bar</span></div>\`)
|
||||
})
|
||||
_push(\`<!--]-->\`)
|
||||
_push(\`<!--]--><!--for-->\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
@ -51,9 +51,9 @@ describe('ssr: v-for', () => {
|
|||
_ssrInterpolate(j)
|
||||
}</div>\`)
|
||||
})
|
||||
_push(\`<!--]--></div>\`)
|
||||
_push(\`<!--]--><!--for--></div>\`)
|
||||
})
|
||||
_push(\`<!--]-->\`)
|
||||
_push(\`<!--]--><!--for-->\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
@ -68,7 +68,7 @@ describe('ssr: v-for', () => {
|
|||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<!--[-->\${_ssrInterpolate(i)}<!--]-->\`)
|
||||
})
|
||||
_push(\`<!--]-->\`)
|
||||
_push(\`<!--]--><!--for-->\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
@ -85,7 +85,7 @@ describe('ssr: v-for', () => {
|
|||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<span>\${_ssrInterpolate(i)}</span>\`)
|
||||
})
|
||||
_push(\`<!--]-->\`)
|
||||
_push(\`<!--]--><!--for-->\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
@ -107,7 +107,7 @@ describe('ssr: v-for', () => {
|
|||
_ssrInterpolate(i + 1)
|
||||
}</span><!--]-->\`)
|
||||
})
|
||||
_push(\`<!--]-->\`)
|
||||
_push(\`<!--]--><!--for-->\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
@ -127,7 +127,7 @@ describe('ssr: v-for', () => {
|
|||
_ssrRenderList(_ctx.list, ({ foo }, index) => {
|
||||
_push(\`<div>\${_ssrInterpolate(foo + _ctx.bar + index)}</div>\`)
|
||||
})
|
||||
_push(\`<!--]-->\`)
|
||||
_push(\`<!--]--><!--for-->\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
|
|
@ -147,7 +147,7 @@ describe('ssr: v-if', () => {
|
|||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`<!--]-->\`)
|
||||
_push(\`<!--]--><!--for-->\`)
|
||||
_push(\`<!--if-->\`)
|
||||
} else {
|
||||
_push(\`<!---->\`)
|
||||
|
|
|
@ -70,7 +70,7 @@ describe('ssr: v-model', () => {
|
|||
: _ssrLooseEqual(_ctx.model, i))) ? " selected" : ""
|
||||
}></option>\`)
|
||||
})
|
||||
_push(\`<!--]--></select></div>\`)
|
||||
_push(\`<!--]--><!--for--></select></div>\`)
|
||||
}"
|
||||
`)
|
||||
|
||||
|
|
|
@ -381,6 +381,7 @@ function processChildrenDynamicInfo(
|
|||
* <Comp/> // Dynamic node -> should be wrapped
|
||||
* <Comp/> // Dynamic node -> should NOT be wrapped
|
||||
* <element/> // Static node
|
||||
* </element>
|
||||
*/
|
||||
function shouldProcessChildAsDynamic(
|
||||
parent: { tag?: string; children: TemplateChildNode[] },
|
||||
|
|
|
@ -49,8 +49,7 @@ export function ssrProcessFor(
|
|||
)
|
||||
if (!disableNestedFragments) {
|
||||
context.pushStringPart(`<!--]-->`)
|
||||
} else {
|
||||
// add anchor for non-fragment v-for
|
||||
context.pushStringPart(`<!--${FOR_ANCHOR_LABEL}-->`)
|
||||
}
|
||||
// v-for anchor for vapor hydration
|
||||
context.pushStringPart(`<!--${FOR_ANCHOR_LABEL}-->`)
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ export function createHydrationFunctions(
|
|||
let n = next(node)
|
||||
// skip if:
|
||||
// - dynamic anchors (`<!--[[-->`, `<!--][-->`)
|
||||
// - dynamic fragment end anchors (e.g. `<!--if-->`, `<!--for-->`)
|
||||
// - vapor fragment end anchors (e.g. `<!--if-->`, `<!--for-->`)
|
||||
if (n && (isDynamicAnchor(n) || isVaporFragmentEndAnchor(n))) {
|
||||
n = next(n)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,12 @@ import { compileScript, parse } from '@vue/compiler-sfc'
|
|||
import * as runtimeVapor from '../src'
|
||||
import * as runtimeDom from '@vue/runtime-dom'
|
||||
import * as VueServerRenderer from '@vue/server-renderer'
|
||||
import { DYNAMIC_COMPONENT_ANCHOR_LABEL, IF_ANCHOR_LABEL } from '@vue/shared'
|
||||
import {
|
||||
DYNAMIC_COMPONENT_ANCHOR_LABEL,
|
||||
FOR_ANCHOR_LABEL,
|
||||
IF_ANCHOR_LABEL,
|
||||
SLOT_ANCHOR_LABEL,
|
||||
} from '@vue/shared'
|
||||
|
||||
const Vue = { ...runtimeDom, ...runtimeVapor }
|
||||
|
||||
|
@ -1438,6 +1443,9 @@ describe('Vapor Mode hydration', () => {
|
|||
})
|
||||
|
||||
describe('for', () => {
|
||||
const forAnchorLabel = FOR_ANCHOR_LABEL
|
||||
const slotAnchorLabel = SLOT_ANCHOR_LABEL
|
||||
|
||||
test('basic v-for', async () => {
|
||||
const { container, data } = await testHydration(
|
||||
`<template>
|
||||
|
@ -1454,7 +1462,7 @@ describe('Vapor Mode hydration', () => {
|
|||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<!--]-->` +
|
||||
`<!--]--><!--${forAnchorLabel}-->` +
|
||||
`</div>`,
|
||||
)
|
||||
|
||||
|
@ -1466,8 +1474,9 @@ describe('Vapor Mode hydration', () => {
|
|||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<span>d</span>` +
|
||||
`<!--]-->` +
|
||||
`<span>d</span>` +
|
||||
`<!--${forAnchorLabel}-->` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
|
@ -1483,13 +1492,23 @@ describe('Vapor Mode hydration', () => {
|
|||
ref(['a', 'b', 'c']),
|
||||
)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div><!--[--><span>a</span><span>b</span><span>c</span><!--]--></div>`,
|
||||
`<div>` +
|
||||
`<!--[-->` +
|
||||
`<span>a</span><span>b</span><span>c</span>` +
|
||||
`<!--]--><!--${forAnchorLabel}-->` +
|
||||
`</div>`,
|
||||
)
|
||||
|
||||
data.value.push('d')
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div><!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--]--></div>`,
|
||||
`<div>` +
|
||||
`<!--[-->` +
|
||||
`<span>a</span><span>b</span><span>c</span>` +
|
||||
`<!--]-->` +
|
||||
`<span>d</span>` +
|
||||
`<!--${forAnchorLabel}-->` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -1512,7 +1531,7 @@ describe('Vapor Mode hydration', () => {
|
|||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<!--]-->` +
|
||||
`<!--]--><!--${forAnchorLabel}-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
|
@ -1526,8 +1545,9 @@ describe('Vapor Mode hydration', () => {
|
|||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<span>d</span>` +
|
||||
`<!--]-->` +
|
||||
`<span>d</span>` +
|
||||
`<!--${forAnchorLabel}-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
|
@ -1540,8 +1560,9 @@ describe('Vapor Mode hydration', () => {
|
|||
`<!--[-->` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<span>d</span>` +
|
||||
`<!--]-->` +
|
||||
`<span>d</span>` +
|
||||
`<!--${forAnchorLabel}-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
|
@ -1567,12 +1588,12 @@ describe('Vapor Mode hydration', () => {
|
|||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<!--]-->` +
|
||||
`<!--]--><!--${forAnchorLabel}-->` +
|
||||
`<!--[-->` +
|
||||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<!--]-->` +
|
||||
`<!--]--><!--${forAnchorLabel}-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
|
@ -1586,14 +1607,16 @@ describe('Vapor Mode hydration', () => {
|
|||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<span>d</span>` +
|
||||
`<!--]-->` +
|
||||
`<span>d</span>` +
|
||||
`<!--${forAnchorLabel}-->` +
|
||||
`<!--[-->` +
|
||||
`<span>a</span>` +
|
||||
`<span>b</span>` +
|
||||
`<span>c</span>` +
|
||||
`<span>d</span>` +
|
||||
`<!--]-->` +
|
||||
`<span>d</span>` +
|
||||
`<!--${forAnchorLabel}-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
|
@ -1605,12 +1628,14 @@ describe('Vapor Mode hydration', () => {
|
|||
`<span></span>` +
|
||||
`<!--[-->` +
|
||||
`<span>c</span>` +
|
||||
`<span>d</span>` +
|
||||
`<!--]-->` +
|
||||
`<span>d</span>` +
|
||||
`<!--${forAnchorLabel}-->` +
|
||||
`<!--[-->` +
|
||||
`<span>c</span>` +
|
||||
`<span>d</span>` +
|
||||
`<!--]-->` +
|
||||
`<span>d</span>` +
|
||||
`<!--${forAnchorLabel}-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
|
@ -1635,7 +1660,7 @@ describe('Vapor Mode hydration', () => {
|
|||
`<div>comp</div>` +
|
||||
`<div>comp</div>` +
|
||||
`<div>comp</div>` +
|
||||
`<!--]-->` +
|
||||
`<!--]--><!--${forAnchorLabel}-->` +
|
||||
`</div>`,
|
||||
)
|
||||
|
||||
|
@ -1647,8 +1672,9 @@ describe('Vapor Mode hydration', () => {
|
|||
`<div>comp</div>` +
|
||||
`<div>comp</div>` +
|
||||
`<div>comp</div>` +
|
||||
`<div>comp</div>` +
|
||||
`<!--]-->` +
|
||||
`<div>comp</div>` +
|
||||
`<!--${forAnchorLabel}-->` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
|
@ -1670,10 +1696,10 @@ describe('Vapor Mode hydration', () => {
|
|||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<!--[-->` +
|
||||
`<!--[--><span>a</span><!--]--><!--slot-->` +
|
||||
`<!--[--><span>b</span><!--]--><!--slot-->` +
|
||||
`<!--[--><span>c</span><!--]--><!--slot-->` +
|
||||
`<!--]-->` +
|
||||
`<!--[--><span>a</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><span>b</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><span>c</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--]--><!--${forAnchorLabel}-->` +
|
||||
`</div>`,
|
||||
)
|
||||
|
||||
|
@ -1682,11 +1708,12 @@ describe('Vapor Mode hydration', () => {
|
|||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<!--[-->` +
|
||||
`<!--[--><span>a</span><!--]--><!--slot-->` +
|
||||
`<!--[--><span>b</span><!--]--><!--slot-->` +
|
||||
`<!--[--><span>c</span><!--]--><!--slot-->` +
|
||||
`<span>d</span><!--slot-->` +
|
||||
`<!--[--><span>a</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><span>b</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><span>c</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--]-->` +
|
||||
`<span>d</span><!--${slotAnchorLabel}-->` +
|
||||
`<!--${forAnchorLabel}-->` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
|
@ -1709,7 +1736,7 @@ describe('Vapor Mode hydration', () => {
|
|||
`<!--[--><div>foo</div>-bar-<!--]-->` +
|
||||
`<!--[--><div>foo</div>-bar-<!--]-->` +
|
||||
`<!--[--><div>foo</div>-bar-<!--]-->` +
|
||||
`<!--]-->` +
|
||||
`<!--]--><!--${forAnchorLabel}-->` +
|
||||
`</div>`,
|
||||
)
|
||||
|
||||
|
@ -1721,18 +1748,17 @@ describe('Vapor Mode hydration', () => {
|
|||
`<!--[--><div>foo</div>-bar-<!--]-->` +
|
||||
`<!--[--><div>foo</div>-bar-<!--]-->` +
|
||||
`<!--[--><div>foo</div>-bar-<!--]-->` +
|
||||
`<div>foo</div>-bar-` +
|
||||
`<!--]-->` +
|
||||
`<div>foo</div>-bar-` +
|
||||
`<!--${forAnchorLabel}-->` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
|
||||
// TODO wait for vapor TransitionGroup support
|
||||
// v-for inside TransitionGroup does not render as a fragment
|
||||
test.todo('v-for in TransitionGroup', async () => {})
|
||||
})
|
||||
|
||||
describe('slots', () => {
|
||||
const slotAnchorLabel = SLOT_ANCHOR_LABEL
|
||||
const forAnchorLabel = FOR_ANCHOR_LABEL
|
||||
test('basic slot', async () => {
|
||||
const { data, container } = await testHydration(
|
||||
`<template>
|
||||
|
@ -1745,13 +1771,13 @@ describe('Vapor Mode hydration', () => {
|
|||
},
|
||||
)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[--><span>foo</span><!--]--><!--slot-->`,
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->`,
|
||||
)
|
||||
|
||||
data.value = 'bar'
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[--><span>bar</span><!--]--><!--slot-->`,
|
||||
`<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->`,
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -1769,13 +1795,13 @@ describe('Vapor Mode hydration', () => {
|
|||
},
|
||||
)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[--><span>foo</span><!--]--><!--slot-->`,
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->`,
|
||||
)
|
||||
|
||||
data.value = 'bar'
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[--><span>bar</span><!--]--><!--slot-->`,
|
||||
`<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->`,
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -1793,12 +1819,14 @@ describe('Vapor Mode hydration', () => {
|
|||
},
|
||||
)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[--><span>foo</span><!--]--><!--slot-->`,
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->`,
|
||||
)
|
||||
|
||||
data.value = false
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(`<!--[--><!--]--><!--slot-->`)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[--><!--]--><!--${slotAnchorLabel}-->`,
|
||||
)
|
||||
})
|
||||
|
||||
test('named slot with v-if and v-for', async () => {
|
||||
|
@ -1821,15 +1849,15 @@ describe('Vapor Mode hydration', () => {
|
|||
)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[-->` +
|
||||
`<!--[--><span>a</span><span>b</span><span>c</span><!--]-->` +
|
||||
`<!--[--><span>a</span><span>b</span><span>c</span><!--]--><!--${forAnchorLabel}-->` +
|
||||
`<!--]-->` +
|
||||
`<!--slot-->`,
|
||||
`<!--${slotAnchorLabel}-->`,
|
||||
)
|
||||
|
||||
data.show = false
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[--><!--[--><!--]--><!--]--><!--slot-->`,
|
||||
`<!--[--><!--[--><!--]--><!--]--><!--${slotAnchorLabel}-->`,
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -1852,7 +1880,7 @@ describe('Vapor Mode hydration', () => {
|
|||
`<span>foo</span>` +
|
||||
`<span></span>` +
|
||||
`<!--]-->` +
|
||||
`<!--slot-->`,
|
||||
`<!--${slotAnchorLabel}-->`,
|
||||
)
|
||||
|
||||
data.value = 'bar'
|
||||
|
@ -1863,7 +1891,7 @@ describe('Vapor Mode hydration', () => {
|
|||
`<span>bar</span>` +
|
||||
`<span></span>` +
|
||||
`<!--]-->` +
|
||||
`<!--slot-->`,
|
||||
`<!--${slotAnchorLabel}-->`,
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -1896,7 +1924,7 @@ describe('Vapor Mode hydration', () => {
|
|||
`<span>foo</span>` +
|
||||
`<span></span>` +
|
||||
`<!--]-->` +
|
||||
`<!--slot-->` +
|
||||
`<!--${slotAnchorLabel}-->` +
|
||||
`<div></div>` +
|
||||
`<!--]-->`,
|
||||
)
|
||||
|
@ -1912,14 +1940,13 @@ describe('Vapor Mode hydration', () => {
|
|||
`<span>bar</span>` +
|
||||
`<span></span>` +
|
||||
`<!--]-->` +
|
||||
`<!--slot-->` +
|
||||
`<!--${slotAnchorLabel}-->` +
|
||||
`<div></div>` +
|
||||
`<!--]-->`,
|
||||
)
|
||||
})
|
||||
|
||||
// problem is next child is incorrect after slot
|
||||
test.todo('mixed slot and text node', async () => {
|
||||
test('mixed slot and text node', async () => {
|
||||
const data = reactive({
|
||||
text: 'foo',
|
||||
msg: 'hi',
|
||||
|
@ -1937,11 +1964,11 @@ describe('Vapor Mode hydration', () => {
|
|||
)
|
||||
|
||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||
`"<div><!--[--><span>foo</span><!--]--><!--slot-->hi</div>"`,
|
||||
`"<div><!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->hi</div>"`,
|
||||
)
|
||||
})
|
||||
|
||||
test.todo('mixed slot and element', async () => {
|
||||
test('mixed slot and element', async () => {
|
||||
const data = reactive({
|
||||
text: 'foo',
|
||||
msg: 'hi',
|
||||
|
@ -1959,14 +1986,272 @@ describe('Vapor Mode hydration', () => {
|
|||
)
|
||||
|
||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||
`"<div><!--hi--><span>foo</span><!--]--><!--slot--><div>hi</div></div>"`,
|
||||
`"<div><!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}--><div>hi</div></div>"`,
|
||||
)
|
||||
})
|
||||
|
||||
// mixed slot and component
|
||||
// mixed slot and fragment component
|
||||
// mixed slot and v-if
|
||||
// mixed slot and v-for
|
||||
test('mixed slot and component', async () => {
|
||||
const data = reactive({
|
||||
msg1: 'foo',
|
||||
msg2: 'bar',
|
||||
})
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<components.Child>
|
||||
<span>{{data.msg1}}</span>
|
||||
</components.Child>
|
||||
</template>`,
|
||||
{
|
||||
Child: `
|
||||
<template>
|
||||
<div>
|
||||
<components.Child2/>
|
||||
<slot/>
|
||||
<components.Child2/>
|
||||
</div>
|
||||
</template>`,
|
||||
Child2: `
|
||||
<template>
|
||||
<div>{{data.msg2}}</div>
|
||||
</template>`,
|
||||
},
|
||||
data,
|
||||
)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<div>bar</div>` +
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<div>bar</div>` +
|
||||
`</div>`,
|
||||
)
|
||||
data.msg2 = 'hello'
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<div>hello</div>` +
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<div>hello</div>` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
|
||||
test('mixed slot and fragment component', async () => {
|
||||
const data = reactive({
|
||||
msg1: 'foo',
|
||||
msg2: 'bar',
|
||||
})
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<components.Child>
|
||||
<span>{{data.msg1}}</span>
|
||||
</components.Child>
|
||||
</template>`,
|
||||
{
|
||||
Child: `
|
||||
<template>
|
||||
<div>
|
||||
<components.Child2/>
|
||||
<slot/>
|
||||
<components.Child2/>
|
||||
</div>
|
||||
</template>`,
|
||||
Child2: `
|
||||
<template>
|
||||
<div>{{data.msg1}}</div> {{data.msg2}}
|
||||
</template>`,
|
||||
},
|
||||
data,
|
||||
)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<!--[--><div>foo</div> bar<!--]-->` +
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><div>foo</div> bar<!--]-->` +
|
||||
`</div>`,
|
||||
)
|
||||
|
||||
data.msg1 = 'hello'
|
||||
data.msg2 = 'vapor'
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<!--[--><div>hello</div> vapor<!--]-->` +
|
||||
`<!--[--><span>hello</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><div>hello</div> vapor<!--]-->` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
|
||||
test('mixed slot and v-if', async () => {
|
||||
const data = reactive({
|
||||
show: true,
|
||||
msg: 'foo',
|
||||
})
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<components.Child>
|
||||
<span>{{data.msg}}</span>
|
||||
</components.Child>
|
||||
</template>`,
|
||||
{
|
||||
Child: `
|
||||
<template>
|
||||
<div v-if="data.show">{{data.msg}}</div>
|
||||
<slot/>
|
||||
<div v-if="data.show">{{data.msg}}</div>
|
||||
</template>`,
|
||||
},
|
||||
data,
|
||||
)
|
||||
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[-->` +
|
||||
`<div>foo</div><!--if-->` +
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<div>foo</div><!--if-->` +
|
||||
`<!--]-->`,
|
||||
)
|
||||
|
||||
data.show = false
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[-->` +
|
||||
`<!--if-->` +
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--if-->` +
|
||||
`<!--]-->`,
|
||||
)
|
||||
})
|
||||
|
||||
test('mixed slot and v-for', async () => {
|
||||
const data = reactive({
|
||||
items: ['a', 'b', 'c'],
|
||||
msg: 'foo',
|
||||
})
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<components.Child>
|
||||
<span>{{data.msg}}</span>
|
||||
</components.Child>
|
||||
</template>`,
|
||||
{
|
||||
Child: `
|
||||
<template>
|
||||
<div v-for="item in data.items" :key="item">{{item}}</div>
|
||||
<slot/>
|
||||
<div v-for="item in data.items" :key="item">{{item}}</div>
|
||||
</template>`,
|
||||
},
|
||||
data,
|
||||
)
|
||||
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[-->` +
|
||||
`<!--[--><div>a</div><div>b</div><div>c</div><!--]--><!--${forAnchorLabel}-->` +
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><div>a</div><div>b</div><div>c</div><!--]--><!--${forAnchorLabel}-->` +
|
||||
`<!--]-->`,
|
||||
)
|
||||
|
||||
data.items.push('d')
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[-->` +
|
||||
`<!--[--><div>a</div><div>b</div><div>c</div><!--]--><div>d</div><!--${forAnchorLabel}-->` +
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><div>a</div><div>b</div><div>c</div><!--]--><div>d</div><!--${forAnchorLabel}-->` +
|
||||
`<!--]-->`,
|
||||
)
|
||||
})
|
||||
|
||||
test('consecutive slots', async () => {
|
||||
const data = reactive({
|
||||
msg1: 'foo',
|
||||
msg2: 'bar',
|
||||
})
|
||||
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<components.Child>
|
||||
<span>{{data.msg1}}</span>
|
||||
<template #bar>
|
||||
<span>{{data.msg2}}</span>
|
||||
</template>
|
||||
</components.Child>
|
||||
</template>`,
|
||||
{
|
||||
Child: `<template><slot/><slot name="bar"/></template>`,
|
||||
},
|
||||
data,
|
||||
)
|
||||
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[-->` +
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--]-->`,
|
||||
)
|
||||
|
||||
data.msg1 = 'hello'
|
||||
data.msg2 = 'vapor'
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--[-->` +
|
||||
`<!--[--><span>hello</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><span>vapor</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--]-->`,
|
||||
)
|
||||
})
|
||||
|
||||
test('consecutive slots with anchor insertion', async () => {
|
||||
const data = reactive({
|
||||
msg1: 'foo',
|
||||
msg2: 'bar',
|
||||
})
|
||||
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<components.Child>
|
||||
<span>{{data.msg1}}</span>
|
||||
<template #bar>
|
||||
<span>{{data.msg2}}</span>
|
||||
</template>
|
||||
</components.Child>
|
||||
</template>`,
|
||||
{
|
||||
Child: `<template>
|
||||
<div>
|
||||
<span/>
|
||||
<slot/>
|
||||
<slot name="bar"/>
|
||||
<span/>
|
||||
</div>
|
||||
</template>`,
|
||||
},
|
||||
data,
|
||||
)
|
||||
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<span></span>` +
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
|
||||
data.msg1 = 'hello'
|
||||
data.msg2 = 'vapor'
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<span></span>` +
|
||||
`<!--[--><span>hello</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><span>vapor</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<span></span>` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// test('element with ref', () => {
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
import {
|
||||
createComment,
|
||||
createTextNode,
|
||||
nextVaporFragmentAnchor,
|
||||
findVaporFragmentAnchor,
|
||||
} from './dom/node'
|
||||
import {
|
||||
type Block,
|
||||
|
@ -34,7 +34,6 @@ import { renderEffect } from './renderEffect'
|
|||
import { VaporVForFlags } from '../../shared/src/vaporFlags'
|
||||
import {
|
||||
currentHydrationNode,
|
||||
isComment,
|
||||
isHydrating,
|
||||
locateHydrationNode,
|
||||
} from './dom/hydration'
|
||||
|
@ -99,15 +98,20 @@ export const createFor = (
|
|||
let oldBlocks: ForBlock[] = []
|
||||
let newBlocks: ForBlock[]
|
||||
let parent: ParentNode | undefined | null
|
||||
const parentAnchor = isHydrating
|
||||
? // Use fragment end anchor if available, otherwise use the specific for anchor.
|
||||
nextVaporFragmentAnchor(
|
||||
let parentAnchor: Node
|
||||
if (isHydrating) {
|
||||
parentAnchor = findVaporFragmentAnchor(
|
||||
currentHydrationNode!,
|
||||
isComment(currentHydrationNode!, '[') ? ']' : FOR_ANCHOR_LABEL,
|
||||
FOR_ANCHOR_LABEL,
|
||||
)!
|
||||
: __DEV__
|
||||
? createComment('for')
|
||||
: createTextNode()
|
||||
if (__DEV__ && !parentAnchor) {
|
||||
// TODO warn, should not happen
|
||||
warn(`createFor anchor not found...`)
|
||||
}
|
||||
} else {
|
||||
parentAnchor = __DEV__ ? createComment('for') : createTextNode()
|
||||
}
|
||||
|
||||
const frag = new VaporFragment(oldBlocks)
|
||||
const instance = currentInstance!
|
||||
const canUseFastRemove = flags & VaporVForFlags.FAST_REMOVE
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
import {
|
||||
createComment,
|
||||
createTextNode,
|
||||
nextVaporFragmentAnchor,
|
||||
findVaporFragmentAnchor,
|
||||
} from './dom/node'
|
||||
import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
|
||||
import {
|
||||
|
@ -99,7 +99,7 @@ export class DynamicFragment extends VaporFragment {
|
|||
this.anchor = currentHydrationNode
|
||||
} else {
|
||||
// find next sibling dynamic fragment end anchor
|
||||
const anchor = nextVaporFragmentAnchor(currentHydrationNode!, label)!
|
||||
const anchor = findVaporFragmentAnchor(currentHydrationNode!, label)!
|
||||
if (anchor) {
|
||||
this.anchor = anchor
|
||||
} else if (__DEV__) {
|
||||
|
|
|
@ -59,7 +59,11 @@ import {
|
|||
} from './componentSlots'
|
||||
import { hmrReload, hmrRerender } from './hmr'
|
||||
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
||||
import { insertionAnchor, insertionParent } from './insertionState'
|
||||
import {
|
||||
insertionAnchor,
|
||||
insertionParent,
|
||||
resetInsertionState,
|
||||
} from './insertionState'
|
||||
|
||||
export { currentInstance } from '@vue/runtime-dom'
|
||||
|
||||
|
@ -142,6 +146,8 @@ export function createComponent(
|
|||
const _insertionAnchor = insertionAnchor
|
||||
if (isHydrating) {
|
||||
locateHydrationNode()
|
||||
} else {
|
||||
resetInsertionState()
|
||||
}
|
||||
|
||||
// vdom interop enabled and component is not an explicit vapor component
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
setInsertionState,
|
||||
} from '../insertionState'
|
||||
import {
|
||||
child,
|
||||
_child,
|
||||
disableHydrationNodeLookup,
|
||||
enableHydrationNodeLookup,
|
||||
next,
|
||||
|
@ -28,6 +28,7 @@ export function withHydration(container: ParentNode, fn: () => void): void {
|
|||
if (!isOptimized) {
|
||||
// optimize anchor cache lookup
|
||||
;(Comment.prototype as any).$fs = undefined
|
||||
;(Node.prototype as any).$nc = undefined
|
||||
isOptimized = true
|
||||
}
|
||||
enableHydrationNodeLookup()
|
||||
|
@ -87,19 +88,17 @@ function locateHydrationNodeImpl(hasFragmentAnchor?: boolean) {
|
|||
let node: Node | null
|
||||
// prepend / firstChild
|
||||
if (insertionAnchor === 0) {
|
||||
node = child(insertionParent!)
|
||||
node = _child(insertionParent!)
|
||||
} else if (insertionAnchor) {
|
||||
// for dynamic children, use insertionAnchor as the node
|
||||
node = insertionAnchor
|
||||
} else {
|
||||
node = insertionParent ? insertionParent.lastChild : currentHydrationNode
|
||||
node = insertionParent
|
||||
? insertionParent.$nc || insertionParent.lastChild
|
||||
: currentHydrationNode
|
||||
|
||||
// if current node is fragment start anchor, find the next one
|
||||
if (node && isComment(node, '[')) {
|
||||
node = node.nextSibling
|
||||
}
|
||||
// if the last child is a vapor fragment end anchor, find the previous one
|
||||
else if (hasFragmentAnchor && node && isVaporFragmentEndAnchor(node)) {
|
||||
if (hasFragmentAnchor && node && isVaporFragmentEndAnchor(node)) {
|
||||
node = node.previousSibling
|
||||
if (__DEV__ && !node) {
|
||||
// TODO warning, should not happen
|
||||
|
@ -135,6 +134,10 @@ function locateHydrationNodeImpl(hasFragmentAnchor?: boolean) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (insertionParent && node) {
|
||||
insertionParent.$nc = node!.previousSibling
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__ && !node) {
|
||||
|
|
|
@ -22,10 +22,42 @@ export function querySelector(selectors: string): Element | null {
|
|||
}
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export function child(node: ParentNode): Node {
|
||||
export function _child(node: ParentNode): Node {
|
||||
return node.firstChild!
|
||||
}
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export function __child(node: ParentNode): Node {
|
||||
/**
|
||||
* During hydration, the first child of a node not be the expected
|
||||
* if the first child is slot
|
||||
*
|
||||
* for template code: `div><slot />{{ data }}</div>`
|
||||
* - slot: 'slot',
|
||||
* - data: 'hi',
|
||||
*
|
||||
* client side:
|
||||
* const n2 = _template("<div> </div>")()
|
||||
* const n1 = _child(n2) -> the text node
|
||||
* _setInsertionState(n2, 0) -> slot fragment
|
||||
*
|
||||
* during hydration:
|
||||
* const n2 = _template("<div><!--[-->slot<!--]--><!--slot-->Hi</div>")()
|
||||
* const n1 = _child(n2) -> should be `Hi` instead of the slot fragment
|
||||
* _setInsertionState(n2, 0) -> slot fragment
|
||||
*/
|
||||
let n = node.firstChild!
|
||||
|
||||
if (isComment(n, '[')) {
|
||||
n = locateEndAnchor(n)!.nextSibling!
|
||||
}
|
||||
|
||||
while (n && isVaporFragmentEndAnchor(n)) {
|
||||
n = n.nextSibling!
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export function _nthChild(node: Node, i: number): Node {
|
||||
return node.childNodes[i]
|
||||
|
@ -56,9 +88,13 @@ export function __next(node: Node): Node {
|
|||
return n
|
||||
}
|
||||
|
||||
type ChildFn = (node: ParentNode) => Node
|
||||
type NextFn = (node: Node) => Node
|
||||
type NthChildFn = (node: Node, i: number) => Node
|
||||
|
||||
interface DelegatedChildFunction extends ChildFn {
|
||||
impl: ChildFn
|
||||
}
|
||||
interface DelegatedNextFunction extends NextFn {
|
||||
impl: NextFn
|
||||
}
|
||||
|
@ -66,6 +102,12 @@ interface DelegatedNthChildFunction extends NthChildFn {
|
|||
impl: NthChildFn
|
||||
}
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export const child: DelegatedChildFunction = node => {
|
||||
return child.impl(node)
|
||||
}
|
||||
child.impl = _child
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export const next: DelegatedNextFunction = node => {
|
||||
return next.impl(node)
|
||||
|
@ -90,11 +132,13 @@ nthChild.impl = _nthChild
|
|||
// of `next` and `nthChild`. After hydration is complete, their implementations
|
||||
// are restored to the original versions.
|
||||
export function enableHydrationNodeLookup(): void {
|
||||
child.impl = __child
|
||||
next.impl = __next
|
||||
nthChild.impl = __nthChild
|
||||
}
|
||||
|
||||
export function disableHydrationNodeLookup(): void {
|
||||
child.impl = _child
|
||||
next.impl = _next
|
||||
nthChild.impl = _nthChild
|
||||
}
|
||||
|
@ -112,15 +156,10 @@ function isNonHydrationNode(node: Node) {
|
|||
)
|
||||
}
|
||||
|
||||
export function nextVaporFragmentAnchor(
|
||||
export function findVaporFragmentAnchor(
|
||||
node: Node,
|
||||
anchorLabel: string,
|
||||
): Comment | null {
|
||||
node = handleWrappedNode(node)
|
||||
if (isComment(node, anchorLabel)) {
|
||||
return node as Comment
|
||||
}
|
||||
|
||||
let n = node.nextSibling
|
||||
while (n) {
|
||||
if (isComment(n, anchorLabel)) return n
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
export let insertionParent: ParentNode | undefined
|
||||
export let insertionParent:
|
||||
| (ParentNode & {
|
||||
// the next child node to be hydrated
|
||||
$nc?: Node | null
|
||||
})
|
||||
| undefined
|
||||
export let insertionAnchor: Node | 0 | undefined
|
||||
|
||||
/**
|
||||
|
|
|
@ -104,7 +104,7 @@ export function ssrRenderSlotInner(
|
|||
if (
|
||||
transition &&
|
||||
slotBuffer[0] === '<!--[-->' &&
|
||||
slotBuffer[end - 1] === '<!--]-->'
|
||||
(slotBuffer[end - 1] as string).startsWith('<!--]-->')
|
||||
) {
|
||||
start++
|
||||
end--
|
||||
|
|
Loading…
Reference in New Issue