Skip to content

Implement Partial Hydration for Activity #32863

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

Merged
merged 8 commits into from
Apr 23, 2025

Conversation

sebmarkbage
Copy link
Collaborator

@sebmarkbage sebmarkbage commented Apr 12, 2025

Stacked on #32862 and #32842.

This means that Activity boundaries now act as boundaries which can have their effects mounted independently. Just like Suspense boundaries, we hydrate the outer content first and then start hydrating the content in an Offscreen lane. Flowing props or interacting with the content increases the priority just like Suspense boundaries.

This skips emitting even the comments for <Activity mode="hidden"> so we don't hydrate those. Instead those are deferred to a later client render.

The implementation are just forked copies of the SuspenseComponent branches and then carefully going through each line and tweaking it.

The main interesting bit is that, unlike Suspense, Activity boundaries don't have fallbacks so all those branches where you might commit a suspended tree disappears. Instead, if something suspends while hydration, we can just leave the dehydrated content in place. However, if something does suspend during client rendering then it should bubble up to the parent. Therefore, we have to be careful to only pushSuspenseHandler when hydrating. That's really the main difference.

This just uses the existing basic Activity tests but I've started work on port all of the applicable Suspense tests in SelectiveHydration-test and PartialHydration-test to Activity versions.

@react-sizebot
Copy link

react-sizebot commented Apr 12, 2025

Comparing: 17f88c8...fb6f1e4

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 +1.73% 518.75 kB 527.72 kB +0.88% 92.26 kB 93.07 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 +1.40% 624.61 kB 633.34 kB +0.73% 110.44 kB 111.25 kB
facebook-www/ReactDOM-prod.classic.js +2.00% 658.01 kB 671.16 kB +1.49% 115.99 kB 117.72 kB
facebook-www/ReactDOM-prod.modern.js +2.03% 648.29 kB 661.44 kB +1.50% 114.44 kB 116.15 kB
oss-experimental/react-reconciler/cjs/react-reconciler-reflection.production.js +4.36% 6.26 kB 6.54 kB +1.57% 1.66 kB 1.69 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler-reflection.production.js +4.36% 6.26 kB 6.54 kB +1.57% 1.66 kB 1.69 kB
oss-stable/react-reconciler/cjs/react-reconciler-reflection.production.js +4.36% 6.26 kB 6.54 kB +1.57% 1.66 kB 1.69 kB
oss-experimental/react-reconciler/cjs/react-reconciler-reflection.development.js +4.35% 6.92 kB 7.22 kB +1.81% 1.66 kB 1.69 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler-reflection.development.js +4.35% 6.92 kB 7.22 kB +1.81% 1.66 kB 1.69 kB
oss-stable/react-reconciler/cjs/react-reconciler-reflection.development.js +4.35% 6.92 kB 7.22 kB +1.81% 1.66 kB 1.69 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js +2.30% 421.64 kB 431.33 kB +1.45% 67.79 kB 68.78 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js +2.30% 421.66 kB 431.36 kB +1.45% 67.82 kB 68.80 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js +2.26% 393.24 kB 402.11 kB +1.32% 63.94 kB 64.78 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js +2.26% 393.26 kB 402.14 kB +1.32% 63.96 kB 64.81 kB
facebook-www/ReactDOM-profiling.modern.js +2.09% 721.39 kB 736.48 kB +1.44% 124.69 kB 126.48 kB
facebook-www/ReactDOM-profiling.classic.js +2.07% 729.44 kB 744.53 kB +1.43% 125.99 kB 127.79 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js +2.00% 528.10 kB 538.69 kB +1.20% 83.67 kB 84.68 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react-reconciler/cjs/react-reconciler-reflection.production.js +4.36% 6.26 kB 6.54 kB +1.57% 1.66 kB 1.69 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler-reflection.production.js +4.36% 6.26 kB 6.54 kB +1.57% 1.66 kB 1.69 kB
oss-stable/react-reconciler/cjs/react-reconciler-reflection.production.js +4.36% 6.26 kB 6.54 kB +1.57% 1.66 kB 1.69 kB
oss-experimental/react-reconciler/cjs/react-reconciler-reflection.development.js +4.35% 6.92 kB 7.22 kB +1.81% 1.66 kB 1.69 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler-reflection.development.js +4.35% 6.92 kB 7.22 kB +1.81% 1.66 kB 1.69 kB
oss-stable/react-reconciler/cjs/react-reconciler-reflection.development.js +4.35% 6.92 kB 7.22 kB +1.81% 1.66 kB 1.69 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js +2.30% 421.64 kB 431.33 kB +1.45% 67.79 kB 68.78 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js +2.30% 421.66 kB 431.36 kB +1.45% 67.82 kB 68.80 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js +2.26% 393.24 kB 402.11 kB +1.32% 63.94 kB 64.78 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js +2.26% 393.26 kB 402.14 kB +1.32% 63.96 kB 64.81 kB
facebook-www/ReactDOM-profiling.modern.js +2.09% 721.39 kB 736.48 kB +1.44% 124.69 kB 126.48 kB
facebook-www/ReactDOM-profiling.classic.js +2.07% 729.44 kB 744.53 kB +1.43% 125.99 kB 127.79 kB
facebook-www/ReactDOM-prod.modern.js +2.03% 648.29 kB 661.44 kB +1.50% 114.44 kB 116.15 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js +2.00% 528.10 kB 538.69 kB +1.20% 83.67 kB 84.68 kB
facebook-www/ReactDOM-prod.classic.js +2.00% 658.01 kB 671.16 kB +1.49% 115.99 kB 117.72 kB
facebook-www/ReactDOMTesting-prod.modern.js +1.98% 662.69 kB 675.84 kB +1.49% 118.07 kB 119.83 kB
facebook-www/ReactDOMTesting-prod.classic.js +1.96% 672.41 kB 685.56 kB +1.43% 119.68 kB 121.39 kB
oss-experimental/react-reconciler/cjs/react-reconciler.production.js +1.85% 466.11 kB 474.73 kB +1.25% 74.87 kB 75.80 kB
facebook-www/ReactReconciler-prod.modern.js +1.82% 489.22 kB 498.15 kB +1.16% 78.61 kB 79.51 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.js +1.81% 313.42 kB 319.10 kB +0.91% 54.89 kB 55.39 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.js +1.81% 313.49 kB 319.17 kB +0.91% 54.91 kB 55.41 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.js +1.81% 313.67 kB 319.35 kB +0.91% 54.95 kB 55.45 kB
facebook-www/ReactReconciler-prod.classic.js +1.79% 499.53 kB 508.46 kB +1.16% 80.20 kB 81.12 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js +1.77% 652.26 kB 663.81 kB +1.05% 103.91 kB 104.99 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js +1.77% 652.28 kB 663.84 kB +1.05% 103.93 kB 105.02 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js +1.76% 551.53 kB 561.25 kB +0.90% 97.13 kB 98.00 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js +1.76% 551.65 kB 561.37 kB +0.90% 97.16 kB 98.03 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js +1.73% 518.63 kB 527.59 kB +0.88% 92.23 kB 93.04 kB
oss-stable/react-dom/cjs/react-dom-client.production.js +1.73% 518.75 kB 527.72 kB +0.88% 92.26 kB 93.07 kB
oss-stable-semver/react-art/cjs/react-art.production.js +1.72% 301.47 kB 306.67 kB +0.95% 51.27 kB 51.76 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-profiling.js +1.72% 570.30 kB 580.14 kB +0.96% 100.11 kB 101.07 kB
oss-stable/react-art/cjs/react-art.production.js +1.72% 301.55 kB 306.75 kB +0.95% 51.30 kB 51.78 kB
facebook-www/ReactReconciler-dev.modern.js +1.72% 817.20 kB 831.22 kB +0.98% 127.60 kB 128.86 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-profiling.js +1.71% 576.25 kB 586.08 kB +0.95% 101.27 kB 102.23 kB
facebook-www/ReactReconciler-dev.classic.js +1.70% 826.40 kB 840.43 kB +0.98% 129.32 kB 130.59 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-prod.js +1.66% 542.88 kB 551.91 kB +0.96% 96.17 kB 97.09 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-prod.js +1.65% 330.67 kB 336.14 kB +0.88% 57.74 kB 58.25 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-prod.js +1.65% 548.39 kB 557.42 kB +0.95% 97.25 kB 98.18 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-profiling.js +1.64% 355.07 kB 360.90 kB +0.88% 61.13 kB 61.67 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js +1.61% 776.71 kB 789.25 kB +0.91% 121.99 kB 123.10 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js +1.54% 688.76 kB 699.33 kB +0.76% 120.19 kB 121.10 kB
react-native/implementations/ReactFabric-prod.js +1.53% 358.85 kB 364.35 kB +0.78% 62.33 kB 62.82 kB
react-native/implementations/ReactFabric-profiling.js +1.52% 386.38 kB 392.26 kB +0.81% 66.25 kB 66.79 kB
oss-experimental/react-art/cjs/react-art.production.js +1.51% 342.46 kB 347.64 kB +0.83% 58.15 kB 58.63 kB
react-native/implementations/ReactNativeRenderer-prod.js +1.50% 366.35 kB 371.85 kB +0.81% 63.41 kB 63.93 kB
react-native/implementations/ReactNativeRenderer-profiling.js +1.49% 393.93 kB 399.80 kB +0.82% 67.46 kB 68.01 kB
react-native/implementations/ReactFabric-prod.fb.js +1.45% 376.33 kB 381.79 kB +0.80% 65.38 kB 65.90 kB
react-native/implementations/ReactNativeRenderer-prod.fb.js +1.44% 380.15 kB 385.62 kB +0.72% 66.06 kB 66.53 kB
react-native/implementations/ReactFabric-profiling.fb.js +1.44% 404.16 kB 409.97 kB +0.69% 69.53 kB 70.01 kB
react-native/implementations/ReactNativeRenderer-profiling.fb.js +1.43% 407.91 kB 413.74 kB +0.75% 70.11 kB 70.64 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js +1.40% 624.61 kB 633.34 kB +0.73% 110.44 kB 111.25 kB
facebook-www/ReactART-prod.modern.js +1.40% 373.14 kB 378.35 kB +0.78% 62.83 kB 63.32 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.production.js +1.37% 639.02 kB 647.75 kB +0.73% 114.02 kB 114.84 kB
facebook-www/ReactART-prod.classic.js +1.36% 383.13 kB 388.33 kB +0.81% 64.46 kB 64.99 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-dev.js +1.34% 994.39 kB 1,007.71 kB +1.07% 167.07 kB 168.86 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-dev.js +1.32% 1,010.72 kB 1,024.04 kB +1.07% 169.90 kB 171.72 kB
oss-experimental/react-dom/cjs/react-dom-client.development.js +1.30% 1,133.66 kB 1,148.38 kB +0.93% 189.20 kB 190.96 kB
oss-experimental/react-art/cjs/react-art.development.js +1.29% 652.79 kB 661.18 kB +0.75% 103.46 kB 104.24 kB
oss-experimental/react-dom/cjs/react-dom-profiling.development.js +1.28% 1,150.05 kB 1,164.77 kB +0.92% 192.04 kB 193.81 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.development.js +1.28% 1,150.20 kB 1,164.92 kB +0.92% 192.88 kB 194.66 kB
oss-stable-semver/react-art/cjs/react-art.development.js +1.25% 563.34 kB 570.40 kB +0.85% 90.30 kB 91.07 kB
oss-stable/react-art/cjs/react-art.development.js +1.25% 563.41 kB 570.48 kB +0.85% 90.33 kB 91.09 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js +1.25% 562.39 kB 569.44 kB +0.88% 91.03 kB 91.83 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js +1.25% 562.42 kB 569.47 kB +0.88% 91.04 kB 91.84 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js +1.25% 562.47 kB 569.52 kB +0.88% 91.05 kB 91.85 kB
facebook-www/ReactTestRenderer-dev.modern.js +1.21% 580.41 kB 587.46 kB +0.78% 94.17 kB 94.90 kB
facebook-www/ReactTestRenderer-dev.classic.js +1.21% 580.42 kB 587.47 kB +0.78% 94.16 kB 94.90 kB
facebook-www/ReactART-dev.modern.js +1.21% 706.04 kB 714.58 kB +0.80% 110.29 kB 111.18 kB
facebook-www/ReactART-dev.classic.js +1.19% 715.54 kB 724.08 kB +0.69% 112.14 kB 112.91 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-dev.js +1.16% 593.85 kB 600.73 kB +0.69% 95.77 kB 96.44 kB
facebook-www/ReactDOM-dev.modern.js +1.14% 1,187.73 kB 1,201.31 kB +0.68% 196.90 kB 198.25 kB
facebook-www/ReactDOM-dev.classic.js +1.13% 1,196.87 kB 1,210.45 kB +0.68% 198.67 kB 200.02 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js +1.13% 960.91 kB 971.81 kB +0.75% 161.75 kB 162.97 kB
oss-stable/react-dom/cjs/react-dom-client.development.js +1.13% 961.04 kB 971.93 kB +0.75% 161.78 kB 162.99 kB
facebook-www/ReactDOMTesting-dev.modern.js +1.13% 1,204.26 kB 1,217.84 kB +0.65% 200.72 kB 202.02 kB
facebook-www/ReactDOMTesting-dev.classic.js +1.12% 1,213.40 kB 1,226.98 kB +0.65% 202.42 kB 203.73 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js +1.11% 977.35 kB 988.25 kB +0.74% 164.59 kB 165.81 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js +1.11% 977.48 kB 988.37 kB +0.74% 164.62 kB 165.84 kB
react-native/implementations/ReactFabric-dev.js +1.06% 644.47 kB 651.33 kB +0.58% 105.02 kB 105.63 kB
react-native/implementations/ReactNativeRenderer-dev.js +1.05% 653.45 kB 660.31 kB +0.60% 106.40 kB 107.04 kB
react-native/implementations/ReactFabric-dev.fb.js +1.03% 663.50 kB 670.36 kB +0.59% 108.16 kB 108.80 kB
react-native/implementations/ReactNativeRenderer-dev.fb.js +1.03% 669.60 kB 676.47 kB +0.60% 109.12 kB 109.78 kB

Generated by 🚫 dangerJS against fb6f1e4

@sebmarkbage
Copy link
Collaborator Author

sebmarkbage commented Apr 17, 2025

I pushed the Partial Hydration and Selection Hydration tests which I converted from the Suspense form to equivalent-ish Activity forms. So this should have pretty solid coverage now.

… SSR

Then we handle this fully in the Activity wrapper. That's what determines
whether the offscreen content is SSR:ed or not.

This also optimizes by avoiding an emit commit pass that used to hydrate
the hidden Activity and then client renders the hidden content.
@sebmarkbage sebmarkbage merged commit 3ef31d1 into facebook:main Apr 23, 2025
239 checks passed
github-actions bot pushed a commit that referenced this pull request Apr 23, 2025
Stacked on #32862 and #32842.

This means that Activity boundaries now act as boundaries which can have
their effects mounted independently. Just like Suspense boundaries, we
hydrate the outer content first and then start hydrating the content in
an Offscreen lane. Flowing props or interacting with the content
increases the priority just like Suspense boundaries.

This skips emitting even the comments for `<Activity mode="hidden">` so
we don't hydrate those. Instead those are deferred to a later client
render.

The implementation are just forked copies of the SuspenseComponent
branches and then carefully going through each line and tweaking it.

The main interesting bit is that, unlike Suspense, Activity boundaries
don't have fallbacks so all those branches where you might commit a
suspended tree disappears. Instead, if something suspends while
hydration, we can just leave the dehydrated content in place. However,
if something does suspend during client rendering then it should bubble
up to the parent. Therefore, we have to be careful to only
pushSuspenseHandler when hydrating. That's really the main difference.

This just uses the existing basic Activity tests but I've started work
on port all of the applicable Suspense tests in SelectiveHydration-test
and PartialHydration-test to Activity versions.

DiffTrain build for [3ef31d1](3ef31d1)
github-actions bot pushed a commit that referenced this pull request Apr 23, 2025
Stacked on #32862 and #32842.

This means that Activity boundaries now act as boundaries which can have
their effects mounted independently. Just like Suspense boundaries, we
hydrate the outer content first and then start hydrating the content in
an Offscreen lane. Flowing props or interacting with the content
increases the priority just like Suspense boundaries.

This skips emitting even the comments for `<Activity mode="hidden">` so
we don't hydrate those. Instead those are deferred to a later client
render.

The implementation are just forked copies of the SuspenseComponent
branches and then carefully going through each line and tweaking it.

The main interesting bit is that, unlike Suspense, Activity boundaries
don't have fallbacks so all those branches where you might commit a
suspended tree disappears. Instead, if something suspends while
hydration, we can just leave the dehydrated content in place. However,
if something does suspend during client rendering then it should bubble
up to the parent. Therefore, we have to be careful to only
pushSuspenseHandler when hydrating. That's really the main difference.

This just uses the existing basic Activity tests but I've started work
on port all of the applicable Suspense tests in SelectiveHydration-test
and PartialHydration-test to Activity versions.

DiffTrain build for [3ef31d1](3ef31d1)
github-actions bot pushed a commit to code/lib-react that referenced this pull request Apr 23, 2025
Stacked on facebook#32862 and facebook#32842.

This means that Activity boundaries now act as boundaries which can have
their effects mounted independently. Just like Suspense boundaries, we
hydrate the outer content first and then start hydrating the content in
an Offscreen lane. Flowing props or interacting with the content
increases the priority just like Suspense boundaries.

This skips emitting even the comments for `<Activity mode="hidden">` so
we don't hydrate those. Instead those are deferred to a later client
render.

The implementation are just forked copies of the SuspenseComponent
branches and then carefully going through each line and tweaking it.

The main interesting bit is that, unlike Suspense, Activity boundaries
don't have fallbacks so all those branches where you might commit a
suspended tree disappears. Instead, if something suspends while
hydration, we can just leave the dehydrated content in place. However,
if something does suspend during client rendering then it should bubble
up to the parent. Therefore, we have to be careful to only
pushSuspenseHandler when hydrating. That's really the main difference.

This just uses the existing basic Activity tests but I've started work
on port all of the applicable Suspense tests in SelectiveHydration-test
and PartialHydration-test to Activity versions.

DiffTrain build for [3ef31d1](facebook@3ef31d1)
github-actions bot pushed a commit to code/lib-react that referenced this pull request Apr 23, 2025
Stacked on facebook#32862 and facebook#32842.

This means that Activity boundaries now act as boundaries which can have
their effects mounted independently. Just like Suspense boundaries, we
hydrate the outer content first and then start hydrating the content in
an Offscreen lane. Flowing props or interacting with the content
increases the priority just like Suspense boundaries.

This skips emitting even the comments for `<Activity mode="hidden">` so
we don't hydrate those. Instead those are deferred to a later client
render.

The implementation are just forked copies of the SuspenseComponent
branches and then carefully going through each line and tweaking it.

The main interesting bit is that, unlike Suspense, Activity boundaries
don't have fallbacks so all those branches where you might commit a
suspended tree disappears. Instead, if something suspends while
hydration, we can just leave the dehydrated content in place. However,
if something does suspend during client rendering then it should bubble
up to the parent. Therefore, we have to be careful to only
pushSuspenseHandler when hydrating. That's really the main difference.

This just uses the existing basic Activity tests but I've started work
on port all of the applicable Suspense tests in SelectiveHydration-test
and PartialHydration-test to Activity versions.

DiffTrain build for [3ef31d1](facebook@3ef31d1)
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