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

[Flight] Support Keyed Server Components #28123

Merged
merged 7 commits into from
Feb 5, 2024

Conversation

sebmarkbage
Copy link
Collaborator

Conceptually a Server Component in the tree is the same as a Client Component.

When we render a Server Component with a key, that key should be used as part of the reconciliation process to ensure the children's state are preserved when they move in a set. The key of a child should also be used to clear the state of the children when that key changes.

Conversely, if a Server Component doesn't have a key it should get an implicit key based on the slot number. It should not inherit the key of its children since the children don't know if that would collide with other keys in the set the Server Component is rendered in.

A Client Component also has an identity based on the function's implementation type. That mainly has to do with the state (or future state after a refactor) that Component might contain. To transfer state between two implementations it needs to be of the same state type. This is not a concern for a Server Components since they never have state so identity doesn't matter.

A Component returns a set of children. If it returns a single child, that's the same as returning a fragment of one child. So if you conditionally return a single child or a fragment, they should technically reconcile against each other.

The simple way to do this is to simply emit a Fragment for every Server Component. That would be correct in all cases. Unfortunately that is also unfortunate since it bloats the payload in the common cases. It also means that Fiber creates an extra indirection in the runtime.

Ideally we want to fold Server Component aways into zero cost on the client. At least where possible. The common cases are that you don't specify a key on a single return child, and that you do specify a key on a Server Component in a dynamic set.

The approach in this PR treats a Server Component that returns other Server Components or Lazy Nodes as a sequence that can be folded away. I.e. the parts that don't generate any output in the RSC payload. Instead, it keeps track of their keys on an internal "context". Which gets reset after each new reified JSON node gets rendered.

Then we transfer the accumulated keys from any parent Server Components onto the child element. In the simple case, the child just inherits the key of the parent.

If the Server Component itself is keyless but a child isn't, we have to add a wrapper fragment to ensure that this fragment gets the implicit key but we can still use the key to reset state. This is unusual though because typically if you keyed something it's because it was already in a fragment.

In the case a Server Component is keyed but forks its children using a fragment, we need to key that fragment so that the whole set can move around as one. In theory this could be flattened into a parent array but that gets tricky if something suspends, because then we can't send the siblings early.

The main downside of this approach is that switching between single child and fragment in a Server Component isn't always going to reconcile against each other. That's because if we saw a single child first, we'd have to add the fragment preemptively in case it forks later. This semantic of React isn't very well known anyway and it might be ok to break it here for pragmatic reasons. The tests document this discrepancy.

Another compromise of this approach is that when combining keys we don't escape them fully. We instead just use a simple , separated concat. This is probably good enough in practice. Additionally, since we don't encode the implicit 0 index slot key, you can move things around between parents which shouldn't really reconcile but does. This keeps the keys shorter and more human readable.

@react-sizebot
Copy link

react-sizebot commented Jan 27, 2024

Comparing: 37bdff6...893316d

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.min.js = 176.59 kB 176.59 kB = 55.01 kB 55.01 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js = 178.69 kB 178.69 kB = 55.64 kB 55.64 kB
facebook-www/ReactDOM-prod.classic.js = 573.52 kB 573.52 kB = 100.99 kB 100.99 kB
facebook-www/ReactDOM-prod.modern.js = 557.29 kB 557.29 kB = 98.07 kB 98.07 kB
oss-experimental/react-server/cjs/react-server-flight.production.js +10.99% 68.58 kB 76.12 kB +11.30% 16.14 kB 17.96 kB
facebook-www/ReactFlightDOMServer-dev.modern.js +9.74% 81.54 kB 89.48 kB +10.53% 17.37 kB 19.20 kB
oss-experimental/react-server/cjs/react-server-flight.development.js +9.51% 79.53 kB 87.10 kB +9.75% 18.73 kB 20.55 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +7.37% 102.25 kB 109.79 kB +7.63% 23.76 kB 25.58 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +7.20% 104.76 kB 112.30 kB +7.28% 24.78 kB 26.59 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +7.15% 105.42 kB 112.96 kB +7.21% 24.99 kB 26.79 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +7.02% 107.35 kB 114.88 kB +7.05% 25.53 kB 27.33 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +7.01% 107.48 kB 115.02 kB +7.06% 25.57 kB 27.37 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.js +7.00% 107.65 kB 115.19 kB +7.25% 25.01 kB 26.82 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.js +6.98% 57.86 kB 61.90 kB +6.53% 14.06 kB 14.97 kB
oss-stable/react-server/cjs/react-server-flight.production.js +6.98% 57.86 kB 61.90 kB +6.53% 14.06 kB 14.97 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.js +6.85% 110.06 kB 117.60 kB +7.08% 25.63 kB 27.44 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +6.83% 110.31 kB 117.85 kB +7.00% 25.84 kB 27.65 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +6.69% 112.72 kB 120.26 kB +6.81% 26.53 kB 28.34 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +6.60% 114.60 kB 122.17 kB +6.77% 26.85 kB 28.67 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +6.48% 116.68 kB 124.24 kB +6.60% 27.64 kB 29.46 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +6.41% 118.05 kB 125.62 kB +6.54% 28.10 kB 29.94 kB
oss-experimental/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.development.js +6.35% 123.21 kB 131.03 kB +6.44% 28.03 kB 29.83 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +6.34% 119.26 kB 126.83 kB +6.57% 28.36 kB 30.22 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.development.js +6.30% 120.02 kB 127.58 kB +6.51% 28.07 kB 29.89 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +6.30% 120.12 kB 127.68 kB +6.50% 28.67 kB 30.53 kB
facebook-www/ReactFlightDOMServer-prod.modern.js +6.29% 38.41 kB 40.83 kB +5.78% 8.66 kB 9.16 kB
oss-experimental/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.development.js +6.22% 125.93 kB 133.76 kB +6.40% 28.76 kB 30.60 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +6.18% 122.44 kB 130.01 kB +6.43% 28.70 kB 30.54 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +6.17% 122.66 kB 130.22 kB +6.45% 28.90 kB 30.77 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js +6.10% 66.62 kB 70.69 kB +6.17% 16.07 kB 17.07 kB
oss-stable/react-server/cjs/react-server-flight.development.js +6.10% 66.62 kB 70.69 kB +6.17% 16.07 kB 17.07 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +6.05% 125.08 kB 132.64 kB +6.32% 29.58 kB 31.45 kB
oss-experimental/react-server/cjs/react-server-flight.production.min.js +5.31% 19.59 kB 20.63 kB +4.90% 6.92 kB 7.26 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +4.43% 91.31 kB 95.35 kB +4.39% 21.54 kB 22.49 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +4.43% 91.31 kB 95.35 kB +4.39% 21.54 kB 22.49 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +4.33% 93.37 kB 97.41 kB +4.07% 22.41 kB 23.33 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +4.33% 93.37 kB 97.41 kB +4.07% 22.41 kB 23.33 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +4.30% 94.03 kB 98.07 kB +4.10% 22.61 kB 23.53 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +4.30% 94.03 kB 98.07 kB +4.10% 22.61 kB 23.53 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +4.21% 95.95 kB 99.99 kB +3.97% 23.13 kB 24.05 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +4.21% 95.95 kB 99.99 kB +3.97% 23.13 kB 24.05 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +4.21% 96.09 kB 100.13 kB +3.99% 23.17 kB 24.09 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +4.21% 96.09 kB 100.13 kB +3.99% 23.17 kB 24.09 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.js +4.18% 96.71 kB 100.75 kB +4.12% 22.78 kB 23.72 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.js +4.18% 96.71 kB 100.75 kB +4.12% 22.78 kB 23.72 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.js +4.08% 99.12 kB 103.16 kB +4.03% 23.39 kB 24.34 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.js +4.08% 99.12 kB 103.16 kB +4.03% 23.39 kB 24.34 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +4.07% 99.37 kB 103.41 kB +3.89% 23.63 kB 24.55 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +4.07% 99.37 kB 103.41 kB +3.89% 23.63 kB 24.55 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +4.03% 100.82 kB 104.89 kB +4.17% 23.86 kB 24.85 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +4.03% 100.82 kB 104.89 kB +4.17% 23.86 kB 24.85 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +3.97% 101.78 kB 105.82 kB +3.79% 24.30 kB 25.22 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +3.97% 101.78 kB 105.82 kB +3.79% 24.30 kB 25.22 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +3.94% 103.10 kB 107.16 kB +4.01% 24.69 kB 25.68 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +3.94% 103.10 kB 107.16 kB +4.01% 24.69 kB 25.68 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +3.92% 103.80 kB 107.87 kB +3.94% 24.90 kB 25.88 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +3.92% 103.80 kB 107.87 kB +3.94% 24.90 kB 25.88 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.min.js +3.89% 16.52 kB 17.16 kB +3.33% 6.01 kB 6.21 kB
oss-stable/react-server/cjs/react-server-flight.production.min.js +3.89% 16.52 kB 17.16 kB +3.33% 6.01 kB 6.21 kB
oss-stable-semver/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.development.js +3.87% 108.93 kB 113.14 kB +3.79% 25.04 kB 25.99 kB
oss-stable/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.development.js +3.87% 108.93 kB 113.14 kB +3.79% 25.04 kB 25.99 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +3.85% 105.68 kB 109.75 kB +3.88% 25.41 kB 26.40 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +3.85% 105.68 kB 109.75 kB +3.88% 25.41 kB 26.40 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +3.84% 105.87 kB 109.93 kB +3.84% 25.45 kB 26.43 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +3.84% 105.87 kB 109.93 kB +3.84% 25.45 kB 26.43 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.development.js +3.83% 106.24 kB 110.30 kB +3.97% 25.07 kB 26.06 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.development.js +3.83% 106.24 kB 110.30 kB +3.97% 25.07 kB 26.06 kB
oss-stable-semver/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.development.js +3.77% 111.65 kB 115.86 kB +3.68% 25.77 kB 26.72 kB
oss-stable/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.development.js +3.77% 111.65 kB 115.86 kB +3.68% 25.77 kB 26.72 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +3.74% 108.66 kB 112.73 kB +3.87% 25.69 kB 26.68 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +3.74% 108.66 kB 112.73 kB +3.87% 25.69 kB 26.68 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +3.73% 108.88 kB 112.94 kB +3.81% 25.91 kB 26.89 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +3.73% 108.88 kB 112.94 kB +3.81% 25.91 kB 26.89 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +3.65% 111.30 kB 115.36 kB +3.79% 26.55 kB 27.56 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +3.65% 111.30 kB 115.36 kB +3.79% 26.55 kB 27.56 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.min.js +3.64% 28.70 kB 29.75 kB +3.38% 9.74 kB 10.07 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.min.js +3.54% 29.49 kB 30.53 kB +3.34% 9.87 kB 10.20 kB
oss-experimental/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.production.min.js +3.53% 29.60 kB 30.64 kB +3.52% 9.91 kB 10.26 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.min.js +3.49% 29.98 kB 31.03 kB +3.24% 10.03 kB 10.36 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.min.js +3.46% 30.23 kB 31.28 kB +3.24% 10.06 kB 10.39 kB
oss-experimental/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.production.min.js +3.44% 30.33 kB 31.37 kB +3.39% 10.14 kB 10.48 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.min.js +3.42% 30.62 kB 31.66 kB +3.18% 10.18 kB 10.51 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.min.js +3.39% 30.74 kB 31.79 kB +3.14% 10.31 kB 10.64 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.min.js +3.34% 31.24 kB 32.28 kB +3.09% 10.46 kB 10.79 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.min.js +3.31% 31.54 kB 32.58 kB +3.07% 10.51 kB 10.83 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.min.js +3.26% 32.04 kB 33.09 kB +3.34% 10.67 kB 11.02 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.min.js +2.48% 25.60 kB 26.24 kB +2.10% 8.85 kB 9.03 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.min.js +2.48% 25.60 kB 26.24 kB +2.10% 8.85 kB 9.03 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.min.js +2.43% 26.33 kB 26.97 kB +1.95% 8.97 kB 9.14 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.min.js +2.43% 26.33 kB 26.97 kB +1.95% 8.97 kB 9.14 kB
oss-stable-semver/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.production.min.js +2.42% 26.44 kB 27.08 kB +2.09% 9.03 kB 9.22 kB
oss-stable/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.production.min.js +2.42% 26.44 kB 27.08 kB +2.09% 9.03 kB 9.22 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.min.js +2.38% 26.83 kB 27.47 kB +1.78% 9.13 kB 9.29 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.min.js +2.38% 26.83 kB 27.47 kB +1.78% 9.13 kB 9.29 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.min.js +2.36% 27.08 kB 27.72 kB +1.88% 9.16 kB 9.33 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.min.js +2.36% 27.08 kB 27.72 kB +1.88% 9.16 kB 9.33 kB
oss-stable-semver/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.production.min.js +2.35% 27.17 kB 27.81 kB +1.91% 9.25 kB 9.43 kB
oss-stable/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.production.min.js +2.35% 27.17 kB 27.81 kB +1.91% 9.25 kB 9.43 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.min.js +2.33% 27.46 kB 28.10 kB +1.79% 9.27 kB 9.44 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.min.js +2.33% 27.46 kB 28.10 kB +1.79% 9.27 kB 9.44 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.min.js +2.30% 27.64 kB 28.28 kB +2.02% 9.42 kB 9.61 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.min.js +2.30% 27.64 kB 28.28 kB +2.02% 9.42 kB 9.61 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.min.js +2.26% 28.14 kB 28.77 kB +1.97% 9.57 kB 9.76 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.min.js +2.26% 28.14 kB 28.77 kB +1.97% 9.57 kB 9.76 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.min.js +2.24% 28.44 kB 29.07 kB +1.93% 9.62 kB 9.80 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.min.js +2.24% 28.44 kB 29.07 kB +1.93% 9.62 kB 9.80 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.min.js +2.20% 28.94 kB 29.58 kB +1.96% 9.77 kB 9.97 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.min.js +2.20% 28.94 kB 29.58 kB +1.96% 9.77 kB 9.97 kB
test_utils/ReactAllWarnings.js Deleted 67.55 kB 0.00 kB Deleted 16.54 kB 0.00 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-flight.production.js +10.99% 68.58 kB 76.12 kB +11.30% 16.14 kB 17.96 kB
facebook-www/ReactFlightDOMServer-dev.modern.js +9.74% 81.54 kB 89.48 kB +10.53% 17.37 kB 19.20 kB
oss-experimental/react-server/cjs/react-server-flight.development.js +9.51% 79.53 kB 87.10 kB +9.75% 18.73 kB 20.55 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +7.37% 102.25 kB 109.79 kB +7.63% 23.76 kB 25.58 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +7.20% 104.76 kB 112.30 kB +7.28% 24.78 kB 26.59 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +7.15% 105.42 kB 112.96 kB +7.21% 24.99 kB 26.79 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +7.02% 107.35 kB 114.88 kB +7.05% 25.53 kB 27.33 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +7.01% 107.48 kB 115.02 kB +7.06% 25.57 kB 27.37 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.js +7.00% 107.65 kB 115.19 kB +7.25% 25.01 kB 26.82 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.js +6.98% 57.86 kB 61.90 kB +6.53% 14.06 kB 14.97 kB
oss-stable/react-server/cjs/react-server-flight.production.js +6.98% 57.86 kB 61.90 kB +6.53% 14.06 kB 14.97 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.js +6.85% 110.06 kB 117.60 kB +7.08% 25.63 kB 27.44 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +6.83% 110.31 kB 117.85 kB +7.00% 25.84 kB 27.65 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +6.69% 112.72 kB 120.26 kB +6.81% 26.53 kB 28.34 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +6.60% 114.60 kB 122.17 kB +6.77% 26.85 kB 28.67 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +6.48% 116.68 kB 124.24 kB +6.60% 27.64 kB 29.46 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +6.41% 118.05 kB 125.62 kB +6.54% 28.10 kB 29.94 kB
oss-experimental/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.development.js +6.35% 123.21 kB 131.03 kB +6.44% 28.03 kB 29.83 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +6.34% 119.26 kB 126.83 kB +6.57% 28.36 kB 30.22 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.development.js +6.30% 120.02 kB 127.58 kB +6.51% 28.07 kB 29.89 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +6.30% 120.12 kB 127.68 kB +6.50% 28.67 kB 30.53 kB
facebook-www/ReactFlightDOMServer-prod.modern.js +6.29% 38.41 kB 40.83 kB +5.78% 8.66 kB 9.16 kB
oss-experimental/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.development.js +6.22% 125.93 kB 133.76 kB +6.40% 28.76 kB 30.60 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +6.18% 122.44 kB 130.01 kB +6.43% 28.70 kB 30.54 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +6.17% 122.66 kB 130.22 kB +6.45% 28.90 kB 30.77 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js +6.10% 66.62 kB 70.69 kB +6.17% 16.07 kB 17.07 kB
oss-stable/react-server/cjs/react-server-flight.development.js +6.10% 66.62 kB 70.69 kB +6.17% 16.07 kB 17.07 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +6.05% 125.08 kB 132.64 kB +6.32% 29.58 kB 31.45 kB
oss-experimental/react-server/cjs/react-server-flight.production.min.js +5.31% 19.59 kB 20.63 kB +4.90% 6.92 kB 7.26 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +4.43% 91.31 kB 95.35 kB +4.39% 21.54 kB 22.49 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +4.43% 91.31 kB 95.35 kB +4.39% 21.54 kB 22.49 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +4.33% 93.37 kB 97.41 kB +4.07% 22.41 kB 23.33 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +4.33% 93.37 kB 97.41 kB +4.07% 22.41 kB 23.33 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +4.30% 94.03 kB 98.07 kB +4.10% 22.61 kB 23.53 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +4.30% 94.03 kB 98.07 kB +4.10% 22.61 kB 23.53 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +4.21% 95.95 kB 99.99 kB +3.97% 23.13 kB 24.05 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +4.21% 95.95 kB 99.99 kB +3.97% 23.13 kB 24.05 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +4.21% 96.09 kB 100.13 kB +3.99% 23.17 kB 24.09 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +4.21% 96.09 kB 100.13 kB +3.99% 23.17 kB 24.09 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.js +4.18% 96.71 kB 100.75 kB +4.12% 22.78 kB 23.72 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.js +4.18% 96.71 kB 100.75 kB +4.12% 22.78 kB 23.72 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.js +4.08% 99.12 kB 103.16 kB +4.03% 23.39 kB 24.34 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.js +4.08% 99.12 kB 103.16 kB +4.03% 23.39 kB 24.34 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +4.07% 99.37 kB 103.41 kB +3.89% 23.63 kB 24.55 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +4.07% 99.37 kB 103.41 kB +3.89% 23.63 kB 24.55 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +4.03% 100.82 kB 104.89 kB +4.17% 23.86 kB 24.85 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +4.03% 100.82 kB 104.89 kB +4.17% 23.86 kB 24.85 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +3.97% 101.78 kB 105.82 kB +3.79% 24.30 kB 25.22 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +3.97% 101.78 kB 105.82 kB +3.79% 24.30 kB 25.22 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +3.94% 103.10 kB 107.16 kB +4.01% 24.69 kB 25.68 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +3.94% 103.10 kB 107.16 kB +4.01% 24.69 kB 25.68 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +3.92% 103.80 kB 107.87 kB +3.94% 24.90 kB 25.88 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +3.92% 103.80 kB 107.87 kB +3.94% 24.90 kB 25.88 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.min.js +3.89% 16.52 kB 17.16 kB +3.33% 6.01 kB 6.21 kB
oss-stable/react-server/cjs/react-server-flight.production.min.js +3.89% 16.52 kB 17.16 kB +3.33% 6.01 kB 6.21 kB
oss-stable-semver/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.development.js +3.87% 108.93 kB 113.14 kB +3.79% 25.04 kB 25.99 kB
oss-stable/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.development.js +3.87% 108.93 kB 113.14 kB +3.79% 25.04 kB 25.99 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +3.85% 105.68 kB 109.75 kB +3.88% 25.41 kB 26.40 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +3.85% 105.68 kB 109.75 kB +3.88% 25.41 kB 26.40 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +3.84% 105.87 kB 109.93 kB +3.84% 25.45 kB 26.43 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +3.84% 105.87 kB 109.93 kB +3.84% 25.45 kB 26.43 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.development.js +3.83% 106.24 kB 110.30 kB +3.97% 25.07 kB 26.06 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.development.js +3.83% 106.24 kB 110.30 kB +3.97% 25.07 kB 26.06 kB
oss-stable-semver/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.development.js +3.77% 111.65 kB 115.86 kB +3.68% 25.77 kB 26.72 kB
oss-stable/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.development.js +3.77% 111.65 kB 115.86 kB +3.68% 25.77 kB 26.72 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +3.74% 108.66 kB 112.73 kB +3.87% 25.69 kB 26.68 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +3.74% 108.66 kB 112.73 kB +3.87% 25.69 kB 26.68 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +3.73% 108.88 kB 112.94 kB +3.81% 25.91 kB 26.89 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +3.73% 108.88 kB 112.94 kB +3.81% 25.91 kB 26.89 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +3.65% 111.30 kB 115.36 kB +3.79% 26.55 kB 27.56 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +3.65% 111.30 kB 115.36 kB +3.79% 26.55 kB 27.56 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.min.js +3.64% 28.70 kB 29.75 kB +3.38% 9.74 kB 10.07 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.min.js +3.54% 29.49 kB 30.53 kB +3.34% 9.87 kB 10.20 kB
oss-experimental/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.production.min.js +3.53% 29.60 kB 30.64 kB +3.52% 9.91 kB 10.26 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.min.js +3.49% 29.98 kB 31.03 kB +3.24% 10.03 kB 10.36 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.min.js +3.46% 30.23 kB 31.28 kB +3.24% 10.06 kB 10.39 kB
oss-experimental/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.production.min.js +3.44% 30.33 kB 31.37 kB +3.39% 10.14 kB 10.48 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.min.js +3.42% 30.62 kB 31.66 kB +3.18% 10.18 kB 10.51 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.min.js +3.39% 30.74 kB 31.79 kB +3.14% 10.31 kB 10.64 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.min.js +3.34% 31.24 kB 32.28 kB +3.09% 10.46 kB 10.79 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.min.js +3.31% 31.54 kB 32.58 kB +3.07% 10.51 kB 10.83 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.min.js +3.26% 32.04 kB 33.09 kB +3.34% 10.67 kB 11.02 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.min.js +2.48% 25.60 kB 26.24 kB +2.10% 8.85 kB 9.03 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.min.js +2.48% 25.60 kB 26.24 kB +2.10% 8.85 kB 9.03 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.min.js +2.43% 26.33 kB 26.97 kB +1.95% 8.97 kB 9.14 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.min.js +2.43% 26.33 kB 26.97 kB +1.95% 8.97 kB 9.14 kB
oss-stable-semver/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.production.min.js +2.42% 26.44 kB 27.08 kB +2.09% 9.03 kB 9.22 kB
oss-stable/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.production.min.js +2.42% 26.44 kB 27.08 kB +2.09% 9.03 kB 9.22 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.min.js +2.38% 26.83 kB 27.47 kB +1.78% 9.13 kB 9.29 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.min.js +2.38% 26.83 kB 27.47 kB +1.78% 9.13 kB 9.29 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.min.js +2.36% 27.08 kB 27.72 kB +1.88% 9.16 kB 9.33 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.min.js +2.36% 27.08 kB 27.72 kB +1.88% 9.16 kB 9.33 kB
oss-stable-semver/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.production.min.js +2.35% 27.17 kB 27.81 kB +1.91% 9.25 kB 9.43 kB
oss-stable/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.production.min.js +2.35% 27.17 kB 27.81 kB +1.91% 9.25 kB 9.43 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.min.js +2.33% 27.46 kB 28.10 kB +1.79% 9.27 kB 9.44 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.min.js +2.33% 27.46 kB 28.10 kB +1.79% 9.27 kB 9.44 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.min.js +2.30% 27.64 kB 28.28 kB +2.02% 9.42 kB 9.61 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.min.js +2.30% 27.64 kB 28.28 kB +2.02% 9.42 kB 9.61 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.min.js +2.26% 28.14 kB 28.77 kB +1.97% 9.57 kB 9.76 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.min.js +2.26% 28.14 kB 28.77 kB +1.97% 9.57 kB 9.76 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.min.js +2.24% 28.44 kB 29.07 kB +1.93% 9.62 kB 9.80 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.min.js +2.24% 28.44 kB 29.07 kB +1.93% 9.62 kB 9.80 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.min.js +2.20% 28.94 kB 29.58 kB +1.96% 9.77 kB 9.97 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.min.js +2.20% 28.94 kB 29.58 kB +1.96% 9.77 kB 9.97 kB
test_utils/ReactAllWarnings.js Deleted 67.55 kB 0.00 kB Deleted 16.54 kB 0.00 kB

Generated by 🚫 dangerJS against 893316d

If an element gets wrapped in a different server component then that has
a different keyPath context and the element might end up with a different
key.

So we don't use the deduping mechanism if we're already inside a
Server Component parent with a key or otherwise. Only the simple case
gets deduped.

The props of a client element are still deduped though if they're the
same instance.
@sebmarkbage sebmarkbage merged commit 95ec128 into facebook:main Feb 5, 2024
36 checks passed
github-actions bot pushed a commit that referenced this pull request Feb 5, 2024
Conceptually a Server Component in the tree is the same as a Client
Component.

When we render a Server Component with a key, that key should be used as
part of the reconciliation process to ensure the children's state are
preserved when they move in a set. The key of a child should also be
used to clear the state of the children when that key changes.

Conversely, if a Server Component doesn't have a key it should get an
implicit key based on the slot number. It should not inherit the key of
its children since the children don't know if that would collide with
other keys in the set the Server Component is rendered in.

A Client Component also has an identity based on the function's
implementation type. That mainly has to do with the state (or future
state after a refactor) that Component might contain. To transfer state
between two implementations it needs to be of the same state type. This
is not a concern for a Server Components since they never have state so
identity doesn't matter.

A Component returns a set of children. If it returns a single child,
that's the same as returning a fragment of one child. So if you
conditionally return a single child or a fragment, they should
technically reconcile against each other.

The simple way to do this is to simply emit a Fragment for every Server
Component. That would be correct in all cases. Unfortunately that is
also unfortunate since it bloats the payload in the common cases. It
also means that Fiber creates an extra indirection in the runtime.

Ideally we want to fold Server Component aways into zero cost on the
client. At least where possible. The common cases are that you don't
specify a key on a single return child, and that you do specify a key on
a Server Component in a dynamic set.

The approach in this PR treats a Server Component that returns other
Server Components or Lazy Nodes as a sequence that can be folded away.
I.e. the parts that don't generate any output in the RSC payload.
Instead, it keeps track of their keys on an internal "context". Which
gets reset after each new reified JSON node gets rendered.

Then we transfer the accumulated keys from any parent Server Components
onto the child element. In the simple case, the child just inherits the
key of the parent.

If the Server Component itself is keyless but a child isn't, we have to
add a wrapper fragment to ensure that this fragment gets the implicit
key but we can still use the key to reset state. This is unusual though
because typically if you keyed something it's because it was already in
a fragment.

In the case a Server Component is keyed but forks its children using a
fragment, we need to key that fragment so that the whole set can move
around as one. In theory this could be flattened into a parent array but
that gets tricky if something suspends, because then we can't send the
siblings early.

The main downside of this approach is that switching between single
child and fragment in a Server Component isn't always going to reconcile
against each other. That's because if we saw a single child first, we'd
have to add the fragment preemptively in case it forks later. This
semantic of React isn't very well known anyway and it might be ok to
break it here for pragmatic reasons. The tests document this
discrepancy.

Another compromise of this approach is that when combining keys we don't
escape them fully. We instead just use a simple `,` separated concat.
This is probably good enough in practice. Additionally, since we don't
encode the implicit 0 index slot key, you can move things around between
parents which shouldn't really reconcile but does. This keeps the keys
shorter and more human readable.

DiffTrain build for [95ec128](95ec128)
gnoff added a commit to gnoff/next.js that referenced this pull request Feb 6, 2024
- facebook/react#28250
- facebook/react#28225
- facebook/react#28123
- facebook/react#28240
- facebook/react#28239
- facebook/react#28245
- facebook/react#28244
- facebook/react#28238
- facebook/react#28235
- facebook/react#28221
- facebook/react#28215
- facebook/react#28214
- facebook/react#28213
- facebook/react#28212
- facebook/react#28211
- facebook/react#28247
- facebook/react#28210
- facebook/react#28186
- facebook/react#28232
- facebook/react#28169
- facebook/react#28177
- facebook/react#28170
- facebook/react#28168
- facebook/react#28122
- facebook/react#27982
- facebook/react#28217
- facebook/react#28223
- facebook/react#28208
- facebook/react#28209
- facebook/react#28200
- facebook/react#28199
- facebook/react#28198
- facebook/react#28197
- facebook/react#28196
- facebook/react#28194
- facebook/react#28192
- facebook/react#28191
- facebook/react#28182
- facebook/react#28181
- facebook/react#28180
- facebook/react#28178
- facebook/react#28201
- facebook/react#28176
- facebook/react#28162
- facebook/react#28131
- facebook/react#28190
- facebook/react#28172
- facebook/react#28171
- facebook/react#28173
- facebook/react#28174
- facebook/react#28175
- facebook/react#28136
- facebook/react#28135
- facebook/react#28134
- facebook/react#28133
- facebook/react#28132
- facebook/react#28130
- facebook/react#28202
- facebook/react#28102
- facebook/react#28161
- facebook/react#28193
- facebook/react#28195
- facebook/react#28189
- facebook/react#28160
- facebook/react#28096
- facebook/react#28183
- facebook/react#28125
- facebook/react#28157
- facebook/react#28115
- facebook/react#28124
- facebook/react#28163
- facebook/react#28164
- facebook/react#28150
- facebook/react#28159
- facebook/react#28069
- facebook/react#28110
- facebook/react#28148
- facebook/react#28116
- facebook/react#28099
- facebook/react#28100
- facebook/react#28147
- facebook/react#28128
- facebook/react#28126
- facebook/react#28139
- facebook/react#28140
- facebook/react#28141
- facebook/react#28142
- facebook/react#28113
- facebook/react#28129
- facebook/react#28114
- facebook/react#28053
- facebook/react#28091
- facebook/react#28087
- facebook/react#28112
- facebook/react#28086
- facebook/react#28101
- facebook/react#28106
- facebook/react#28117
- facebook/react#28118
- facebook/react#28105
- facebook/react#27883
- facebook/react#28111
- facebook/react#28095
- facebook/react#28108
- facebook/react#28090
- facebook/react#28089
- facebook/react#28076
- facebook/react#28074
- facebook/react#28103
- facebook/react#28098
- facebook/react#28097
- facebook/react#28068
- facebook/react#28093
- facebook/react#28094
- facebook/react#28073
- facebook/react#28084
- facebook/react#28063
- facebook/react#28085
- facebook/react#28083
- facebook/react#28065
- facebook/react#28061
- facebook/react#28077
- facebook/react#28075
- facebook/react#28078
- facebook/react#28050
- facebook/react#28011
- facebook/react#28055
- facebook/react#28066
- facebook/react#28067
- facebook/react#28010
- facebook/react#27993
- facebook/react#28052
- facebook/react#28060
- facebook/react#28059
- facebook/react#28034
- facebook/react#28033
- facebook/react#28004
- facebook/react#28051
- facebook/react#28012
- facebook/react#28001
- facebook/react#28002
- facebook/react#27995
- facebook/react#28006
- facebook/react#28005
- facebook/react#28007
- facebook/react#28008
- facebook/react#28009
- facebook/react#28000
- facebook/react#28003
- facebook/react#27997
- facebook/react#27240
- facebook/react#27977
- facebook/react#27940
- facebook/react#27939
- facebook/react#28090
- facebook/react#28089
- facebook/react#28076
- facebook/react#28074
- facebook/react#28103
- facebook/react#28098
- facebook/react#28097
- facebook/react#28068
- facebook/react#28093
- facebook/react#28094
- facebook/react#28073
- facebook/react#28084
- facebook/react#28063
- facebook/react#28085
- facebook/react#28083
- facebook/react#28065
- facebook/react#28061
- facebook/react#28077
- facebook/react#28075
- facebook/react#28078
- facebook/react#28050
- facebook/react#28011
- facebook/react#28055
- facebook/react#28066
- facebook/react#28067
- facebook/react#28010
- facebook/react#27993
- facebook/react#28052
- facebook/react#28060
- facebook/react#28059
- facebook/react#28034
- facebook/react#28033
- facebook/react#28004
- facebook/react#28051
- facebook/react#28012
- facebook/react#28001
- facebook/react#28002
- facebook/react#27995
- facebook/react#28006
- facebook/react#28005
- facebook/react#28007
- facebook/react#28008
- facebook/react#28009
- facebook/react#28000
- facebook/react#28003
- facebook/react#27997
- facebook/react#27240
- facebook/react#27977
- facebook/react#27940
- facebook/react#27939
gnoff added a commit to gnoff/next.js that referenced this pull request Feb 6, 2024
- facebook/react#28250
- facebook/react#28225
- facebook/react#28123
- facebook/react#28240
- facebook/react#28239
- facebook/react#28245
- facebook/react#28244
- facebook/react#28238
- facebook/react#28235
- facebook/react#28221
- facebook/react#28215
- facebook/react#28214
- facebook/react#28213
- facebook/react#28212
- facebook/react#28211
- facebook/react#28247
- facebook/react#28210
- facebook/react#28186
- facebook/react#28232
- facebook/react#28169
- facebook/react#28177
- facebook/react#28170
- facebook/react#28168
- facebook/react#28122
- facebook/react#27982
- facebook/react#28217
- facebook/react#28223
- facebook/react#28208
- facebook/react#28209
- facebook/react#28200
- facebook/react#28199
- facebook/react#28198
- facebook/react#28197
- facebook/react#28196
- facebook/react#28194
- facebook/react#28192
- facebook/react#28191
- facebook/react#28182
- facebook/react#28181
- facebook/react#28180
- facebook/react#28178
- facebook/react#28201
- facebook/react#28176
- facebook/react#28162
- facebook/react#28131
- facebook/react#28190
- facebook/react#28172
- facebook/react#28171
- facebook/react#28173
- facebook/react#28174
- facebook/react#28175
- facebook/react#28136
- facebook/react#28135
- facebook/react#28134
- facebook/react#28133
- facebook/react#28132
- facebook/react#28130
- facebook/react#28202
- facebook/react#28102
- facebook/react#28161
- facebook/react#28193
- facebook/react#28195
- facebook/react#28189
- facebook/react#28160
- facebook/react#28096
- facebook/react#28183
- facebook/react#28125
- facebook/react#28157
- facebook/react#28115
- facebook/react#28124
- facebook/react#28163
- facebook/react#28164
- facebook/react#28150
- facebook/react#28159
- facebook/react#28069
- facebook/react#28110
- facebook/react#28148
- facebook/react#28116
- facebook/react#28099
- facebook/react#28100
- facebook/react#28147
- facebook/react#28128
- facebook/react#28126
- facebook/react#28139
- facebook/react#28140
- facebook/react#28141
- facebook/react#28142
- facebook/react#28113
- facebook/react#28129
- facebook/react#28114
- facebook/react#28053
- facebook/react#28091
- facebook/react#28087
- facebook/react#28112
- facebook/react#28086
- facebook/react#28101
- facebook/react#28106
- facebook/react#28117
- facebook/react#28118
- facebook/react#28105
- facebook/react#27883
- facebook/react#28111
- facebook/react#28095
- facebook/react#28108
- facebook/react#28090
- facebook/react#28089
- facebook/react#28076
- facebook/react#28074
- facebook/react#28103
- facebook/react#28098
- facebook/react#28097
- facebook/react#28068
- facebook/react#28093
- facebook/react#28094
- facebook/react#28073
- facebook/react#28084
- facebook/react#28063
- facebook/react#28085
- facebook/react#28083
- facebook/react#28065
- facebook/react#28061
- facebook/react#28077
- facebook/react#28075
- facebook/react#28078
- facebook/react#28050
- facebook/react#28011
- facebook/react#28055
- facebook/react#28066
- facebook/react#28067
- facebook/react#28010
- facebook/react#27993
- facebook/react#28052
- facebook/react#28060
- facebook/react#28059
- facebook/react#28034
- facebook/react#28033
- facebook/react#28004
- facebook/react#28051
- facebook/react#28012
- facebook/react#28001
- facebook/react#28002
- facebook/react#27995
- facebook/react#28006
- facebook/react#28005
- facebook/react#28007
- facebook/react#28008
- facebook/react#28009
- facebook/react#28000
- facebook/react#28003
- facebook/react#27997
- facebook/react#27240
- facebook/react#27977
- facebook/react#27940
- facebook/react#27939
- facebook/react#28090
- facebook/react#28089
- facebook/react#28076
- facebook/react#28074
- facebook/react#28103
- facebook/react#28098
- facebook/react#28097
- facebook/react#28068
- facebook/react#28093
- facebook/react#28094
- facebook/react#28073
- facebook/react#28084
- facebook/react#28063
- facebook/react#28085
- facebook/react#28083
- facebook/react#28065
- facebook/react#28061
- facebook/react#28077
- facebook/react#28075
- facebook/react#28078
- facebook/react#28050
- facebook/react#28011
- facebook/react#28055
- facebook/react#28066
- facebook/react#28067
- facebook/react#28010
- facebook/react#27993
- facebook/react#28052
- facebook/react#28060
- facebook/react#28059
- facebook/react#28034
- facebook/react#28033
- facebook/react#28004
- facebook/react#28051
- facebook/react#28012
- facebook/react#28001
- facebook/react#28002
- facebook/react#27995
- facebook/react#28006
- facebook/react#28005
- facebook/react#28007
- facebook/react#28008
- facebook/react#28009
- facebook/react#28000
- facebook/react#28003
- facebook/react#27997
- facebook/react#27240
- facebook/react#27977
- facebook/react#27940
- facebook/react#27939
gnoff added a commit to vercel/next.js that referenced this pull request Feb 6, 2024
Updates React from 60a927d04 to 2bc7d336a

Also updates aliases for `react.shared-subset` to `react.react-server`

### React upstream changes

- facebook/react#28250
- facebook/react#28225
- facebook/react#28123
- facebook/react#28240
- facebook/react#28239
- facebook/react#28245
- facebook/react#28244
- facebook/react#28238
- facebook/react#28235
- facebook/react#28221
- facebook/react#28215
- facebook/react#28214
- facebook/react#28213
- facebook/react#28212
- facebook/react#28211
- facebook/react#28247
- facebook/react#28210
- facebook/react#28186
- facebook/react#28232
- facebook/react#28169
- facebook/react#28177
- facebook/react#28170
- facebook/react#28168
- facebook/react#28122
- facebook/react#27982
- facebook/react#28217
- facebook/react#28223
- facebook/react#28208
- facebook/react#28209
- facebook/react#28200
- facebook/react#28199
- facebook/react#28198
- facebook/react#28197
- facebook/react#28196
- facebook/react#28194
- facebook/react#28192
- facebook/react#28191
- facebook/react#28182
- facebook/react#28181
- facebook/react#28180
- facebook/react#28178
- facebook/react#28201
- facebook/react#28176
- facebook/react#28162
- facebook/react#28131
- facebook/react#28190
- facebook/react#28172
- facebook/react#28171
- facebook/react#28173
- facebook/react#28174
- facebook/react#28175
- facebook/react#28136
- facebook/react#28135
- facebook/react#28134
- facebook/react#28133
- facebook/react#28132
- facebook/react#28130
- facebook/react#28202
- facebook/react#28102
- facebook/react#28161
- facebook/react#28193
- facebook/react#28195
- facebook/react#28189
- facebook/react#28160
- facebook/react#28096
- facebook/react#28183
- facebook/react#28125
- facebook/react#28157
- facebook/react#28115
- facebook/react#28124
- facebook/react#28163
- facebook/react#28164
- facebook/react#28150
- facebook/react#28159
- facebook/react#28069
- facebook/react#28110
- facebook/react#28148
- facebook/react#28116
- facebook/react#28099
- facebook/react#28100
- facebook/react#28147
- facebook/react#28128
- facebook/react#28126
- facebook/react#28139
- facebook/react#28140
- facebook/react#28141
- facebook/react#28142
- facebook/react#28113
- facebook/react#28129
- facebook/react#28114
- facebook/react#28053
- facebook/react#28091
- facebook/react#28087
- facebook/react#28112
- facebook/react#28086
- facebook/react#28101
- facebook/react#28106
- facebook/react#28117
- facebook/react#28118
- facebook/react#28105
- facebook/react#27883
- facebook/react#28111
- facebook/react#28095
- facebook/react#28108
- facebook/react#28090
- facebook/react#28089
- facebook/react#28076
- facebook/react#28074
- facebook/react#28103
- facebook/react#28098
- facebook/react#28097
- facebook/react#28068
- facebook/react#28093
- facebook/react#28094
- facebook/react#28073
- facebook/react#28084
- facebook/react#28063
- facebook/react#28085
- facebook/react#28083
- facebook/react#28065
- facebook/react#28061
- facebook/react#28077
- facebook/react#28075
- facebook/react#28078
- facebook/react#28050
- facebook/react#28011
- facebook/react#28055
- facebook/react#28066
- facebook/react#28067
- facebook/react#28010
- facebook/react#27993
- facebook/react#28052
- facebook/react#28060
- facebook/react#28059
- facebook/react#28034
- facebook/react#28033
- facebook/react#28004
- facebook/react#28051
- facebook/react#28012
- facebook/react#28001
- facebook/react#28002
- facebook/react#27995
- facebook/react#28006
- facebook/react#28005
- facebook/react#28007
- facebook/react#28008
- facebook/react#28009
- facebook/react#28000
- facebook/react#28003
- facebook/react#27997
- facebook/react#27240
- facebook/react#27977
- facebook/react#27940
- facebook/react#27939
- facebook/react#28090
- facebook/react#28089
- facebook/react#28076
- facebook/react#28074
- facebook/react#28103
- facebook/react#28098
- facebook/react#28097
- facebook/react#28068
- facebook/react#28093
- facebook/react#28094
- facebook/react#28073
- facebook/react#28084
- facebook/react#28063
- facebook/react#28085
- facebook/react#28083
- facebook/react#28065
- facebook/react#28061
- facebook/react#28077
- facebook/react#28075
- facebook/react#28078
- facebook/react#28050
- facebook/react#28011
- facebook/react#28055
- facebook/react#28066
- facebook/react#28067
- facebook/react#28010
- facebook/react#27993
- facebook/react#28052
- facebook/react#28060
- facebook/react#28059
- facebook/react#28034
- facebook/react#28033
- facebook/react#28004
- facebook/react#28051
- facebook/react#28012
- facebook/react#28001
- facebook/react#28002
- facebook/react#27995
- facebook/react#28006
- facebook/react#28005
- facebook/react#28007
- facebook/react#28008
- facebook/react#28009
- facebook/react#28000
- facebook/react#28003
- facebook/react#27997
- facebook/react#27240
- facebook/react#27977
- facebook/react#27940
- facebook/react#27939

Closes NEXT-2331
@kspeyanski
Copy link

I believe I have encountered a use case that might benefit from this, so let me share it in case you find it valid or helpful.

const ServerComponent = async (props) => {
  return (
    <React.Suspense fallback={<>{props.children}</>}>
        <AsyncComponentThatWouldGetDataAndRenderClientProvider>
          <>
            {props.children}
          </>
        </AsyncComponentThatWouldGetDataAndRenderClientProvider>
    </React.Suspense>
  )
}

This code seems to work, however, in the streamed payload the props.children tree is present two times - once as the Suspense's fallback and once as children prop of it's parent.

What I've been wondering is, if key-ing the server component (props.children) would minimize the payload?

@sebmarkbage
Copy link
Collaborator Author

What I've been wondering is, if key-ing the server component (props.children) would minimize the payload?

No. Key doesn't have anything to do with that. RSC does dedupe after the third copy of an object has been detected. However, with this change it'll actually be able to apply that optimization in fewer cases than before this change.

In theory the heuristic can get smarter and dedupe this but it has other tradeoffs on performance.

The way to fix it though is to just turn that whole thing into a client component.

@sebmarkbage
Copy link
Collaborator Author

sebmarkbage commented Feb 6, 2024

I don't have time to finish all the tests but here are some test cases that should be tested:

  • Test that a server component doesn't dedupe where it would have different keys depending on where it's used.
  • Test the same thing with an intermediate Promise to ensure that same thing doesn't rely on sync context.
  • Test that an array or iterable child of a server component where there is a parent key but the root server component is not keyed. I.e. for clearing state.
  • Test the same thing but the root server component has a key and you can move the whole thing around inside a set.
  • Test that fragments or other types don't leak the keypath even deeper than nearest host component.

sebmarkbage added a commit that referenced this pull request Feb 8, 2024
…suspend (#28283)

In #28123 I switched these to be lazy references. However that creates a
lazy wrapper even if they're synchronously available. We try to as much
as possible preserve the original data structure in these cases.

E.g. here in the dev outlining I only use a lazy wrapper if it didn't
complete synchronously:
https://github.com/facebook/react/pull/28272/files#diff-d4c9c509922b3671d3ecce4e051df66dd5c3d38ff913c7a7fe94abc3ba2ed72eR638

Unfortunately we don't have a data structure that tracks the status of
each emitted row. We could store the task in the map but then they
couldn't be GC:ed as they complete. We could maybe store the status of
each element but seems so heavy.

For now I just went back to direct reference which might be an issue
since it can suspend something higher up when deduped.
github-actions bot pushed a commit that referenced this pull request Feb 8, 2024
…suspend (#28283)

In #28123 I switched these to be lazy references. However that creates a
lazy wrapper even if they're synchronously available. We try to as much
as possible preserve the original data structure in these cases.

E.g. here in the dev outlining I only use a lazy wrapper if it didn't
complete synchronously:
https://github.com/facebook/react/pull/28272/files#diff-d4c9c509922b3671d3ecce4e051df66dd5c3d38ff913c7a7fe94abc3ba2ed72eR638

Unfortunately we don't have a data structure that tracks the status of
each emitted row. We could store the task in the map but then they
couldn't be GC:ed as they complete. We could maybe store the status of
each element but seems so heavy.

For now I just went back to direct reference which might be an issue
since it can suspend something higher up when deduped.

DiffTrain build for [ba5e6a8](ba5e6a8)
EdisonVan pushed a commit to EdisonVan/react that referenced this pull request Apr 15, 2024
Conceptually a Server Component in the tree is the same as a Client
Component.

When we render a Server Component with a key, that key should be used as
part of the reconciliation process to ensure the children's state are
preserved when they move in a set. The key of a child should also be
used to clear the state of the children when that key changes.

Conversely, if a Server Component doesn't have a key it should get an
implicit key based on the slot number. It should not inherit the key of
its children since the children don't know if that would collide with
other keys in the set the Server Component is rendered in.

A Client Component also has an identity based on the function's
implementation type. That mainly has to do with the state (or future
state after a refactor) that Component might contain. To transfer state
between two implementations it needs to be of the same state type. This
is not a concern for a Server Components since they never have state so
identity doesn't matter.

A Component returns a set of children. If it returns a single child,
that's the same as returning a fragment of one child. So if you
conditionally return a single child or a fragment, they should
technically reconcile against each other.

The simple way to do this is to simply emit a Fragment for every Server
Component. That would be correct in all cases. Unfortunately that is
also unfortunate since it bloats the payload in the common cases. It
also means that Fiber creates an extra indirection in the runtime.

Ideally we want to fold Server Component aways into zero cost on the
client. At least where possible. The common cases are that you don't
specify a key on a single return child, and that you do specify a key on
a Server Component in a dynamic set.

The approach in this PR treats a Server Component that returns other
Server Components or Lazy Nodes as a sequence that can be folded away.
I.e. the parts that don't generate any output in the RSC payload.
Instead, it keeps track of their keys on an internal "context". Which
gets reset after each new reified JSON node gets rendered.

Then we transfer the accumulated keys from any parent Server Components
onto the child element. In the simple case, the child just inherits the
key of the parent.

If the Server Component itself is keyless but a child isn't, we have to
add a wrapper fragment to ensure that this fragment gets the implicit
key but we can still use the key to reset state. This is unusual though
because typically if you keyed something it's because it was already in
a fragment.

In the case a Server Component is keyed but forks its children using a
fragment, we need to key that fragment so that the whole set can move
around as one. In theory this could be flattened into a parent array but
that gets tricky if something suspends, because then we can't send the
siblings early.

The main downside of this approach is that switching between single
child and fragment in a Server Component isn't always going to reconcile
against each other. That's because if we saw a single child first, we'd
have to add the fragment preemptively in case it forks later. This
semantic of React isn't very well known anyway and it might be ok to
break it here for pragmatic reasons. The tests document this
discrepancy.

Another compromise of this approach is that when combining keys we don't
escape them fully. We instead just use a simple `,` separated concat.
This is probably good enough in practice. Additionally, since we don't
encode the implicit 0 index slot key, you can move things around between
parents which shouldn't really reconcile but does. This keeps the keys
shorter and more human readable.
EdisonVan pushed a commit to EdisonVan/react that referenced this pull request Apr 15, 2024
…suspend (facebook#28283)

In facebook#28123 I switched these to be lazy references. However that creates a
lazy wrapper even if they're synchronously available. We try to as much
as possible preserve the original data structure in these cases.

E.g. here in the dev outlining I only use a lazy wrapper if it didn't
complete synchronously:
https://github.com/facebook/react/pull/28272/files#diff-d4c9c509922b3671d3ecce4e051df66dd5c3d38ff913c7a7fe94abc3ba2ed72eR638

Unfortunately we don't have a data structure that tracks the status of
each emitted row. We could store the task in the map but then they
couldn't be GC:ed as they complete. We could maybe store the status of
each element but seems so heavy.

For now I just went back to direct reference which might be an issue
since it can suspend something higher up when deduped.
bigfootjon pushed a commit that referenced this pull request Apr 18, 2024
Conceptually a Server Component in the tree is the same as a Client
Component.

When we render a Server Component with a key, that key should be used as
part of the reconciliation process to ensure the children's state are
preserved when they move in a set. The key of a child should also be
used to clear the state of the children when that key changes.

Conversely, if a Server Component doesn't have a key it should get an
implicit key based on the slot number. It should not inherit the key of
its children since the children don't know if that would collide with
other keys in the set the Server Component is rendered in.

A Client Component also has an identity based on the function's
implementation type. That mainly has to do with the state (or future
state after a refactor) that Component might contain. To transfer state
between two implementations it needs to be of the same state type. This
is not a concern for a Server Components since they never have state so
identity doesn't matter.

A Component returns a set of children. If it returns a single child,
that's the same as returning a fragment of one child. So if you
conditionally return a single child or a fragment, they should
technically reconcile against each other.

The simple way to do this is to simply emit a Fragment for every Server
Component. That would be correct in all cases. Unfortunately that is
also unfortunate since it bloats the payload in the common cases. It
also means that Fiber creates an extra indirection in the runtime.

Ideally we want to fold Server Component aways into zero cost on the
client. At least where possible. The common cases are that you don't
specify a key on a single return child, and that you do specify a key on
a Server Component in a dynamic set.

The approach in this PR treats a Server Component that returns other
Server Components or Lazy Nodes as a sequence that can be folded away.
I.e. the parts that don't generate any output in the RSC payload.
Instead, it keeps track of their keys on an internal "context". Which
gets reset after each new reified JSON node gets rendered.

Then we transfer the accumulated keys from any parent Server Components
onto the child element. In the simple case, the child just inherits the
key of the parent.

If the Server Component itself is keyless but a child isn't, we have to
add a wrapper fragment to ensure that this fragment gets the implicit
key but we can still use the key to reset state. This is unusual though
because typically if you keyed something it's because it was already in
a fragment.

In the case a Server Component is keyed but forks its children using a
fragment, we need to key that fragment so that the whole set can move
around as one. In theory this could be flattened into a parent array but
that gets tricky if something suspends, because then we can't send the
siblings early.

The main downside of this approach is that switching between single
child and fragment in a Server Component isn't always going to reconcile
against each other. That's because if we saw a single child first, we'd
have to add the fragment preemptively in case it forks later. This
semantic of React isn't very well known anyway and it might be ok to
break it here for pragmatic reasons. The tests document this
discrepancy.

Another compromise of this approach is that when combining keys we don't
escape them fully. We instead just use a simple `,` separated concat.
This is probably good enough in practice. Additionally, since we don't
encode the implicit 0 index slot key, you can move things around between
parents which shouldn't really reconcile but does. This keeps the keys
shorter and more human readable.

DiffTrain build for commit 95ec128.
bigfootjon pushed a commit that referenced this pull request Apr 18, 2024
…suspend (#28283)

In #28123 I switched these to be lazy references. However that creates a
lazy wrapper even if they're synchronously available. We try to as much
as possible preserve the original data structure in these cases.

E.g. here in the dev outlining I only use a lazy wrapper if it didn't
complete synchronously:
https://github.com/facebook/react/pull/28272/files#diff-d4c9c509922b3671d3ecce4e051df66dd5c3d38ff913c7a7fe94abc3ba2ed72eR638

Unfortunately we don't have a data structure that tracks the status of
each emitted row. We could store the task in the map but then they
couldn't be GC:ed as they complete. We could maybe store the status of
each element but seems so heavy.

For now I just went back to direct reference which might be an issue
since it can suspend something higher up when deduped.

DiffTrain build for commit ba5e6a8.
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