Skip to content

Commit a7513a1

Browse files
authored
fix(runtime-core): avoid re-fetching resource props with unchanged values (#15105)
1 parent b6191cb commit a7513a1

2 files changed

Lines changed: 71 additions & 0 deletions

File tree

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2809,6 +2809,56 @@ describe('SSR hydration', () => {
28092809
}
28102810
})
28112811

2812+
test('does not re-write unchanged resource props when hydrating', () => {
2813+
__DEV__ = false
2814+
try {
2815+
const container = document.createElement('div')
2816+
container.innerHTML = `<img src="/foo.png">`
2817+
const el = container.firstChild as HTMLImageElement
2818+
const setSrc = vi.fn()
2819+
Object.defineProperty(el, 'src', {
2820+
configurable: true,
2821+
get: () => el.getAttribute('src'),
2822+
set: setSrc,
2823+
})
2824+
createSSRApp({
2825+
render: () =>
2826+
createElementVNode(
2827+
'img',
2828+
{ src: '/foo.png' },
2829+
null,
2830+
PatchFlags.PROPS,
2831+
['src'],
2832+
),
2833+
}).mount(container)
2834+
expect(setSrc).not.toHaveBeenCalled()
2835+
expect(el.getAttribute('src')).toBe('/foo.png')
2836+
} finally {
2837+
__DEV__ = true
2838+
}
2839+
})
2840+
2841+
test('still patches resource props when server and client values differ', () => {
2842+
__DEV__ = false
2843+
try {
2844+
const { container } = mountWithHydration(
2845+
`<img src="/server.png">`,
2846+
() =>
2847+
createElementVNode(
2848+
'img',
2849+
{ src: '/client.png' },
2850+
null,
2851+
PatchFlags.PROPS,
2852+
['src'],
2853+
),
2854+
)
2855+
const el = container.firstChild as HTMLImageElement
2856+
expect(el.getAttribute('src')).toBe('/client.png')
2857+
} finally {
2858+
__DEV__ = true
2859+
}
2860+
})
2861+
28122862
test('force patch svg dynamic props with correct namespace when hydrating', () => {
28132863
__DEV__ = false
28142864
try {

packages/runtime-core/src/hydration.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,9 @@ export function createHydrationFunctions(
525525
(isCustomElement && !isReservedProp(key)) ||
526526
(dynamicProps && dynamicProps.includes(key))
527527
) {
528+
if (isUnchangedResourceProp(el, key, props[key])) {
529+
continue
530+
}
528531
patchProp(el, key, null, props[key], namespace, parentComponent)
529532
}
530533
}
@@ -806,6 +809,24 @@ export function createHydrationFunctions(
806809
/**
807810
* Dev only
808811
*/
812+
// attributes whose assignment triggers a (re)fetch of a resource
813+
const resourceProps = /*@__PURE__*/ new Set(['src', 'srcset', 'href', 'poster'])
814+
815+
function isUnchangedResourceProp(
816+
el: Element,
817+
key: string,
818+
clientValue: any,
819+
): boolean {
820+
if (!resourceProps.has(key)) {
821+
return false
822+
}
823+
// compare against the rendered attribute rather than the reflected DOM
824+
// property, which normalizes URLs to absolute form.
825+
return (
826+
el.getAttribute(key) === (clientValue == null ? null : `${clientValue}`)
827+
)
828+
}
829+
809830
function propHasMismatch(
810831
el: Element & { $cls?: string },
811832
key: string,

0 commit comments

Comments
 (0)