From 5cf2b7acdd86a0a2e3f788b04aa77d8216492f8f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 26 Nov 2023 21:09:26 -0500 Subject: [PATCH 1/2] ignore href when hydrating --- .changeset/slimy-clouds-talk.md | 5 +++ packages/svelte/src/internal/client/render.js | 41 +++++++++---------- .../ignore-mismatched-href/_before.html | 1 + .../samples/ignore-mismatched-href/_config.js | 7 ++++ .../ignore-mismatched-href/main.svelte | 5 +++ 5 files changed, 38 insertions(+), 21 deletions(-) create mode 100644 .changeset/slimy-clouds-talk.md create mode 100644 packages/svelte/tests/hydration/samples/ignore-mismatched-href/_before.html create mode 100644 packages/svelte/tests/hydration/samples/ignore-mismatched-href/_config.js create mode 100644 packages/svelte/tests/hydration/samples/ignore-mismatched-href/main.svelte diff --git a/.changeset/slimy-clouds-talk.md b/.changeset/slimy-clouds-talk.md new file mode 100644 index 000000000000..f92019e7d368 --- /dev/null +++ b/.changeset/slimy-clouds-talk.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: ignore href attributes when hydrating diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index afb060639a54..6fcd8a056fba 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -2532,6 +2532,7 @@ export function attr(dom, attribute, value) { // (we can't just compare the strings as they can be different between client and server but result in the // same url, so we would need to create hidden anchor elements to compare them) attribute !== 'src' && + attribute !== 'href' && attribute !== 'srcset') ) { if (value === null) { @@ -2566,13 +2567,13 @@ function split_srcset(srcset) { } /** - * @param {HTMLSourceElement | HTMLImageElement} element_srcset + * @param {HTMLSourceElement | HTMLImageElement} element * @param {string | undefined | null} srcset * @returns {boolean} */ -export function srcset_url_equal(element_srcset, srcset) { - const element_urls = split_srcset(element_srcset.srcset); - const urls = split_srcset(srcset || ''); +export function srcset_url_equal(element, srcset) { + const element_urls = split_srcset(element.srcset); + const urls = split_srcset(srcset ?? ''); return ( urls.length === element_urls.length && @@ -2595,22 +2596,20 @@ export function srcset_url_equal(element_srcset, srcset) { * @param {string | null} value */ function check_src_in_dev_hydration(dom, attribute, value) { - if (current_hydration_fragment !== null && (attribute === 'src' || attribute === 'srcset')) { - if ( - (attribute === 'src' && !src_url_equal(dom.getAttribute('src') || '', value || '')) || - (attribute === 'srcset' && - !srcset_url_equal(/** @type {HTMLImageElement | HTMLSourceElement} */ (dom), value || '')) - ) { - // eslint-disable-next-line no-console - console.error( - 'Detected a src/srcset attribute value change during hydration. This will not be repaired during hydration, ' + - 'the src/srcset value that came from the server will be used. Related element:', - dom, - ' Differing value:', - value - ); - } - } + if (!current_hydration_fragment) return; + if (attribute !== 'src' && attribute !== 'href' && attribute !== 'srcset') return; + + if (attribute === 'srcset' && srcset_url_equal(dom, value)) return; + if (src_url_equal(dom.getAttribute(attribute) ?? '', value ?? '')) return; + + // eslint-disable-next-line no-console + console.error( + `Detected a ${attribute} attribute value change during hydration. This will not be repaired during hydration, ` + + `the ${attribute} value that came from the server will be used. Related element:`, + dom, + ' Differing value:', + value + ); } /** @@ -2778,7 +2777,7 @@ export function spread_attributes(dom, prev, attrs, lowercase_attributes, css_ha if ( current_hydration_fragment === null || // @ts-ignore see attr method for an explanation of src/srcset - (dom[name] !== value && name !== 'src' && name !== 'srcset') + (dom[name] !== value && name !== 'src' && name !== 'href' && name !== 'srcset') ) { // @ts-ignore dom[name] = value; diff --git a/packages/svelte/tests/hydration/samples/ignore-mismatched-href/_before.html b/packages/svelte/tests/hydration/samples/ignore-mismatched-href/_before.html new file mode 100644 index 000000000000..afeffd5eb63d --- /dev/null +++ b/packages/svelte/tests/hydration/samples/ignore-mismatched-href/_before.html @@ -0,0 +1 @@ +foo diff --git a/packages/svelte/tests/hydration/samples/ignore-mismatched-href/_config.js b/packages/svelte/tests/hydration/samples/ignore-mismatched-href/_config.js new file mode 100644 index 000000000000..cc24163f2c5c --- /dev/null +++ b/packages/svelte/tests/hydration/samples/ignore-mismatched-href/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + test(assert, target) { + assert.equal(target.querySelector('a')?.getAttribute('href'), '/bar'); + } +}); diff --git a/packages/svelte/tests/hydration/samples/ignore-mismatched-href/main.svelte b/packages/svelte/tests/hydration/samples/ignore-mismatched-href/main.svelte new file mode 100644 index 000000000000..5dcd4d4002ad --- /dev/null +++ b/packages/svelte/tests/hydration/samples/ignore-mismatched-href/main.svelte @@ -0,0 +1,5 @@ + + +foo From fb6c2b888a6e757ffde41abcbd61209d46550402 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 27 Nov 2023 08:42:20 -0500 Subject: [PATCH 2/2] remove unused export keyword --- packages/svelte/src/internal/client/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 6fcd8a056fba..5a2eed8703bc 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -2551,7 +2551,7 @@ let src_url_equal_anchor; * @param {string} url * @returns {boolean} */ -export function src_url_equal(element_src, url) { +function src_url_equal(element_src, url) { if (element_src === url) return true; if (!src_url_equal_anchor) { src_url_equal_anchor = document.createElement('a');