Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Fiber] support hydration when rendering Suspense anywhere #32224

Merged
merged 1 commit into from
Feb 4, 2025

Conversation

gnoff
Copy link
Collaborator

@gnoff gnoff commented Jan 25, 2025

follow up to #32163

This continues the work of making Suspense workable anywhere in a react-dom tree. See the prior PRs for how we handle server rendering and client rendering. In this change we update the hydration implementation to be able to locate expected nodes. In particular this means hydration understands now that the default hydration context is the document body when the container is above the body.

One case that is unique to hydration is clearing Suspense boundaries. When hydration fails or when the server instructs the client to recover an errored boundary it's possible that the html, head, and body tags in the initial document were written from a fallback or a different primary content on the server and need to be replaced by the client render. However these tags (and in the case of head, their content) won't be inside the comment nodes that identify the bounds of the Suspense boundary. And when client rendering you may not even render the same singletons that were server rendered. So when server rendering a boudnary which contributes to the preamble (the html, head, and body tag openings plus the head contents) we emit a special marker comment just before closing the boundary out. This marker encodes which parts of the preamble this boundary owned. If we need to clear the suspense boundary on the client we read this marker and use it to reset the appropriate singleton state.

@react-sizebot
Copy link

react-sizebot commented Jan 25, 2025

Comparing: 152bfe3...686c9d9

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.68 kB 6.68 kB = 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js +0.55% 514.52 kB 517.33 kB +0.55% 91.76 kB 92.27 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB = 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js +0.62% 557.55 kB 561.02 kB +0.61% 99.01 kB 99.62 kB
facebook-www/ReactDOM-prod.classic.js +0.45% 596.50 kB 599.18 kB +0.49% 104.89 kB 105.40 kB
facebook-www/ReactDOM-prod.modern.js +0.46% 586.92 kB 589.60 kB +0.48% 103.35 kB 103.85 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js +0.79% 228.10 kB 229.90 kB +0.54% 40.90 kB 41.12 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js +0.79% 228.17 kB 229.98 kB +0.54% 40.93 kB 41.15 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js +0.78% 229.85 kB 231.63 kB +0.52% 41.89 kB 42.10 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js +0.78% 229.92 kB 231.71 kB +0.52% 41.91 kB 42.13 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js +0.77% 233.30 kB 235.10 kB +0.54% 42.86 kB 43.09 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js +0.77% 233.38 kB 235.18 kB +0.55% 42.89 kB 43.12 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.production.js +0.71% 254.27 kB 256.07 kB +0.46% 43.96 kB 44.16 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js +0.71% 213.71 kB 215.23 kB +0.46% 39.45 kB 39.64 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js +0.71% 213.79 kB 215.30 kB +0.46% 39.48 kB 39.66 kB
facebook-www/ReactDOMServerStreaming-prod.modern.js +0.70% 221.38 kB 222.93 kB +0.41% 40.93 kB 41.09 kB
oss-experimental/react-dom/cjs/react-dom-server.node.production.js +0.70% 256.09 kB 257.88 kB +0.45% 45.11 kB 45.31 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.production.js +0.69% 260.04 kB 261.84 kB +0.47% 46.03 kB 46.24 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.67% 209.41 kB 210.82 kB +0.43% 38.23 kB 38.40 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.67% 209.44 kB 210.84 kB +0.43% 38.26 kB 38.42 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js +0.66% 213.93 kB 215.33 kB +0.44% 40.00 kB 40.17 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js +0.66% 213.95 kB 215.36 kB +0.44% 40.02 kB 40.20 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.production.js +0.65% 232.87 kB 234.39 kB +0.42% 41.86 kB 42.04 kB
facebook-www/ReactDOMServer-prod.modern.js +0.65% 217.07 kB 218.47 kB +0.44% 39.30 kB 39.47 kB
facebook-www/ReactDOMServer-prod.classic.js +0.64% 219.75 kB 221.16 kB +0.43% 39.63 kB 39.80 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js +0.62% 557.55 kB 561.02 kB +0.61% 99.01 kB 99.62 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.62% 226.97 kB 228.38 kB +0.43% 40.50 kB 40.67 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.production.js +0.61% 572.28 kB 575.75 kB +0.56% 102.61 kB 103.18 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.production.js +0.61% 232.05 kB 233.46 kB +0.42% 42.41 kB 42.58 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js +0.59% 613.07 kB 616.71 kB +0.56% 107.72 kB 108.32 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js +0.56% 316.53 kB 318.31 kB +0.36% 61.61 kB 61.83 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js +0.56% 316.60 kB 318.38 kB +0.36% 61.63 kB 61.86 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js +0.56% 367.24 kB 369.29 kB +0.35% 66.19 kB 66.42 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js +0.56% 367.31 kB 369.36 kB +0.35% 66.24 kB 66.47 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js +0.56% 370.69 kB 372.76 kB +0.33% 66.83 kB 67.05 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js +0.56% 370.77 kB 372.84 kB +0.33% 66.88 kB 67.10 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js +0.56% 371.46 kB 373.53 kB +0.33% 66.99 kB 67.21 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js +0.56% 371.54 kB 373.61 kB +0.33% 67.05 kB 67.27 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js +0.55% 514.40 kB 517.21 kB +0.56% 91.73 kB 92.24 kB
oss-stable/react-dom/cjs/react-dom-client.production.js +0.55% 514.52 kB 517.33 kB +0.55% 91.76 kB 92.27 kB
facebook-www/ReactDOMServerStreaming-dev.modern.js +0.55% 363.39 kB 365.37 kB +0.35% 65.68 kB 65.91 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.development.js +0.52% 343.60 kB 345.37 kB +0.34% 65.21 kB 65.44 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js +0.52% 355.25 kB 357.08 kB +0.37% 64.66 kB 64.89 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js +0.52% 355.25 kB 357.09 kB +0.37% 64.66 kB 64.89 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js +0.52% 355.27 kB 357.11 kB +0.36% 64.68 kB 64.92 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js +0.52% 355.28 kB 357.11 kB +0.36% 64.68 kB 64.91 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js +0.52% 544.80 kB 547.61 kB +0.56% 96.45 kB 96.99 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js +0.52% 544.92 kB 547.74 kB +0.56% 96.48 kB 97.02 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-prod.js +0.52% 540.07 kB 542.86 kB +0.57% 95.81 kB 96.36 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-prod.js +0.51% 545.58 kB 548.37 kB +0.56% 96.89 kB 97.44 kB
oss-experimental/react-dom/cjs/react-dom-server.node.development.js +0.51% 405.52 kB 407.57 kB +0.32% 70.56 kB 70.78 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.development.js +0.50% 409.57 kB 411.63 kB +0.31% 71.11 kB 71.33 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.development.js +0.50% 410.35 kB 412.42 kB +0.35% 71.26 kB 71.51 kB
facebook-www/ReactDOMServer-dev.modern.js +0.49% 371.83 kB 373.67 kB +0.37% 66.94 kB 67.18 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-profiling.js +0.49% 564.91 kB 567.69 kB +0.57% 99.54 kB 100.10 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-profiling.js +0.49% 570.85 kB 573.64 kB +0.56% 100.70 kB 101.26 kB
facebook-www/ReactDOMServer-dev.classic.js +0.48% 378.66 kB 380.49 kB +0.37% 67.95 kB 68.20 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.development.js +0.48% 384.72 kB 386.56 kB +0.37% 68.24 kB 68.49 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.development.js +0.48% 384.72 kB 386.56 kB +0.37% 68.24 kB 68.49 kB
facebook-www/ReactDOM-prod.modern.js +0.46% 586.92 kB 589.60 kB +0.48% 103.35 kB 103.85 kB
facebook-www/ReactDOM-prod.classic.js +0.45% 596.50 kB 599.18 kB +0.49% 104.89 kB 105.40 kB
facebook-www/ReactDOMTesting-prod.modern.js +0.45% 601.64 kB 604.32 kB +0.47% 107.04 kB 107.55 kB
oss-experimental/react-reconciler/cjs/react-reconciler.production.js +0.44% 427.32 kB 429.20 kB +0.22% 68.83 kB 68.99 kB
facebook-www/ReactDOMTesting-prod.classic.js +0.44% 611.21 kB 613.89 kB +0.48% 108.58 kB 109.10 kB
facebook-www/ReactDOM-profiling.modern.js +0.44% 613.93 kB 616.61 kB +0.47% 107.20 kB 107.70 kB
oss-experimental/react-dom/cjs/react-dom-client.development.js +0.44% 1,027.89 kB 1,032.37 kB +0.42% 172.08 kB 172.80 kB
facebook-www/ReactDOM-profiling.classic.js +0.43% 623.56 kB 626.24 kB +0.48% 108.78 kB 109.30 kB
oss-experimental/react-dom/cjs/react-dom-profiling.development.js +0.43% 1,044.29 kB 1,048.77 kB +0.42% 174.92 kB 175.65 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.development.js +0.43% 1,044.81 kB 1,049.29 kB +0.41% 175.82 kB 176.55 kB
oss-experimental/react-markup/cjs/react-markup.production.js +0.42% 215.24 kB 216.14 kB +0.20% 39.74 kB 39.82 kB
oss-experimental/react-art/cjs/react-art.production.js +0.42% 321.20 kB 322.53 kB +0.19% 54.76 kB 54.87 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js +0.41% 480.22 kB 482.18 kB +0.25% 76.62 kB 76.81 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js +0.36% 942.82 kB 946.18 kB +0.38% 159.20 kB 159.81 kB
oss-stable/react-dom/cjs/react-dom-client.development.js +0.36% 942.95 kB 946.30 kB +0.38% 159.23 kB 159.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js +0.35% 959.26 kB 962.62 kB +0.38% 162.03 kB 162.65 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js +0.35% 959.39 kB 962.74 kB +0.38% 162.06 kB 162.67 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-dev.js +0.34% 992.91 kB 996.27 kB +0.37% 166.94 kB 167.55 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js +0.34% 714.88 kB 717.28 kB +0.22% 112.82 kB 113.07 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-dev.js +0.33% 1,009.26 kB 1,012.61 kB +0.38% 169.75 kB 170.40 kB
facebook-www/ReactDOM-dev.modern.js +0.31% 1,062.33 kB 1,065.67 kB +0.35% 176.99 kB 177.62 kB
facebook-www/ReactDOM-dev.classic.js +0.31% 1,071.47 kB 1,074.81 kB +0.37% 178.70 kB 179.36 kB
facebook-www/ReactDOMTesting-dev.modern.js +0.31% 1,079.23 kB 1,082.57 kB +0.35% 180.89 kB 181.53 kB
facebook-www/ReactDOMTesting-dev.classic.js +0.31% 1,088.38 kB 1,091.72 kB +0.36% 182.60 kB 183.25 kB
oss-experimental/react-markup/cjs/react-markup.development.js +0.31% 356.28 kB 357.37 kB +0.16% 63.97 kB 64.07 kB
oss-experimental/react-art/cjs/react-art.development.js +0.29% 612.97 kB 614.76 kB +0.15% 97.75 kB 97.90 kB
oss-experimental/react-markup/cjs/react-markup.react-server.production.js +0.29% 314.98 kB 315.88 kB +0.13% 58.89 kB 58.97 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js +0.29% 390.10 kB 391.22 kB +0.20% 63.31 kB 63.44 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js +0.29% 390.13 kB 391.24 kB +0.20% 63.33 kB 63.46 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js +0.27% 416.27 kB 417.40 kB +0.19% 66.95 kB 67.08 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js +0.27% 416.30 kB 417.43 kB +0.19% 66.98 kB 67.11 kB
facebook-www/ReactReconciler-prod.modern.js +0.25% 451.70 kB 452.83 kB +0.17% 72.58 kB 72.71 kB
facebook-www/ReactReconciler-prod.classic.js +0.24% 461.85 kB 462.98 kB +0.16% 74.14 kB 74.26 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js +0.21% 640.61 kB 641.98 kB +0.14% 102.13 kB 102.27 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js +0.21% 640.63 kB 642.00 kB +0.14% 102.15 kB 102.29 kB
oss-experimental/react-markup/cjs/react-markup.react-server.development.js +0.21% 529.48 kB 530.56 kB +0.11% 94.93 kB 95.04 kB
react-native/shims/ReactNativeTypes.js = 8.37 kB 8.32 kB = 2.19 kB 2.19 kB

Generated by 🚫 dangerJS against 0e39257

stacked on facebook#32163

This continues the work of making Suspense workable anywhere in a react-dom tree. See the prior PRs for how we handle server rendering and client rendering. In this change we update the hydration implementation to be able to locate expected nodes. In particular this means hydration understands now that the default hydration context is the document body when the container is above the body.

One case that is unique to hydration is clearing Suspense boundaries. When hydration fails or when the server instructs the client to recover an errored boundary it's possible that the html, head, and body tags in the initial document were written from a fallback or a different primary content on the server and need to be replaced by the client render. However these tags (and in the case of head, their content) won't be inside the comment nodes that identify the bounds of the Suspense boundary. And when client rendering you may not even render the same singletons that were server rendered. So when server rendering a boudnary which contributes to the preamble (the html, head, and body tag openings plus the head contents) we emit a special marker comment just before closing the boundary out. This marker encodes which parts of the preamble this boundary owned. If we need to clear the suspense boundary on the client we read this marker and use it to reset the appropriate singleton state.
rootPreamble.htmlChunks = preambleState.htmlChunks;
preambleState.contribution |= HTMLContribution;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess the contribution flag means that the chunks of this preamble were handled and passed along? I was expecting this to set the contribution flag on the rootPreamble instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah the reason we even need to track which boundary contributed to which part of the preamble is because if that boundary ever needs to be retried on the client we need to clean up the disconnected parts since they won't be 'inside' that boundary like normal tags would be. The root never gets cleaned up (except in that the entire container might be cleared but that will already be the entire Document if you are rendering these singletons anyway) so when a Suspense boundary is contributing a part of the preamble it tracks in it's own state that it's preamble parts were actually used so we can associate the necessary info with that boundary on the client to clean it up.

@gnoff gnoff merged commit 8bda715 into facebook:main Feb 4, 2025
191 checks passed
@gnoff gnoff deleted the suspense-anywhere-hydration branch February 4, 2025 20:30
github-actions bot pushed a commit that referenced this pull request Feb 4, 2025
follow up to #32163

This continues the work of making Suspense workable anywhere in a
react-dom tree. See the prior PRs for how we handle server rendering and
client rendering. In this change we update the hydration implementation
to be able to locate expected nodes. In particular this means hydration
understands now that the default hydration context is the document body
when the container is above the body.

One case that is unique to hydration is clearing Suspense boundaries.
When hydration fails or when the server instructs the client to recover
an errored boundary it's possible that the html, head, and body tags in
the initial document were written from a fallback or a different primary
content on the server and need to be replaced by the client render.
However these tags (and in the case of head, their content) won't be
inside the comment nodes that identify the bounds of the Suspense
boundary. And when client rendering you may not even render the same
singletons that were server rendered. So when server rendering a
boudnary which contributes to the preamble (the html, head, and body tag
openings plus the head contents) we emit a special marker comment just
before closing the boundary out. This marker encodes which parts of the
preamble this boundary owned. If we need to clear the suspense boundary
on the client we read this marker and use it to reset the appropriate
singleton state.

DiffTrain build for [8bda715](8bda715)
github-actions bot pushed a commit that referenced this pull request Feb 4, 2025
follow up to #32163

This continues the work of making Suspense workable anywhere in a
react-dom tree. See the prior PRs for how we handle server rendering and
client rendering. In this change we update the hydration implementation
to be able to locate expected nodes. In particular this means hydration
understands now that the default hydration context is the document body
when the container is above the body.

One case that is unique to hydration is clearing Suspense boundaries.
When hydration fails or when the server instructs the client to recover
an errored boundary it's possible that the html, head, and body tags in
the initial document were written from a fallback or a different primary
content on the server and need to be replaced by the client render.
However these tags (and in the case of head, their content) won't be
inside the comment nodes that identify the bounds of the Suspense
boundary. And when client rendering you may not even render the same
singletons that were server rendered. So when server rendering a
boudnary which contributes to the preamble (the html, head, and body tag
openings plus the head contents) we emit a special marker comment just
before closing the boundary out. This marker encodes which parts of the
preamble this boundary owned. If we need to clear the suspense boundary
on the client we read this marker and use it to reset the appropriate
singleton state.

DiffTrain build for [8bda715](8bda715)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants