Skip to content

Conversation

sebmarkbage
Copy link
Collaborator

@sebmarkbage sebmarkbage commented May 19, 2025

Stacked on #33308.

For "together" mode, we can be a self-blocking row that adds all its boundaries to the blocked set, but there's no parent row that unblocks it.

A particular quirk of this mode is that it's not enough to just unblock them all on the server together. Because if one boundary downloads all its html and then issues a complete instruction it'll appear before the others while streaming in. What we actually want is to reveal them all in a single batch.

This implementation takes a short cut by unblocking the rows in flushPartialBoundary. That ensures that all the segments of every boundary has a chance to flush before we start emitting any of the complete boundary instructions. Once the last one unblocks, all the complete boundary instructions are queued. Ideally this would be a single <script> tag so that they can't be split up even if we get a chunk containing some of them.

A downside of this approach is that we always outline these boundaries. We could inline them if they all complete before the parent flushes. E.g. by checking if the row is blocked only by its own boundaries and if all the boundaries would fit without getting outlined, then we can inline them all at once. I went ahead and did this because it solves an issue with renderToString where it doesn't support the script runtime so it can only handle this if inlined.

@sebmarkbage sebmarkbage requested a review from eps1lon May 19, 2025 23:51
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label May 19, 2025
@react-sizebot
Copy link

react-sizebot commented May 19, 2025

Comparing: d38c7e1...1c50823

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.83 kB 529.83 kB = 93.52 kB 93.52 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.57 kB 651.57 kB = 114.78 kB 114.78 kB
facebook-www/ReactDOM-prod.classic.js = 675.81 kB 675.81 kB = 118.87 kB 118.87 kB
facebook-www/ReactDOM-prod.modern.js = 666.09 kB 666.09 kB = 117.26 kB 117.26 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-server/cjs/react-server.production.js +1.93% 127.53 kB 129.99 kB +1.77% 22.42 kB 22.82 kB
oss-stable/react-server/cjs/react-server.production.js +1.93% 127.53 kB 129.99 kB +1.77% 22.42 kB 22.82 kB
oss-experimental/react-server/cjs/react-server.production.js +1.66% 145.82 kB 148.24 kB +1.77% 25.06 kB 25.50 kB
facebook-www/ReactDOMServer-prod.classic.js +1.54% 240.96 kB 244.66 kB +1.54% 42.82 kB 43.48 kB
oss-stable-semver/react-server/cjs/react-server.development.js +1.39% 185.53 kB 188.11 kB +1.49% 32.96 kB 33.45 kB
oss-stable/react-server/cjs/react-server.development.js +1.39% 185.53 kB 188.11 kB +1.49% 32.96 kB 33.45 kB
oss-experimental/react-server/cjs/react-server.development.js +1.26% 205.45 kB 208.03 kB +1.39% 35.56 kB 36.05 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.js +0.94% 230.45 kB 232.63 kB +0.91% 42.09 kB 42.47 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.js +0.94% 230.53 kB 232.70 kB +0.91% 42.11 kB 42.50 kB
oss-experimental/react-markup/cjs/react-markup.production.js +0.93% 238.99 kB 241.21 kB +0.92% 43.44 kB 43.84 kB
facebook-www/ReactDOMServerStreaming-prod.modern.js +0.89% 248.27 kB 250.49 kB +0.89% 45.09 kB 45.49 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.js +0.89% 244.56 kB 246.74 kB +0.87% 43.78 kB 44.16 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.js +0.89% 244.64 kB 246.81 kB +0.87% 43.80 kB 44.18 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.js +0.88% 246.36 kB 248.53 kB +0.87% 44.76 kB 45.15 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.js +0.88% 246.44 kB 248.61 kB +0.88% 44.78 kB 45.18 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.js +0.87% 249.77 kB 251.94 kB +0.85% 45.72 kB 46.11 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.js +0.87% 249.84 kB 252.01 kB +0.85% 45.75 kB 46.14 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.production.js +0.84% 262.77 kB 264.99 kB +0.88% 46.62 kB 47.03 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.production.js +0.78% 283.57 kB 285.79 kB +0.94% 48.88 kB 49.34 kB
oss-experimental/react-dom/cjs/react-dom-server.node.production.js +0.78% 285.49 kB 287.71 kB +0.91% 50.09 kB 50.54 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.production.js +0.77% 289.52 kB 291.74 kB +0.88% 51.07 kB 51.52 kB
oss-experimental/react-markup/cjs/react-markup.development.js +0.68% 380.60 kB 383.18 kB +0.78% 68.03 kB 68.56 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js +0.68% 337.62 kB 339.90 kB +0.69% 65.55 kB 66.00 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js +0.68% 337.70 kB 339.98 kB +0.68% 65.58 kB 66.03 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js +0.67% 391.79 kB 394.40 kB +0.63% 70.34 kB 70.78 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js +0.67% 391.87 kB 394.47 kB +0.63% 70.39 kB 70.83 kB
facebook-www/ReactDOMServerStreaming-dev.modern.js +0.66% 391.43 kB 394.01 kB +0.69% 69.91 kB 70.39 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js +0.66% 395.23 kB 397.83 kB +0.62% 70.97 kB 71.41 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js +0.66% 395.30 kB 397.91 kB +0.63% 71.01 kB 71.46 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js +0.66% 396.01 kB 398.61 kB +0.63% 71.11 kB 71.56 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js +0.66% 396.08 kB 398.69 kB +0.63% 71.16 kB 71.61 kB
oss-experimental/react-markup/cjs/react-markup.react-server.production.js +0.65% 340.03 kB 342.26 kB +0.66% 62.72 kB 63.14 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.development.js +0.61% 371.59 kB 373.88 kB +0.63% 70.52 kB 70.97 kB
oss-experimental/react-dom/cjs/react-dom-server.node.development.js +0.60% 437.20 kB 439.80 kB +0.64% 75.94 kB 76.42 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.development.js +0.59% 441.20 kB 443.81 kB +0.65% 76.57 kB 77.07 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.development.js +0.59% 442.21 kB 444.81 kB +0.64% 76.79 kB 77.28 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.56% 224.92 kB 226.17 kB +0.74% 40.66 kB 40.96 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.56% 224.94 kB 226.19 kB +0.74% 40.69 kB 40.99 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.js +0.54% 229.43 kB 230.68 kB +0.70% 42.43 kB 42.73 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.js +0.54% 229.46 kB 230.71 kB +0.69% 42.46 kB 42.76 kB
facebook-www/ReactDOMServer-prod.modern.js +0.52% 237.97 kB 239.22 kB +0.64% 42.47 kB 42.74 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.production.js +0.51% 253.27 kB 254.57 kB +0.70% 44.59 kB 44.90 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.production.js +0.50% 258.35 kB 259.65 kB +0.72% 46.47 kB 46.81 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js +0.47% 377.70 kB 379.47 kB +0.58% 68.37 kB 68.77 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js +0.47% 377.70 kB 379.48 kB +0.58% 68.37 kB 68.77 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js +0.47% 377.72 kB 379.50 kB +0.58% 68.40 kB 68.80 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js +0.47% 377.73 kB 379.50 kB +0.58% 68.40 kB 68.80 kB
oss-experimental/react-markup/cjs/react-markup.react-server.development.js +0.46% 562.39 kB 564.97 kB +0.51% 100.21 kB 100.71 kB
facebook-www/ReactDOMServer-dev.modern.js +0.45% 396.45 kB 398.23 kB +0.59% 70.70 kB 71.12 kB
facebook-www/ReactDOMServer-dev.classic.js +0.44% 399.91 kB 401.69 kB +0.60% 71.26 kB 71.68 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.development.js +0.43% 411.89 kB 413.67 kB +0.64% 72.59 kB 73.05 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.development.js +0.43% 411.89 kB 413.67 kB +0.64% 72.58 kB 73.05 kB

Generated by 🚫 dangerJS against 1c50823

@@ -194,6 +193,10 @@ export function getViewTransitionFormatContext(
return parentContext;
}

export function canHavePreamble(): boolean {
return false;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@gnoff Top level Suspense creates a scenario where you sometimes have to emit some kind of top level HTML tag to force Suspense boundaries to flush early. This shouldn't be necessary in renderToString which is a legacy API.

I think I can basically disable the feature this way.

if (allComplete) {
unblockSuspenseListRow(request, row);
}
}
Copy link
Collaborator Author

@sebmarkbage sebmarkbage May 20, 2025

Choose a reason for hiding this comment

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

This optimization wouldn't be safe if this had some hoistable state because it would not have been hoisted as part of hoistPreambleState since these boundaries weren't COMPLETE by the time we started flushing the shell.

Not sure how to solve that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Probably have to find a way to move this optimization earlier.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved to render phase. 1c50823

@sebmarkbage sebmarkbage merged commit 99aa685 into facebook:main May 20, 2025
239 of 240 checks passed
sebmarkbage added a commit that referenced this pull request May 20, 2025
…future rows (#33312)

Stacked on #33311.

When a row contains Suspense boundaries that themselves depend on CSS,
they will not resolve until the CSS has loaded on the client. We need
future rows in a list to be blocked until this happens. We could do
something in the runtime but a simpler approach is to just add those CSS
dependencies to all those boundaries as well.

To do this, we first hoist the HoistableState from a completed boundary
onto its parent row. Then when the row finishes do we hoist it onto the
next row and onto any boundaries within that row.
github-actions bot pushed a commit that referenced this pull request May 20, 2025
Stacked on #33308.

For "together" mode, we can be a self-blocking row that adds all its
boundaries to the blocked set, but there's no parent row that unblocks
it.

A particular quirk of this mode is that it's not enough to just unblock
them all on the server together. Because if one boundary downloads all
its html and then issues a complete instruction it'll appear before the
others while streaming in. What we actually want is to reveal them all
in a single batch.

This implementation takes a short cut by unblocking the rows in
`flushPartialBoundary`. That ensures that all the segments of every
boundary has a chance to flush before we start emitting any of the
complete boundary instructions. Once the last one unblocks, all the
complete boundary instructions are queued. Ideally this would be a
single `<script>` tag so that they can't be split up even if we get a
chunk containing some of them.

~A downside of this approach is that we always outline these boundaries.
We could inline them if they all complete before the parent flushes.
E.g. by checking if the row is blocked only by its own boundaries and if
all the boundaries would fit without getting outlined, then we can
inline them all at once.~ I went ahead and did this because it solves an
issue with `renderToString` where it doesn't support the script runtime
so it can only handle this if inlined.

DiffTrain build for [99aa685](99aa685)
github-actions bot pushed a commit that referenced this pull request May 20, 2025
…future rows (#33312)

Stacked on #33311.

When a row contains Suspense boundaries that themselves depend on CSS,
they will not resolve until the CSS has loaded on the client. We need
future rows in a list to be blocked until this happens. We could do
something in the runtime but a simpler approach is to just add those CSS
dependencies to all those boundaries as well.

To do this, we first hoist the HoistableState from a completed boundary
onto its parent row. Then when the row finishes do we hoist it onto the
next row and onto any boundaries within that row.

DiffTrain build for [50389e1](50389e1)
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.

5 participants