From 26a579b7674ab5bc716746e8580804c8cdd5588a Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 16 May 2025 20:53:04 +0800 Subject: [PATCH 1/3] fix(hydration): handle transition appear hydration edge case --- packages/runtime-core/src/hydration.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index a94ff356810..5cbf0827010 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -398,9 +398,10 @@ export function createHydrationFunctions( parentComponent.vnode.props.appear const content = (el as HTMLTemplateElement).content - .firstChild as Element + .firstChild as Element & { $cls: string | null } if (needCallTransitionHooks) { + content.$cls = content.getAttribute('class') transition!.beforeEnter(content) } @@ -786,7 +787,7 @@ export function createHydrationFunctions( * Dev only */ function propHasMismatch( - el: Element, + el: Element & { $cls?: string | null }, key: string, clientValue: any, vnode: VNode, @@ -799,7 +800,7 @@ function propHasMismatch( if (key === 'class') { // classes might be in different order, but that doesn't affect cascade // so we just need to check if the class lists contain the same classes. - actual = el.getAttribute('class') + actual = el.$cls || el.getAttribute('class') expected = normalizeClass(clientValue) if (!isSetEqual(toClassSet(actual || ''), toClassSet(expected))) { mismatchType = MismatchTypes.CLASS From 6754842570ed34de3fa69b657752b68e0b2ebf48 Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 16 May 2025 20:57:27 +0800 Subject: [PATCH 2/3] test: add test --- .../runtime-core/__tests__/hydration.spec.ts | 23 +++++++++++++++++++ packages/runtime-core/src/hydration.ts | 7 +++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts index 20519cf997f..4a9e0fac2b6 100644 --- a/packages/runtime-core/__tests__/hydration.spec.ts +++ b/packages/runtime-core/__tests__/hydration.spec.ts @@ -1654,6 +1654,29 @@ describe('SSR hydration', () => { expect(`mismatch`).not.toHaveBeenWarned() }) + test('transition appear work with pre-existing class', () => { + const { vnode, container } = mountWithHydration( + ``, + () => + h( + Transition, + { appear: true }, + { + default: () => h('div', { class: 'foo' }, 'foo'), + }, + ), + ) + expect(container.firstChild).toMatchInlineSnapshot(` +
+ foo +
+ `) + expect(vnode.el).toBe(container.firstChild) + expect(`mismatch`).not.toHaveBeenWarned() + }) + test('transition appear with v-if', () => { const show = false const { vnode, container } = mountWithHydration( diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index 5cbf0827010..49fb6e594cf 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -800,7 +800,12 @@ function propHasMismatch( if (key === 'class') { // classes might be in different order, but that doesn't affect cascade // so we just need to check if the class lists contain the same classes. - actual = el.$cls || el.getAttribute('class') + if (el.$cls) { + actual = el.$cls + delete el.$cls + } else { + actual = el.getAttribute('class') + } expected = normalizeClass(clientValue) if (!isSetEqual(toClassSet(actual || ''), toClassSet(expected))) { mismatchType = MismatchTypes.CLASS From 1a1a63da10e73fe7c7ef8fa6e0e6b205a8078df7 Mon Sep 17 00:00:00 2001 From: daiwei Date: Mon, 19 May 2025 22:17:09 +0800 Subject: [PATCH 3/3] chore: minor tweaks --- packages/runtime-core/src/hydration.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index 49fb6e594cf..6ffdbc68de4 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -398,10 +398,11 @@ export function createHydrationFunctions( parentComponent.vnode.props.appear const content = (el as HTMLTemplateElement).content - .firstChild as Element & { $cls: string | null } + .firstChild as Element & { $cls?: string } if (needCallTransitionHooks) { - content.$cls = content.getAttribute('class') + const cls = content.getAttribute('class') + if (cls) content.$cls = cls transition!.beforeEnter(content) } @@ -787,7 +788,7 @@ export function createHydrationFunctions( * Dev only */ function propHasMismatch( - el: Element & { $cls?: string | null }, + el: Element & { $cls?: string }, key: string, clientValue: any, vnode: VNode,