diff --git a/.changeset/curvy-colts-occur.md b/.changeset/curvy-colts-occur.md new file mode 100644 index 000000000000..3245e6638206 --- /dev/null +++ b/.changeset/curvy-colts-occur.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: make hydration less whitespace sensitive diff --git a/.gitignore b/.gitignore index d50343766485..6d05d789a674 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,13 @@ coverage # dotenv environment variables file .env .env.test +flake.nix +flake.lock +.envrc # build output .vercel +.direnv # OS-specific .DS_Store diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index 47df5fc9a5f8..2c3e3af4a798 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -36,7 +36,7 @@ const CATCH = 2; */ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) { if (hydrating) { - hydrate_next(); + hydrate_next(true); } var anchor = node; diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 1f80b7922bc2..37492c80e298 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -1,6 +1,6 @@ /** @import { TemplateNode } from '#client' */ -import { COMMENT_NODE } from '#client/constants'; +import { COMMENT_NODE, TEXT_NODE } from '#client/constants'; import { HYDRATION_END, HYDRATION_ERROR, @@ -40,8 +40,30 @@ export function set_hydrate_node(node) { return (hydrate_node = node); } -export function hydrate_next() { - return set_hydrate_node(/** @type {TemplateNode} */ (get_next_sibling(hydrate_node))); +/** + * Moove to the next node to be hydrated. Empty text nodes will be skipped, + * unless `allow_text` is set to true. + * + * Skipping whitespace helps to sucessful hydrate even if some middleware added + * arbitrary whitespace into the html. This was at least twice an issue: + * + * - https://github.com/sveltejs/svelte/issues/15819 + * - https://github.com/sveltejs/svelte/issues/16242 + * + * Removing empty text nodes should be finde, as required text nodes will be + * added on demand. Doing so is necessary because an empty text on the server + * side will result in a missing text nodes as well. + * + * @param {boolean} allow_text + */ +export function hydrate_next(allow_text = false) { + var node = set_hydrate_node(/** @type {TemplateNode} */(get_next_sibling(hydrate_node))); + while (!allow_text && node.nodeType === TEXT_NODE && !node.nodeValue?.trim()) { + var next_sibling = get_next_sibling(hydrate_node) + hydrate_node.parentElement?.removeChild(hydrate_node) + node = set_hydrate_node(/** @type {TemplateNode} */(next_sibling)) + } + return node } /** @param {TemplateNode} node */ diff --git a/packages/svelte/tests/hydration/samples/cloudflare-mirage-borking-2/_config.js b/packages/svelte/tests/hydration/samples/cloudflare-mirage-borking-2/_config.js deleted file mode 100644 index 56ba73b06408..000000000000 --- a/packages/svelte/tests/hydration/samples/cloudflare-mirage-borking-2/_config.js +++ /dev/null @@ -1,6 +0,0 @@ -import { test } from '../../test'; - -// https://github.com/sveltejs/svelte/issues/15819 -export default test({ - expect_hydration_error: true -}); diff --git a/packages/svelte/tests/hydration/samples/cloudflare-mirage-borking-2/_expected.html b/packages/svelte/tests/hydration/samples/cloudflare-mirage-borking-2/_expected.html deleted file mode 100644 index 5179fb04a5f7..000000000000 --- a/packages/svelte/tests/hydration/samples/cloudflare-mirage-borking-2/_expected.html +++ /dev/null @@ -1 +0,0 @@ -

start

cond

diff --git a/packages/svelte/tests/hydration/samples/cloudflare-mirage-borking-2/main.svelte b/packages/svelte/tests/hydration/samples/cloudflare-mirage-borking-2/main.svelte index bfb4f2cdb8cf..3f0097fb9ddd 100644 --- a/packages/svelte/tests/hydration/samples/cloudflare-mirage-borking-2/main.svelte +++ b/packages/svelte/tests/hydration/samples/cloudflare-mirage-borking-2/main.svelte @@ -1,4 +1,5 @@ diff --git a/packages/svelte/tests/hydration/samples/whitespace-before-child/Nested.svelte b/packages/svelte/tests/hydration/samples/whitespace-before-child/Nested.svelte new file mode 100644 index 000000000000..c230e50ebc61 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/whitespace-before-child/Nested.svelte @@ -0,0 +1 @@ +

nested

diff --git a/packages/svelte/tests/hydration/samples/whitespace-before-child/_override.html b/packages/svelte/tests/hydration/samples/whitespace-before-child/_override.html new file mode 100644 index 000000000000..90ca4ef4b8c9 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/whitespace-before-child/_override.html @@ -0,0 +1,2 @@ + +

nested

\ No newline at end of file diff --git a/packages/svelte/tests/hydration/samples/whitespace-before-child/main.svelte b/packages/svelte/tests/hydration/samples/whitespace-before-child/main.svelte new file mode 100644 index 000000000000..0aa292515099 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/whitespace-before-child/main.svelte @@ -0,0 +1,7 @@ + +
+ +