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: '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
+}