diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts index fa069b92530..6bbe4118295 100644 --- a/packages/runtime-core/__tests__/hydration.spec.ts +++ b/packages/runtime-core/__tests__/hydration.spec.ts @@ -1406,6 +1406,14 @@ describe('SSR hydration', () => { mountWithHydration(`
`, () => h('div', { class: 'foo bar' }) ) + // SVG classes + mountWithHydration(``, () => + h('svg', { class: 'foo bar' }) + ) + // class with different order + mountWithHydration(`
`, () => + h('div', { class: 'bar foo' }) + ) expect(`Hydration class mismatch`).not.toHaveBeenWarned() mountWithHydration(`
`, () => h('div', { class: 'foo' }) diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index 57ced5118a6..d42a9952f25 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -718,9 +718,11 @@ function propHasMismatch(el: Element, key: string, clientValue: any): boolean { let actual: any let expected: any if (key === 'class') { - actual = el.getAttribute('class') - expected = normalizeClass(clientValue) - if (actual !== expected) { + // 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 = toClassSet(el.getAttribute('class') || '') + expected = toClassSet(normalizeClass(clientValue)) + if (!isSetEqual(actual, expected)) { mismatchType = mismatchKey = `class` } } else if (key === 'style') { @@ -765,3 +767,19 @@ function propHasMismatch(el: Element, key: string, clientValue: any): boolean { } return false } + +function toClassSet(str: string): Set { + return new Set(str.trim().split(/\s+/)) +} + +function isSetEqual(a: Set, b: Set): boolean { + if (a.size !== b.size) { + return false + } + for (const s of a) { + if (!b.has(s)) { + return false + } + } + return true +}