Skip to content

fix: make hydration more html whitespace tolerant #16243

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

janvogt
Copy link

@janvogt janvogt commented Jun 26, 2025

See:

Before submitting the PR, please make sure you do the following

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • Prefix your PR title with feat:, fix:, chore:, or docs:.
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.
  • If this PR changes code within packages/svelte/src, add a changeset (npx changeset).

Tests and linting

  • Run the tests with pnpm test and lint the project with pnpm lint

Copy link

changeset-bot bot commented Jun 26, 2025

🦋 Changeset detected

Latest commit: 94c4784

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
svelte Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@svelte-docs-bot
Copy link

.envrc Outdated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this and the flake files seem like accidental commits - could you remove them?

Copy link
Author

@janvogt janvogt Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I just did. I also commited my .gitignore changes, as a separate commit. Is that acceptabl? Otherwise I can remove 94c4784 again.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you remove these tests?

Copy link
Author

@janvogt janvogt Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not remove these tests, I changed them: They were asserting a hydration failure, but with this fix hydration does not fail anymore.

Well, at least it was not my intention to remove them. What I know for sure is that the respective tests' folders including the test .svelte components still exist and the test count did not decrease. Without this change these tests will fail with the - unfulfilled- expectation of a failed hydration.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upon further investigation I realized cloudflare-mirage-borking should still continue to fail hydration, as the failure is unrelated to this fix (additional element inserted, so not a seatically meaningless whitespace change). I updated to PR to only expect successful hydration for cloudflare-mirage-borking-2.

Copy link
Contributor

Playground

pnpm add https://pkg.pr.new/svelte@16243

Jan Vogt added 4 commits June 26, 2025 09:49
one of the cases we CAN hydrate
this fixes sveltejs#16242 and allows to sucessfully hydrate sveltejs#15851 as well.
For some reason await blocks render html whitespace and
their custom hydration error handling seems to depend
on hydrating text nodes.
*/
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()) {
Copy link
Contributor

@7nik 7nik Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trimming "breaks" things - this is why you did hydrate_next(true) for the await block, but similar thing can be in any block. Use

import { test } from '../../test';

export default test({
    before_test() {
        window.document.body.insertBefore(window.document.createTextNode(''), window.document.querySelector('main'))
    }
});

to insert empty text node during testing.

Copy link
Author

@janvogt janvogt Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I realized it when running the full test-suite. However, the await case was the only one in the entire test suite that seem to rely on whitespace within html. So, allowed for the new logic to be disabled for that case. Notably, this isn't even a hydration test.

On a more conceptual level it is a bad idea to rely on whitespace within html to be preserved. There are countless tools - internal and external - that do non-semantic whitespace changes in html.

Unfortunately, I don't know enough about why exactly preserving whitespace is currently needed, to propose a more principled solution. All I can say is that the new test I added should at least fail gracefully and not crash the entire app resulting in a blank page after hydration.

Copy link
Author

@janvogt janvogt Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also browsers mostly ignore whitespace, so as a developer I didn't expect the observed crash to blank page of a complex production app to be caused by hydration not able to handle modified white space. Depending on it, will certainly lead to a terrible dev experience for many more. This would be very incompatible with the svelte experience, you awesome people got me hooked on (pun very much intended). Further reading: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, the tests are a far from covering all possible cases.

The case is the following nodes:

  1. hydration mark;
  2. text node (user content + " ", so it's guarantied it is a non-empty text);
  3. other nodes.

and when the text node happens to be just a whitespace, you remove it, that cases the shift of nodes order.

Copy link
Author

@janvogt janvogt Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, the tests are a far from covering all possible cases.

Ok, I feared that being the case. That makes it much harder for me to contribute a fix.

The case is the following nodes:

  1. hydration mark;

  2. text node (user content + " ", so it's guarantied it is a non-empty text);

  3. other nodes.

If you could provide tests for the relevant cases, I cloud try to find a more general solution.

If not, I suggest we just prevent the failing call to appendChild and handle most html whitespace manipulation as hydration mismatches. I can change my PR to do that. The unfortunate thing is, that I believe pre svelte 5 the hydration was much more robust in that regard.

and when the text node happens to be just a whitespace, you remove it, that cases the shift of nodes order.
Yes, that was expected.

In the test suite it only resulted in a gracefully handled hydration mismatch for the await tests whilst making more hydration tests actually hydrate. So it seemed like a good compromise. To make await work as well I allowed to opt out of text node skipping.

If I understand correctly now, you're saying that relying on text nodes to carry relevance happens far more than the existing tests suggest. In that case I am asking myself if that information could be transmitted in a more reliable way. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the REPL (check console) in my first message: this branch is causes hydration_mismatch, on 5.34.8 - all good.
More advanced case where this PR causes crash.

Oh, I seem to misread the issue - I thought GT inserts empty text nodes.

We'll make a more general hydration fix.

Copy link
Author

@janvogt janvogt Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll make a more general hydration fix.

Awesome. Should I change this commit to only contain the crashing test case?

Oh, I seem to misread the issue - I thought GT inserts empty text nodes.

Sorry I don't follow. What does GT stand for here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Google Translate

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that makes sense 😅

As empty is ambiguous: GT does insert whitespace text nodes. So in HTML they are semantically empty but literally they are not as they contain newline and space characters, which causes the issue in my case.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW. I tried to reproduce the issue within the REPL. Unfortunately without success. So the test in f7ce3f6a9 is the only way to show the crash.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants