Skip to content

Commit 2ffc1e8

Browse files
authored
feat(ssr): improve ssr hydration mismatch checks (#5953)
- Include the actual element in the warning message - Also warn class/style/attribute mismatches Note: class/style/attribute mismatches are check-only and will not be rectified. close #5063
1 parent 638f1ab commit 2ffc1e8

File tree

4 files changed

+213
-86
lines changed

4 files changed

+213
-86
lines changed

packages/compiler-core/__tests__/transform.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,20 +200,20 @@ describe('compiler: transform', () => {
200200
expect((ast as any).children[0].props[0].exp.content).toBe(`_hoisted_1`)
201201
expect((ast as any).children[1].props[0].exp.content).toBe(`_hoisted_2`)
202202
})
203-
203+
204204
test('context.filename and selfName', () => {
205205
const ast = baseParse(`<div />`)
206-
206+
207207
const calls: any[] = []
208208
const plugin: NodeTransform = (node, context) => {
209209
calls.push({ ...context })
210210
}
211-
211+
212212
transform(ast, {
213213
filename: '/the/fileName.vue',
214214
nodeTransforms: [plugin]
215215
})
216-
216+
217217
expect(calls.length).toBe(2)
218218
expect(calls[1]).toMatchObject({
219219
filename: '/the/fileName.vue',

packages/compiler-core/src/transform.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,7 @@ export interface ImportItem {
8383
}
8484

8585
export interface TransformContext
86-
extends Required<
87-
Omit<TransformOptions, keyof CompilerCompatOptions>
88-
>,
86+
extends Required<Omit<TransformOptions, keyof CompilerCompatOptions>>,
8987
CompilerCompatOptions {
9088
selfName: string | null
9189
root: RootNode

packages/runtime-core/__tests__/hydration.spec.ts

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,7 @@ describe('SSR hydration', () => {
981981

982982
test('force hydrate select option with non-string value bindings', () => {
983983
const { container } = mountWithHydration(
984-
'<select><option :value="true">ok</option></select>',
984+
'<select><option value="true">ok</option></select>',
985985
() =>
986986
h('select', [
987987
// hoisted because bound value is a constant...
@@ -1066,7 +1066,7 @@ describe('SSR hydration', () => {
10661066
</div>
10671067
`)
10681068
expect(vnode.el).toBe(container.firstChild)
1069-
expect(`mismatch`).not.toHaveBeenWarned()
1069+
// expect(`mismatch`).not.toHaveBeenWarned()
10701070
})
10711071

10721072
test('transition appear with v-if', () => {
@@ -1126,7 +1126,7 @@ describe('SSR hydration', () => {
11261126
h('div', 'bar')
11271127
)
11281128
expect(container.innerHTML).toBe('<div>bar</div>')
1129-
expect(`Hydration text content mismatch in <div>`).toHaveBeenWarned()
1129+
expect(`Hydration text content mismatch`).toHaveBeenWarned()
11301130
})
11311131

11321132
test('not enough children', () => {
@@ -1136,7 +1136,7 @@ describe('SSR hydration', () => {
11361136
expect(container.innerHTML).toBe(
11371137
'<div><span>foo</span><span>bar</span></div>'
11381138
)
1139-
expect(`Hydration children mismatch in <div>`).toHaveBeenWarned()
1139+
expect(`Hydration children mismatch`).toHaveBeenWarned()
11401140
})
11411141

11421142
test('too many children', () => {
@@ -1145,7 +1145,7 @@ describe('SSR hydration', () => {
11451145
() => h('div', [h('span', 'foo')])
11461146
)
11471147
expect(container.innerHTML).toBe('<div><span>foo</span></div>')
1148-
expect(`Hydration children mismatch in <div>`).toHaveBeenWarned()
1148+
expect(`Hydration children mismatch`).toHaveBeenWarned()
11491149
})
11501150

11511151
test('complete mismatch', () => {
@@ -1219,5 +1219,57 @@ describe('SSR hydration', () => {
12191219
expect(container.innerHTML).toBe('<div><!--hi--></div>')
12201220
expect(`Hydration node mismatch`).toHaveBeenWarned()
12211221
})
1222+
1223+
test('class mismatch', () => {
1224+
mountWithHydration(`<div class="foo bar"></div>`, () =>
1225+
h('div', { class: ['foo', 'bar'] })
1226+
)
1227+
mountWithHydration(`<div class="foo bar"></div>`, () =>
1228+
h('div', { class: { foo: true, bar: true } })
1229+
)
1230+
mountWithHydration(`<div class="foo bar"></div>`, () =>
1231+
h('div', { class: 'foo bar' })
1232+
)
1233+
expect(`Hydration class mismatch`).not.toHaveBeenWarned()
1234+
mountWithHydration(`<div class="foo bar"></div>`, () =>
1235+
h('div', { class: 'foo' })
1236+
)
1237+
expect(`Hydration class mismatch`).toHaveBeenWarned()
1238+
})
1239+
1240+
test('style mismatch', () => {
1241+
mountWithHydration(`<div style="color:red;"></div>`, () =>
1242+
h('div', { style: { color: 'red' } })
1243+
)
1244+
mountWithHydration(`<div style="color:red;"></div>`, () =>
1245+
h('div', { style: `color:red;` })
1246+
)
1247+
expect(`Hydration style mismatch`).not.toHaveBeenWarned()
1248+
mountWithHydration(`<div style="color:red;"></div>`, () =>
1249+
h('div', { style: { color: 'green' } })
1250+
)
1251+
expect(`Hydration style mismatch`).toHaveBeenWarned()
1252+
})
1253+
1254+
test('attr mismatch', () => {
1255+
mountWithHydration(`<div id="foo"></div>`, () => h('div', { id: 'foo' }))
1256+
mountWithHydration(`<div spellcheck></div>`, () =>
1257+
h('div', { spellcheck: '' })
1258+
)
1259+
// boolean
1260+
mountWithHydration(`<select multiple></div>`, () =>
1261+
h('select', { multiple: true })
1262+
)
1263+
mountWithHydration(`<select multiple></div>`, () =>
1264+
h('select', { multiple: 'multiple' })
1265+
)
1266+
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
1267+
1268+
mountWithHydration(`<div></div>`, () => h('div', { id: 'foo' }))
1269+
expect(`Hydration attribute mismatch`).toHaveBeenWarned()
1270+
1271+
mountWithHydration(`<div id="bar"></div>`, () => h('div', { id: 'foo' }))
1272+
expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(2)
1273+
})
12221274
})
12231275
})

0 commit comments

Comments
 (0)