diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index ae660abf1..e41a4fdc9 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -805,16 +805,16 @@ export function createHydrationFunctions( } } - const isTemplateNode = (node: Node): node is HTMLTemplateElement => { - return ( - node.nodeType === DOMNodeTypes.ELEMENT && - (node as Element).tagName === 'TEMPLATE' - ) - } - return [hydrate, hydrateNode] } +export const isTemplateNode = (node: Node): node is HTMLTemplateElement => { + return ( + node.nodeType === DOMNodeTypes.ELEMENT && + (node as Element).tagName === 'TEMPLATE' + ) +} + /** * Dev only */ diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 533000ee8..1c7fa5d78 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -601,3 +601,7 @@ export { createInternalObject } from './internalObject' * @internal */ export { createCanSetSetupRefChecker } from './rendererTemplateRef' +/** + * @internal + */ +export { isTemplateNode } from './hydration' diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts index 72b7e0787..999caf97c 100644 --- a/packages/runtime-vapor/__tests__/hydration.spec.ts +++ b/packages/runtime-vapor/__tests__/hydration.spec.ts @@ -1568,8 +1568,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " - abc - " + abc" `, ) @@ -1578,8 +1577,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " - abcd - " + abcd" `, ) }) @@ -1601,8 +1599,7 @@ describe('Vapor Mode hydration', () => { ` "
- abc -
3
+ abc
3
" `, ) @@ -1613,8 +1610,7 @@ describe('Vapor Mode hydration', () => { ` "
- abcd -
4
+ abcd
4
" `, ) @@ -1635,8 +1631,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- abc -
" + abc" `, ) @@ -1645,8 +1640,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- abcd -
" + abcd" `, ) @@ -1655,8 +1649,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- bcd -
" + bcd" `, ) }) @@ -1677,9 +1670,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- abc - abc -
" + abc + abc" `, ) @@ -1688,9 +1680,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- abcd - abcd -
" + abcd + abcd" `, ) @@ -1699,9 +1690,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- cd - cd -
" + cd + cd" `, ) }) @@ -1722,8 +1712,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
comp
comp
comp
-
" +
comp
comp
comp
" `, ) @@ -1732,8 +1721,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
comp
comp
comp
comp
-
" +
comp
comp
comp
comp
" `, ) }) @@ -1758,8 +1746,7 @@ describe('Vapor Mode hydration', () => { a b - c - + c " `, ) @@ -1772,8 +1759,7 @@ describe('Vapor Mode hydration', () => { a b - cd - + cd " `, ) @@ -1797,8 +1783,7 @@ describe('Vapor Mode hydration', () => {
foo
-bar-
foo
-bar- -
foo
-bar- - +
foo
-bar- " `, ) @@ -1811,8 +1796,7 @@ describe('Vapor Mode hydration', () => {
foo
-bar-
foo
-bar- -
foo
-bar-
foo
-bar- - +
foo
-bar-
foo
-bar- " `, ) @@ -1950,8 +1934,7 @@ describe('Vapor Mode hydration', () => { ` " - abc - + abc " `, ) @@ -2383,10 +2366,9 @@ describe('Vapor Mode hydration', () => { ` " -
a
b
c
+
a
b
c
foo -
a
b
c
- +
a
b
c
" `, ) @@ -2397,10 +2379,9 @@ describe('Vapor Mode hydration', () => { ` " -
a
b
c
d
+
a
b
c
d
foo -
a
b
c
d
- +
a
b
c
d
" `, ) @@ -2725,14 +2706,115 @@ describe('Vapor Mode hydration', () => { }) }) - describe.todo('transition', async () => { - test('transition appear', async () => {}) + describe('transition', async () => { + test('transition appear', async () => { + const { container } = await testHydration( + ``, + ) + expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( + `"
foo
"`, + ) + expect(`mismatch`).not.toHaveBeenWarned() + }) - test('transition appear with v-if', async () => {}) + test('transition appear work with pre-existing class', async () => { + const { container } = await testHydration( + ``, + ) + expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( + `"
foo
"`, + ) + expect(`mismatch`).not.toHaveBeenWarned() + }) - test('transition appear with v-show', async () => {}) + test('transition appear work with empty content', async () => { + const data = ref(true) + const { container } = await testHydration( + ``, + undefined, + data, + ) + expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( + `""`, + ) + expect(`mismatch`).not.toHaveBeenWarned() - test('transition appear w/ event listener', async () => {}) + data.value = false + await nextTick() + expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( + `"foo"`, + ) + }) + + test('transition appear with v-if', async () => { + const data = ref(false) + const { container } = await testHydration( + ``, + undefined, + data, + ) + expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( + `""`, + ) + expect(`mismatch`).not.toHaveBeenWarned() + }) + + test('transition appear with v-show', async () => { + const data = ref(false) + const { container } = await testHydration( + ``, + undefined, + data, + ) + expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( + `""`, + ) + expect(`mismatch`).not.toHaveBeenWarned() + }) + + test('transition appear w/ event listener', async () => { + const { container } = await testHydration( + ` + `, + ) + expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( + `""`, + ) + + triggerEvent('click', container.querySelector('button')!) + await nextTick() + expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( + `""`, + ) + }) }) describe.todo('async component', async () => { diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 7954dcb05..4e88afe8f 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -135,9 +135,11 @@ export const createFor = ( if (isHydrating) { parentAnchor = locateFragmentEndAnchor()! - // TODO: special handling vFor not render as a fragment. (inside Transition/TransitionGroup) - if (__DEV__ && !parentAnchor) { - throw new Error(`v-for fragment anchor node was not found.`) + if (__DEV__) { + if (!parentAnchor) { + throw new Error(`v-for fragment anchor node was not found.`) + } + ;(parentAnchor as Comment).data = 'for' } } } else { diff --git a/packages/runtime-vapor/src/components/Transition.ts b/packages/runtime-vapor/src/components/Transition.ts index 560b277cb..b3deaf0b6 100644 --- a/packages/runtime-vapor/src/components/Transition.ts +++ b/packages/runtime-vapor/src/components/Transition.ts @@ -1,4 +1,5 @@ import { + type BaseTransitionProps, type GenericComponentInstance, type TransitionElement, type TransitionHooks, @@ -9,7 +10,9 @@ import { baseResolveTransitionHooks, checkTransitionMode, currentInstance, + isTemplateNode, leaveCbKey, + queuePostFlushCb, resolveTransitionProps, useTransitionState, warn, @@ -24,6 +27,11 @@ import { import { extend, isArray } from '@vue/shared' import { renderEffect } from '../renderEffect' import { isFragment } from '../fragment' +import { + currentHydrationNode, + isHydrating, + setCurrentHydrationNode, +} from '../dom/hydration' const decorate = (t: typeof VaporTransition) => { t.displayName = 'VaporTransition' @@ -34,6 +42,33 @@ const decorate = (t: typeof VaporTransition) => { export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate( (props, { slots, attrs }) => { + // wrapped + let resetDisplay: Function | undefined + if ( + isHydrating && + currentHydrationNode && + isTemplateNode(currentHydrationNode) + ) { + // replace