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] Eval Fake Server Component Functions to Recreate Native Stacks #29632

Merged
merged 5 commits into from
May 30, 2024

Conversation

sebmarkbage
Copy link
Collaborator

@sebmarkbage sebmarkbage commented May 29, 2024

We have three kinds of stacks that we send in the RSC protocol:

  • The stack trace where a replayed console.log was called on the server.
  • The JSX callsite that created a Server Component which then later called another component.
  • The JSX callsite that created a Host or Client Component.

These stack frames disappear in native stacks on the client since they're executed on the server. This evals a fake file which only has one call in it on the same line/column as the server. Then we call through these fake modules to "replay" the callstack. We then replay the console.log within this stack, or call console.createTask in this stack to recreate the stack.

The main concern with this approach is the performance. It adds significant cost to create all these eval:ed functions but it should eventually balance out.

This doesn't yet apply source maps to these. With source maps it'll be able to show the server source code when clicking the links.

I don't love how these appear.

  • Because we haven't yet initialized the client module we don't have the name of the client component we're about to render yet which leads to the <...> task name.
  • The (async) suffix Chrome adds is still a problem.
  • The VMxxxx prefix is used to disambiguate which is noisy. Might be helped by source maps.
  • The continuation of the async stacks end up rooted somewhere in the bootstrapping of the app. This might be ok when the bootstrapping ends up ignore listed but it's kind of a problem that you can't clear the async stack.
Screenshot 2024-05-28 at 11 58 56 PM Screenshot 2024-05-28 at 11 58 07 PM Screenshot 2024-05-28 at 11 58 31 PM Screenshot 2024-05-28 at 11 59 12 PM

This lets the native stack traces include Server Component's stack frames.

This applies to client element's stacks, console replay stacks, and server component's stacks.
@sebmarkbage sebmarkbage requested review from gnoff and hoxyq May 29, 2024 04:15
Copy link

vercel bot commented May 29, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
react-compiler-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 29, 2024 7:58pm

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels May 29, 2024
@react-sizebot
Copy link

react-sizebot commented May 29, 2024

Comparing: 6f23540...592ad3f

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.66 kB 6.66 kB = 1.82 kB 1.82 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 496.04 kB 496.04 kB = 88.77 kB 88.77 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.67 kB 6.67 kB = 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 500.84 kB 500.84 kB = 89.46 kB 89.46 kB
facebook-www/ReactDOM-prod.classic.js = 593.48 kB 593.48 kB = 104.38 kB 104.38 kB
facebook-www/ReactDOM-prod.modern.js = 569.87 kB 569.87 kB = 100.77 kB 100.77 kB
oss-experimental/react-client/cjs/react-client-flight.development.js +8.79% 92.30 kB 100.42 kB +10.96% 20.46 kB 22.71 kB
oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +8.79% 92.31 kB 100.43 kB +10.80% 20.17 kB 22.35 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +8.77% 92.55 kB 100.67 kB +10.75% 20.24 kB 22.42 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js +8.47% 95.80 kB 103.92 kB +10.25% 21.21 kB 23.39 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +8.43% 96.31 kB 104.43 kB +10.18% 21.37 kB 23.55 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +8.10% 100.23 kB 108.34 kB +10.13% 22.25 kB 24.50 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.unbundled.development.js +7.96% 102.01 kB 110.12 kB +9.88% 22.81 kB 25.06 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.development.js +7.95% 102.04 kB 110.16 kB +9.87% 22.83 kB 25.09 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.development.js +7.85% 103.44 kB 111.56 kB +9.74% 23.19 kB 25.45 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js +7.84% 103.47 kB 111.59 kB +9.72% 23.24 kB 25.50 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.development.js +7.77% 104.52 kB 112.64 kB +9.67% 23.40 kB 25.66 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js +7.76% 104.55 kB 112.66 kB +9.65% 23.44 kB 25.70 kB
test_utils/ReactAllWarnings.js Deleted 63.82 kB 0.00 kB Deleted 15.95 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-client/cjs/react-client-flight.development.js +8.79% 92.30 kB 100.42 kB +10.96% 20.46 kB 22.71 kB
oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +8.79% 92.31 kB 100.43 kB +10.80% 20.17 kB 22.35 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +8.77% 92.55 kB 100.67 kB +10.75% 20.24 kB 22.42 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js +8.47% 95.80 kB 103.92 kB +10.25% 21.21 kB 23.39 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +8.43% 96.31 kB 104.43 kB +10.18% 21.37 kB 23.55 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +8.10% 100.23 kB 108.34 kB +10.13% 22.25 kB 24.50 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.unbundled.development.js +7.96% 102.01 kB 110.12 kB +9.88% 22.81 kB 25.06 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.development.js +7.95% 102.04 kB 110.16 kB +9.87% 22.83 kB 25.09 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.development.js +7.85% 103.44 kB 111.56 kB +9.74% 23.19 kB 25.45 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js +7.84% 103.47 kB 111.59 kB +9.72% 23.24 kB 25.50 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.development.js +7.77% 104.52 kB 112.64 kB +9.67% 23.40 kB 25.66 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js +7.76% 104.55 kB 112.66 kB +9.65% 23.44 kB 25.70 kB
test_utils/ReactAllWarnings.js Deleted 63.82 kB 0.00 kB Deleted 15.95 kB 0.00 kB

Generated by 🚫 dangerJS against 592ad3f

This is because otherwise it ends up being "eval" instead of missing
due to this being in an eval:ed script.
type !== null &&
type.$$typeof === REACT_LAZY_TYPE
) {
if (type._init === readChunk) {
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 thinking of maybe using "use client" as the task name in this case because you're about to enter the the client and likely the client code will include the name of the client component but we may not have the code loaded yet when the element is created.

There should be only one of these if you follow the owner stack.

For host components and built-ins we use the string name since nothing else
will have those as the owner it'll always terminate with those and they
don't have another name.
Comment on lines +1712 to +1714
const filename = parsed[2] || parsed[5] || '';
const line = +(parsed[3] || parsed[6]);
const col = +(parsed[4] || parsed[7]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you sure that all of this will be present in the stack frame?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If it's not, it won't match the regexp an we'll skip the frame. This is not perfect for edge cases like built-ins. We also have to do something for Hermes for HaaS and any other VM that we choose to support. We can iterate.

Bun uses JSC but it seems to use V8-style stack trace formatting too.

There is also an option that use the prepareStackTrace technique on the server to get a structured stack and then send structured data in the RSC protocol instead of strings. This would also let use do arbitrary formatting on the client. I was hoping to avoid that complexity though.

@sebmarkbage sebmarkbage merged commit 9d4fba0 into facebook:main May 30, 2024
40 checks passed
github-actions bot pushed a commit that referenced this pull request May 30, 2024
…ks (#29632)

We have three kinds of stacks that we send in the RSC protocol:

- The stack trace where a replayed `console.log` was called on the
server.
- The JSX callsite that created a Server Component which then later
called another component.
- The JSX callsite that created a Host or Client Component.

These stack frames disappear in native stacks on the client since
they're executed on the server. This evals a fake file which only has
one call in it on the same line/column as the server. Then we call
through these fake modules to "replay" the callstack. We then replay the
`console.log` within this stack, or call `console.createTask` in this
stack to recreate the stack.

The main concern with this approach is the performance. It adds
significant cost to create all these eval:ed functions but it should
eventually balance out.

This doesn't yet apply source maps to these. With source maps it'll be
able to show the server source code when clicking the links.

I don't love how these appear.

- Because we haven't yet initialized the client module we don't have the
name of the client component we're about to render yet which leads to
the `<...>` task name.
- The `(async)` suffix Chrome adds is still a problem.
- The VMxxxx prefix is used to disambiguate which is noisy. Might be
helped by source maps.
- The continuation of the async stacks end up rooted somewhere in the
bootstrapping of the app. This might be ok when the bootstrapping ends
up ignore listed but it's kind of a problem that you can't clear the
async stack.

<img width="927" alt="Screenshot 2024-05-28 at 11 58 56 PM"
src="https://github.com/facebook/react/assets/63648/1c9d32ce-e671-47c8-9d18-9fab3bffabd0">

<img width="431" alt="Screenshot 2024-05-28 at 11 58 07 PM"
src="https://github.com/facebook/react/assets/63648/52f57518-bbed-400e-952d-6650835ac6b6">
<img width="327" alt="Screenshot 2024-05-28 at 11 58 31 PM"
src="https://github.com/facebook/react/assets/63648/d311a639-79a1-457f-9a46-4f3298d07e65">

<img width="817" alt="Screenshot 2024-05-28 at 11 59 12 PM"
src="https://github.com/facebook/react/assets/63648/3aefd356-acf4-4daa-bdbf-b8c8345f6d4b">

DiffTrain build for commit 9d4fba0.
github-actions bot pushed a commit that referenced this pull request May 30, 2024
…ks (#29632)

We have three kinds of stacks that we send in the RSC protocol:

- The stack trace where a replayed `console.log` was called on the
server.
- The JSX callsite that created a Server Component which then later
called another component.
- The JSX callsite that created a Host or Client Component.

These stack frames disappear in native stacks on the client since
they're executed on the server. This evals a fake file which only has
one call in it on the same line/column as the server. Then we call
through these fake modules to "replay" the callstack. We then replay the
`console.log` within this stack, or call `console.createTask` in this
stack to recreate the stack.

The main concern with this approach is the performance. It adds
significant cost to create all these eval:ed functions but it should
eventually balance out.

This doesn't yet apply source maps to these. With source maps it'll be
able to show the server source code when clicking the links.

I don't love how these appear.

- Because we haven't yet initialized the client module we don't have the
name of the client component we're about to render yet which leads to
the `<...>` task name.
- The `(async)` suffix Chrome adds is still a problem.
- The VMxxxx prefix is used to disambiguate which is noisy. Might be
helped by source maps.
- The continuation of the async stacks end up rooted somewhere in the
bootstrapping of the app. This might be ok when the bootstrapping ends
up ignore listed but it's kind of a problem that you can't clear the
async stack.

<img width="927" alt="Screenshot 2024-05-28 at 11 58 56 PM"
src="https://github.com/facebook/react/assets/63648/1c9d32ce-e671-47c8-9d18-9fab3bffabd0">

<img width="431" alt="Screenshot 2024-05-28 at 11 58 07 PM"
src="https://github.com/facebook/react/assets/63648/52f57518-bbed-400e-952d-6650835ac6b6">
<img width="327" alt="Screenshot 2024-05-28 at 11 58 31 PM"
src="https://github.com/facebook/react/assets/63648/d311a639-79a1-457f-9a46-4f3298d07e65">

<img width="817" alt="Screenshot 2024-05-28 at 11 59 12 PM"
src="https://github.com/facebook/react/assets/63648/3aefd356-acf4-4daa-bdbf-b8c8345f6d4b">

DiffTrain build for [9d4fba0](9d4fba0)
sebmarkbage added a commit that referenced this pull request May 30, 2024
Follow up to #29632.

It's possible for `eval` to throw such as if we're in a CSP environment.
This is non-essential debug information. We can still proceed to create
a fake stack entry. It'll still have the right name. It just won't have
the right line/col number nor source url/source map. It might also be
ignored listed since it's inside Flight.
github-actions bot pushed a commit that referenced this pull request May 30, 2024
Follow up to #29632.

It's possible for `eval` to throw such as if we're in a CSP environment.
This is non-essential debug information. We can still proceed to create
a fake stack entry. It'll still have the right name. It just won't have
the right line/col number nor source url/source map. It might also be
ignored listed since it's inside Flight.

DiffTrain build for commit 9710853.
github-actions bot pushed a commit that referenced this pull request May 30, 2024
Follow up to #29632.

It's possible for `eval` to throw such as if we're in a CSP environment.
This is non-essential debug information. We can still proceed to create
a fake stack entry. It'll still have the right name. It just won't have
the right line/col number nor source url/source map. It might also be
ignored listed since it's inside Flight.

DiffTrain build for [9710853](9710853)
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