Skip to content

[Fizz] Support basic SuspenseList forwards/backwards revealOrder #33306

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
May 19, 2025

Conversation

sebmarkbage
Copy link
Collaborator

@sebmarkbage sebmarkbage commented May 19, 2025

Basically we track a SuspenseListRow on the task. These keep track of "pending tasks" that block the row. A row is blocked by:

  • First itself completing rendering.
  • A previous row completing.
  • Any tasks inside the row and before the Suspense boundary inside the row. This is mainly because we don't yet know if we'll discover more SuspenseBoundaries.
  • Previous row's SuspenseBoundaries completing.

If a boundary might get outlined, then we can't consider it completed until we have written it because it determined whether other future boundaries in the row can finish.

This is just handling basic semantics. Features not supported yet that need follow ups later:

Backwards row are rendered in reverse order but flushed in the right
order using the segments.
… them

Not covered by Suspense boundaries.

While these already block the parent, it is needed to know whether we might
discover more Suspense boundaries later.
However, if a boundary might get outlined, then we can't consider it complete
until it actually gets written.

If it's not eligible then we can flush it early to allow for it to be inline.
@sebmarkbage sebmarkbage requested review from gnoff and eps1lon May 19, 2025 17:29
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label May 19, 2025
These are now appearing in order (except the "first paint" thing)
@react-sizebot
Copy link

react-sizebot commented May 19, 2025

Comparing: 462d08f...0c7f03f

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 = 529.74 kB 529.74 kB = 93.49 kB 93.49 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 = 651.48 kB 651.48 kB = 114.76 kB 114.76 kB
facebook-www/ReactDOM-prod.classic.js = 675.72 kB 675.72 kB = 118.85 kB 118.85 kB
facebook-www/ReactDOM-prod.modern.js = 666.00 kB 666.00 kB = 117.23 kB 117.23 kB
oss-experimental/react-server/cjs/react-server.production.js +8.50% 134.26 kB 145.67 kB +8.25% 23.17 kB 25.08 kB
oss-stable-semver/react-server/cjs/react-server.production.js +7.15% 118.89 kB 127.39 kB +6.53% 21.07 kB 22.44 kB
oss-stable/react-server/cjs/react-server.production.js +7.15% 118.89 kB 127.39 kB +6.53% 21.07 kB 22.44 kB
oss-experimental/react-server/cjs/react-server.development.js +6.03% 193.64 kB 205.33 kB +5.71% 33.65 kB 35.57 kB
oss-stable-semver/react-server/cjs/react-server.development.js +5.71% 175.38 kB 185.40 kB +5.13% 31.37 kB 32.98 kB
oss-stable/react-server/cjs/react-server.development.js +5.71% 175.38 kB 185.40 kB +5.13% 31.37 kB 32.98 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.production.js +5.18% 240.62 kB 253.09 kB +4.46% 42.70 kB 44.61 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.production.js +5.07% 245.70 kB 258.17 kB +4.25% 44.60 kB 46.49 kB
facebook-www/ReactDOMServerStreaming-prod.modern.js +4.98% 236.32 kB 248.09 kB +4.35% 43.23 kB 45.11 kB
oss-experimental/react-markup/cjs/react-markup.production.js +4.26% 229.05 kB 238.81 kB +3.85% 41.85 kB 43.46 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.production.js +4.09% 252.27 kB 262.59 kB +3.63% 45.00 kB 46.63 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js +3.70% 222.05 kB 230.27 kB +3.28% 40.77 kB 42.11 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js +3.70% 222.13 kB 230.35 kB +3.27% 40.80 kB 42.13 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.production.js +3.63% 273.46 kB 283.39 kB +3.30% 47.34 kB 48.90 kB
oss-experimental/react-dom/cjs/react-dom-server.node.production.js +3.61% 275.38 kB 285.31 kB +3.27% 48.52 kB 50.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js +3.58% 216.97 kB 224.74 kB +3.17% 39.44 kB 40.69 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js +3.58% 217.00 kB 224.76 kB +3.17% 39.46 kB 40.71 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.production.js +3.55% 279.41 kB 289.34 kB +3.21% 49.50 kB 51.09 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js +3.51% 221.49 kB 229.25 kB +3.00% 41.22 kB 42.46 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js +3.50% 221.51 kB 229.28 kB +2.99% 41.25 kB 42.48 kB
facebook-www/ReactDOMServer-prod.modern.js +3.38% 230.02 kB 237.79 kB +3.19% 41.18 kB 42.49 kB
facebook-www/ReactDOMServer-prod.classic.js +3.37% 232.93 kB 240.78 kB +3.23% 41.51 kB 42.85 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js +3.30% 236.57 kB 244.38 kB +3.04% 42.51 kB 43.80 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js +3.30% 236.64 kB 244.46 kB +3.04% 42.54 kB 43.83 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js +3.28% 238.36 kB 246.18 kB +2.94% 43.50 kB 44.78 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js +3.28% 238.44 kB 246.26 kB +2.94% 43.53 kB 44.80 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js +3.23% 241.77 kB 249.59 kB +2.85% 44.48 kB 45.74 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js +3.23% 241.84 kB 249.66 kB +2.84% 44.50 kB 45.77 kB
oss-experimental/react-markup/cjs/react-markup.development.js +3.06% 369.16 kB 380.47 kB +2.85% 66.17 kB 68.05 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.development.js +2.95% 360.79 kB 371.42 kB +2.82% 68.59 kB 70.53 kB
oss-experimental/react-markup/cjs/react-markup.react-server.production.js +2.94% 330.16 kB 339.86 kB +2.47% 61.24 kB 62.75 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.development.js +2.85% 400.35 kB 411.76 kB +2.53% 70.80 kB 72.60 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.development.js +2.85% 400.36 kB 411.77 kB +2.53% 70.80 kB 72.59 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js +2.81% 328.22 kB 337.45 kB +2.58% 63.89 kB 65.54 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js +2.81% 328.29 kB 337.53 kB +2.58% 63.92 kB 65.57 kB
oss-experimental/react-dom/cjs/react-dom-server.node.development.js +2.71% 425.54 kB 437.07 kB +2.55% 74.07 kB 75.96 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.development.js +2.68% 429.58 kB 441.07 kB +2.48% 74.74 kB 76.59 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.development.js +2.67% 430.59 kB 442.08 kB +2.47% 74.96 kB 76.81 kB
facebook-www/ReactDOMServerStreaming-dev.modern.js +2.65% 381.18 kB 391.30 kB +2.43% 68.26 kB 69.93 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js +2.65% 367.82 kB 377.57 kB +2.45% 66.75 kB 68.39 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js +2.65% 367.83 kB 377.57 kB +2.45% 66.75 kB 68.39 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js +2.65% 367.85 kB 377.60 kB +2.45% 66.78 kB 68.41 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js +2.65% 367.85 kB 377.60 kB +2.45% 66.78 kB 68.41 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js +2.58% 381.83 kB 391.66 kB +2.39% 68.72 kB 70.36 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js +2.58% 381.90 kB 391.74 kB +2.39% 68.77 kB 70.41 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js +2.54% 385.30 kB 395.10 kB +2.34% 69.36 kB 70.98 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js +2.54% 385.38 kB 395.18 kB +2.34% 69.41 kB 71.03 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js +2.54% 386.08 kB 395.88 kB +2.34% 69.50 kB 71.13 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js +2.54% 386.16 kB 395.96 kB +2.33% 69.56 kB 71.18 kB
facebook-www/ReactDOMServer-dev.modern.js +2.52% 386.58 kB 396.33 kB +2.35% 69.09 kB 70.72 kB
facebook-www/ReactDOMServer-dev.classic.js +2.50% 390.04 kB 399.78 kB +2.37% 69.63 kB 71.28 kB
oss-experimental/react-markup/cjs/react-markup.react-server.development.js +2.02% 551.12 kB 562.26 kB +1.75% 98.51 kB 100.23 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-server/cjs/react-server.production.js +8.50% 134.26 kB 145.67 kB +8.25% 23.17 kB 25.08 kB
oss-stable-semver/react-server/cjs/react-server.production.js +7.15% 118.89 kB 127.39 kB +6.53% 21.07 kB 22.44 kB
oss-stable/react-server/cjs/react-server.production.js +7.15% 118.89 kB 127.39 kB +6.53% 21.07 kB 22.44 kB
oss-experimental/react-server/cjs/react-server.development.js +6.03% 193.64 kB 205.33 kB +5.71% 33.65 kB 35.57 kB
oss-stable-semver/react-server/cjs/react-server.development.js +5.71% 175.38 kB 185.40 kB +5.13% 31.37 kB 32.98 kB
oss-stable/react-server/cjs/react-server.development.js +5.71% 175.38 kB 185.40 kB +5.13% 31.37 kB 32.98 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.production.js +5.18% 240.62 kB 253.09 kB +4.46% 42.70 kB 44.61 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.production.js +5.07% 245.70 kB 258.17 kB +4.25% 44.60 kB 46.49 kB
facebook-www/ReactDOMServerStreaming-prod.modern.js +4.98% 236.32 kB 248.09 kB +4.35% 43.23 kB 45.11 kB
oss-experimental/react-markup/cjs/react-markup.production.js +4.26% 229.05 kB 238.81 kB +3.85% 41.85 kB 43.46 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.production.js +4.09% 252.27 kB 262.59 kB +3.63% 45.00 kB 46.63 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js +3.70% 222.05 kB 230.27 kB +3.28% 40.77 kB 42.11 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js +3.70% 222.13 kB 230.35 kB +3.27% 40.80 kB 42.13 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.production.js +3.63% 273.46 kB 283.39 kB +3.30% 47.34 kB 48.90 kB
oss-experimental/react-dom/cjs/react-dom-server.node.production.js +3.61% 275.38 kB 285.31 kB +3.27% 48.52 kB 50.11 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js +3.58% 216.97 kB 224.74 kB +3.17% 39.44 kB 40.69 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js +3.58% 217.00 kB 224.76 kB +3.17% 39.46 kB 40.71 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.production.js +3.55% 279.41 kB 289.34 kB +3.21% 49.50 kB 51.09 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js +3.51% 221.49 kB 229.25 kB +3.00% 41.22 kB 42.46 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js +3.50% 221.51 kB 229.28 kB +2.99% 41.25 kB 42.48 kB
facebook-www/ReactDOMServer-prod.modern.js +3.38% 230.02 kB 237.79 kB +3.19% 41.18 kB 42.49 kB
facebook-www/ReactDOMServer-prod.classic.js +3.37% 232.93 kB 240.78 kB +3.23% 41.51 kB 42.85 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js +3.30% 236.57 kB 244.38 kB +3.04% 42.51 kB 43.80 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js +3.30% 236.64 kB 244.46 kB +3.04% 42.54 kB 43.83 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js +3.28% 238.36 kB 246.18 kB +2.94% 43.50 kB 44.78 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js +3.28% 238.44 kB 246.26 kB +2.94% 43.53 kB 44.80 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js +3.23% 241.77 kB 249.59 kB +2.85% 44.48 kB 45.74 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js +3.23% 241.84 kB 249.66 kB +2.84% 44.50 kB 45.77 kB
oss-experimental/react-markup/cjs/react-markup.development.js +3.06% 369.16 kB 380.47 kB +2.85% 66.17 kB 68.05 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.development.js +2.95% 360.79 kB 371.42 kB +2.82% 68.59 kB 70.53 kB
oss-experimental/react-markup/cjs/react-markup.react-server.production.js +2.94% 330.16 kB 339.86 kB +2.47% 61.24 kB 62.75 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.development.js +2.85% 400.35 kB 411.76 kB +2.53% 70.80 kB 72.60 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.development.js +2.85% 400.36 kB 411.77 kB +2.53% 70.80 kB 72.59 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js +2.81% 328.22 kB 337.45 kB +2.58% 63.89 kB 65.54 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js +2.81% 328.29 kB 337.53 kB +2.58% 63.92 kB 65.57 kB
oss-experimental/react-dom/cjs/react-dom-server.node.development.js +2.71% 425.54 kB 437.07 kB +2.55% 74.07 kB 75.96 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.development.js +2.68% 429.58 kB 441.07 kB +2.48% 74.74 kB 76.59 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.development.js +2.67% 430.59 kB 442.08 kB +2.47% 74.96 kB 76.81 kB
facebook-www/ReactDOMServerStreaming-dev.modern.js +2.65% 381.18 kB 391.30 kB +2.43% 68.26 kB 69.93 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js +2.65% 367.82 kB 377.57 kB +2.45% 66.75 kB 68.39 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js +2.65% 367.83 kB 377.57 kB +2.45% 66.75 kB 68.39 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js +2.65% 367.85 kB 377.60 kB +2.45% 66.78 kB 68.41 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js +2.65% 367.85 kB 377.60 kB +2.45% 66.78 kB 68.41 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js +2.58% 381.83 kB 391.66 kB +2.39% 68.72 kB 70.36 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js +2.58% 381.90 kB 391.74 kB +2.39% 68.77 kB 70.41 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js +2.54% 385.30 kB 395.10 kB +2.34% 69.36 kB 70.98 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js +2.54% 385.38 kB 395.18 kB +2.34% 69.41 kB 71.03 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js +2.54% 386.08 kB 395.88 kB +2.34% 69.50 kB 71.13 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js +2.54% 386.16 kB 395.96 kB +2.33% 69.56 kB 71.18 kB
facebook-www/ReactDOMServer-dev.modern.js +2.52% 386.58 kB 396.33 kB +2.35% 69.09 kB 70.72 kB
facebook-www/ReactDOMServer-dev.classic.js +2.50% 390.04 kB 399.78 kB +2.37% 69.63 kB 71.28 kB
oss-experimental/react-markup/cjs/react-markup.react-server.development.js +2.02% 551.12 kB 562.26 kB +1.75% 98.51 kB 100.23 kB

Generated by 🚫 dangerJS against 0c7f03f

}

// @gate enableSuspenseList
it('shows content independently by default', async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a difference between a default SuspenseList and just wrapping it with a Fragment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think so but I always forget the special cases for updates that affect nested SuspenseList.

I forget that the default doesn't do anything a lot but basically you probably always should pick some options. We could make it a required props. It's mainly useful to disable conditionally. However, we don't really an option other than undefined. I've been thinking that maybe should add "independent" as an option for that case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This case is a bit interesting:

<SuspenseList>
<Suspense fallback={<Text text="Loading B" />}>
<B />
</Suspense>
<Suspense fallback={<Text text="Loading C" />}>
<C />
</Suspense>
</SuspenseList>

It's a case where because the outer one is in "together" mode, it blocks the inner ones from revealing independently. That's the same as if it was a Fragment so I think it's the same.

However, if the inner one had an explicit "independent" string, shouldn't that override the outer "together" mode?

componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component
thenableState: null | ThenableState,
legacyContext: LegacyContext, // the current legacy context that this task is executing in
debugTask: null | ConsoleTask, // DEV only
// DON'T ANY MORE FIELDS. We at 16 already which otherwise requires converting to a constructor.
// DON'T ANY MORE FIELDS. We at 16 in prod already which otherwise requires converting to a constructor.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Aren't we at 17 now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not counting legacyContext.

Typo

Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>
@sebmarkbage sebmarkbage merged commit 5dc1b21 into facebook:main May 19, 2025
14 of 15 checks passed
github-actions bot pushed a commit that referenced this pull request May 19, 2025
)

Basically we track a `SuspenseListRow` on the task. These keep track of
"pending tasks" that block the row. A row is blocked by:

- First itself completing rendering.
- A previous row completing.
- Any tasks inside the row and before the Suspense boundary inside the
row. This is mainly because we don't yet know if we'll discover more
SuspenseBoundaries.
- Previous row's SuspenseBoundaries completing.

If a boundary might get outlined, then we can't consider it completed
until we have written it because it determined whether other future
boundaries in the row can finish.

This is just handling basic semantics. Features not supported yet that
need follow ups later:

- CSS dependencies of previous rows should be added as dependencies of
future row's suspense boundary. Because otherwise if the client is
blocked on CSS then a previous row could be blocked but the server
doesn't know it.
- I need a second pass on nested SuspenseList semantics.
- `revealOrder="together"`
- `tail="hidden"`/`tail="collapsed"`. This needs some new runtime
semantics to the Fizz runtime and to allow the hydration to handle
missing rows in the HTML. This should also be future compatible with
AsyncIterable where we don't know how many rows upfront.
- Need to double check resuming semantics.

---------

Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>

DiffTrain build for [5dc1b21](5dc1b21)
sebmarkbage added a commit that referenced this pull request May 20, 2025
Follow up to #33306.

If we're nested inside a SuspenseList and we have a row, then we can
point our last row to block the parent row and unblock the parent when
the last child unblocks.
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