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

Tag all user space call sites with the "react-stack-bottom-frame" name #30369

Merged
merged 1 commit into from
Jul 22, 2024

Conversation

sebmarkbage
Copy link
Collaborator

Ideally we wouldn't need to filter out React internals and it'd just be covered by ignore listing by any downstream tool. E.g. a framework using captureOwnerStack could have its own ignore listing. Printed owner stacks would get browser source map ignore-listing. React DevTools could have its own ignore list for internals. However, it's nice to be able to provide nice owner stacks without a bunch of noise by default. Especially on the server since they have to be serialized.

We currently call each function that calls into user space and track its stack frame. However, this needs code for checking each one and doesn't let us work across bundles.

Instead, we can name each of these frame something predictable by giving the function a name.

Unfortunately, it's a common practice to rename functions or inline them in compilers. Even if we didn't, others downstream from us or a dev-mode minifier could. I use this .bind() trick to avoid minifying these functions and ensure they get a unique name added to them in all browsers. It's not 100% fool proof since a smart enough compiler could also discover that the this value is not used and strip out the function and then inline it but nobody does this yet at least.

This lets us find the bottom stack easily from stack traces just by looking for the name.

Copy link

vercel bot commented Jul 18, 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 Jul 18, 2024 2:19am

@react-sizebot
Copy link

react-sizebot commented Jul 18, 2024

Comparing: 163365a...e8b559f

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 = 499.44 kB 499.44 kB = 89.59 kB 89.59 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 = 504.26 kB 504.26 kB = 90.30 kB 90.31 kB
facebook-www/ReactDOM-prod.classic.js = 599.20 kB 599.20 kB = 105.80 kB 105.79 kB
facebook-www/ReactDOM-prod.modern.js = 573.37 kB 573.37 kB = 101.68 kB 101.68 kB
test_utils/ReactAllWarnings.js Deleted 64.07 kB 0.00 kB Deleted 15.86 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-stable-rc/react-server/cjs/react-server-flight.development.js +0.65% 80.05 kB 80.57 kB +0.91% 14.87 kB 15.00 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js +0.65% 80.05 kB 80.57 kB +0.91% 14.87 kB 15.00 kB
oss-stable/react-server/cjs/react-server-flight.development.js +0.65% 80.05 kB 80.57 kB +0.91% 14.87 kB 15.00 kB
oss-stable-rc/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +0.49% 124.86 kB 125.48 kB +0.58% 23.19 kB 23.33 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +0.49% 124.86 kB 125.48 kB +0.58% 23.19 kB 23.33 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +0.49% 124.86 kB 125.48 kB +0.58% 23.19 kB 23.33 kB
oss-stable-rc/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +0.49% 125.01 kB 125.63 kB +0.58% 23.26 kB 23.39 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +0.49% 125.01 kB 125.63 kB +0.58% 23.26 kB 23.39 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +0.49% 125.01 kB 125.63 kB +0.58% 23.26 kB 23.39 kB
oss-stable-rc/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.45% 120.03 kB 120.56 kB +0.64% 22.39 kB 22.53 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.45% 120.03 kB 120.56 kB +0.64% 22.39 kB 22.53 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.45% 120.03 kB 120.56 kB +0.64% 22.39 kB 22.53 kB
oss-stable-rc/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.development.js +0.42% 126.39 kB 126.93 kB +0.59% 23.34 kB 23.48 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.development.js +0.42% 126.39 kB 126.93 kB +0.59% 23.34 kB 23.48 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.development.js +0.42% 126.39 kB 126.93 kB +0.59% 23.34 kB 23.48 kB
oss-stable-rc/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +0.42% 126.55 kB 127.09 kB +0.59% 23.41 kB 23.55 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +0.42% 126.55 kB 127.09 kB +0.59% 23.41 kB 23.55 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +0.42% 126.55 kB 127.09 kB +0.59% 23.41 kB 23.55 kB
oss-stable-rc/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.42% 127.50 kB 128.04 kB +0.58% 23.62 kB 23.76 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.42% 127.50 kB 128.04 kB +0.58% 23.62 kB 23.76 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.42% 127.50 kB 128.04 kB +0.58% 23.62 kB 23.76 kB
oss-stable-rc/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.42% 127.65 kB 128.18 kB +0.58% 23.68 kB 23.82 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.42% 127.65 kB 128.18 kB +0.58% 23.68 kB 23.82 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.42% 127.65 kB 128.18 kB +0.58% 23.68 kB 23.82 kB
oss-stable-rc/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +0.42% 123.79 kB 124.31 kB +0.58% 22.98 kB 23.12 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +0.42% 123.79 kB 124.31 kB +0.58% 22.98 kB 23.12 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +0.42% 123.79 kB 124.31 kB +0.58% 22.98 kB 23.12 kB
oss-stable-rc/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +0.42% 124.41 kB 124.93 kB +0.57% 23.17 kB 23.30 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +0.42% 124.41 kB 124.93 kB +0.57% 23.17 kB 23.30 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +0.42% 124.41 kB 124.93 kB +0.57% 23.17 kB 23.30 kB
oss-stable-rc/react-server/cjs/react-server.development.js +0.29% 152.78 kB 153.22 kB +0.28% 28.21 kB 28.29 kB
oss-stable-semver/react-server/cjs/react-server.development.js +0.29% 152.78 kB 153.22 kB +0.28% 28.21 kB 28.29 kB
oss-stable/react-server/cjs/react-server.development.js +0.29% 152.78 kB 153.22 kB +0.28% 28.21 kB 28.29 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.development.js = 963.38 kB 960.78 kB = 163.83 kB 163.46 kB
oss-experimental/react-dom/cjs/react-dom-profiling.development.js = 962.80 kB 960.21 kB = 162.77 kB 162.41 kB
oss-experimental/react-dom/cjs/react-dom-client.development.js = 946.41 kB 943.82 kB = 159.96 kB 159.59 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js = 642.84 kB 640.25 kB = 103.37 kB 103.01 kB
oss-experimental/react-art/cjs/react-art.development.js = 565.32 kB 562.72 kB = 91.59 kB 91.24 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.development.js = 328.60 kB 326.70 kB = 63.30 kB 62.99 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.development.js = 391.36 kB 389.08 kB = 69.11 kB 68.74 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.development.js = 390.81 kB 388.53 kB = 69.03 kB 68.66 kB
oss-experimental/react-dom/cjs/react-dom-server.node.development.js = 386.27 kB 383.99 kB = 68.47 kB 68.16 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.development.js = 367.84 kB 365.56 kB = 66.26 kB 65.92 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.development.js = 367.84 kB 365.56 kB = 66.26 kB 65.92 kB
oss-experimental/react-html/cjs/react-html.development.js = 339.93 kB 337.65 kB = 62.14 kB 61.78 kB
oss-experimental/react-html/cjs/react-html.react-server.development.js = 486.40 kB 482.39 kB = 87.91 kB 87.32 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js = 141.58 kB 140.00 kB = 26.23 kB 26.02 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js = 143.79 kB 142.19 kB = 26.59 kB 26.40 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js = 141.43 kB 139.85 kB = 26.16 kB 25.96 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js = 143.65 kB 142.05 kB = 26.53 kB 26.34 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js = 142.70 kB 141.10 kB = 26.33 kB 26.14 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.development.js = 142.54 kB 140.94 kB = 26.27 kB 26.07 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js = 136.17 kB 134.57 kB = 25.29 kB 25.09 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js = 140.13 kB 138.41 kB = 26.00 kB 25.82 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js = 139.51 kB 137.79 kB = 25.82 kB 25.64 kB
oss-experimental/react-server/cjs/react-server.development.js = 171.78 kB 169.50 kB = 31.01 kB 30.66 kB
oss-experimental/react-server/cjs/react-server-flight.development.js = 95.81 kB 94.09 kB = 17.64 kB 17.47 kB
test_utils/ReactAllWarnings.js Deleted 64.07 kB 0.00 kB Deleted 15.86 kB 0.00 kB

Generated by 🚫 dangerJS against e8b559f

We can use this trick to avoid minifying these functions and ensure they
get a unique name added to them in all browsers.

This lets us find the bottom stack easily from stack traces.
@sebmarkbage
Copy link
Collaborator Author

I noticed that, in JSC, if the function body is too small/simple it can get inlined and the stack frame excluded. That could maybe also be a JIT level specific - like only if it gets to the highest optimization tier does it kick in. So it's not just minifiers that might inline but the VM as well.

That's something we'll have to be careful about but that also applies to both the previous technique and this new one.

Copy link
Collaborator

@eps1lon eps1lon left a comment

Choose a reason for hiding this comment

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

Does this change any observable behavior or is this purely an internal refactor? Would be nice to have tests if this changes behavior.

@sebmarkbage
Copy link
Collaborator Author

It's just an internal refactor that then becomes the basis for a stack of other PRs.

@sebmarkbage sebmarkbage merged commit 792f192 into facebook:main Jul 22, 2024
188 checks passed
github-actions bot pushed a commit that referenced this pull request Jul 22, 2024
#30369)

Ideally we wouldn't need to filter out React internals and it'd just be
covered by ignore listing by any downstream tool. E.g. a framework using
captureOwnerStack could have its own ignore listing. Printed owner
stacks would get browser source map ignore-listing. React DevTools could
have its own ignore list for internals. However, it's nice to be able to
provide nice owner stacks without a bunch of noise by default.
Especially on the server since they have to be serialized.

We currently call each function that calls into user space and track its
stack frame. However, this needs code for checking each one and doesn't
let us work across bundles.

Instead, we can name each of these frame something predictable by giving
the function a name.

Unfortunately, it's a common practice to rename functions or inline them
in compilers. Even if we didn't, others downstream from us or a dev-mode
minifier could. I use this `.bind()` trick to avoid minifying these
functions and ensure they get a unique name added to them in all
browsers. It's not 100% fool proof since a smart enough compiler could
also discover that the `this` value is not used and strip out the
function and then inline it but nobody does this yet at least.

This lets us find the bottom stack easily from stack traces just by
looking for the name.

DiffTrain build for commit 792f192.
github-actions bot pushed a commit that referenced this pull request Jul 22, 2024
#30369)

Ideally we wouldn't need to filter out React internals and it'd just be
covered by ignore listing by any downstream tool. E.g. a framework using
captureOwnerStack could have its own ignore listing. Printed owner
stacks would get browser source map ignore-listing. React DevTools could
have its own ignore list for internals. However, it's nice to be able to
provide nice owner stacks without a bunch of noise by default.
Especially on the server since they have to be serialized.

We currently call each function that calls into user space and track its
stack frame. However, this needs code for checking each one and doesn't
let us work across bundles.

Instead, we can name each of these frame something predictable by giving
the function a name.

Unfortunately, it's a common practice to rename functions or inline them
in compilers. Even if we didn't, others downstream from us or a dev-mode
minifier could. I use this `.bind()` trick to avoid minifying these
functions and ensure they get a unique name added to them in all
browsers. It's not 100% fool proof since a smart enough compiler could
also discover that the `this` value is not used and strip out the
function and then inline it but nobody does this yet at least.

This lets us find the bottom stack easily from stack traces just by
looking for the name.

DiffTrain build for [792f192](792f192)
sebmarkbage added a commit that referenced this pull request Jul 22, 2024
Stacked on #30400 and
#30369

Previously we were using fake evals to recreate a stack for console
replaying and thrown errors. However, for owner stacks we just used the
raw string that came from the server.

This means that the format of the owner stack could include different
formats. Like Spidermonkey format for the client components and V8 for
the server components. This means that this stack can't be parsed
natively by the browser like when printing them as error like in
#30289. Additionally, since
there's no source file registered with that name and no source mapping
url, it can't be source mapped.

Before:

<img width="1329" alt="before-firefox"
src="https://github.com/user-attachments/assets/cbe03f9c-96ac-48fb-b58f-f3a224a774f4">

Instead, we need to create a fake stack like we do for the other things.
That way when it's printed as an Error it gets source mapped. It also
means that the format is consistently in the native format of the
current browser.

After:

<img width="753" alt="after-firefox"
src="https://github.com/user-attachments/assets/b436f1f5-ca37-4203-b29f-df9828c9fad3">

So this is nice because you can just take the result from
`captureOwnerStack()` and append it to an `Error` stack and print it
natively. E.g. this is what React DevTools will do.

If you want to parse and present it yourself though it's a bit awkward
though. The `captureOwnerStack()` API now includes a bunch of
`rsc://React/` URLs. These don't really have any direct connection to
the source map. Only the browser knows this connection from the eval.
You basically have to strip the prefix and then manually pass the
remainder to your own `findSourceMapURL`.

Another awkward part is that since Safari doesn't support eval sourceURL
exposed into `error.stack` - it means that `captureOwnerStack()` get an
empty location for server components since the fake eval doesn't work
there. That's not a big deal since these stacks are already broken even
for client modules for many because the `eval-source-map` strategy in
Webpack doesn't work in Safari for this same reason.

A lot of this refactoring is just clarifying that there's three kind of
ReactComponentInfo fields:

- `stack` - The raw stack as described on the original server.
- `debugStack` - The Error object containing the stack as represented in
the current client as fake evals.
- `debugTask` - The same thing as `debugStack` but described in terms of
a native `console.createTask`.
github-actions bot pushed a commit that referenced this pull request Jul 22, 2024
Stacked on #30400 and
#30369

Previously we were using fake evals to recreate a stack for console
replaying and thrown errors. However, for owner stacks we just used the
raw string that came from the server.

This means that the format of the owner stack could include different
formats. Like Spidermonkey format for the client components and V8 for
the server components. This means that this stack can't be parsed
natively by the browser like when printing them as error like in
#30289. Additionally, since
there's no source file registered with that name and no source mapping
url, it can't be source mapped.

Before:

<img width="1329" alt="before-firefox"
src="https://github.com/user-attachments/assets/cbe03f9c-96ac-48fb-b58f-f3a224a774f4">

Instead, we need to create a fake stack like we do for the other things.
That way when it's printed as an Error it gets source mapped. It also
means that the format is consistently in the native format of the
current browser.

After:

<img width="753" alt="after-firefox"
src="https://github.com/user-attachments/assets/b436f1f5-ca37-4203-b29f-df9828c9fad3">

So this is nice because you can just take the result from
`captureOwnerStack()` and append it to an `Error` stack and print it
natively. E.g. this is what React DevTools will do.

If you want to parse and present it yourself though it's a bit awkward
though. The `captureOwnerStack()` API now includes a bunch of
`rsc://React/` URLs. These don't really have any direct connection to
the source map. Only the browser knows this connection from the eval.
You basically have to strip the prefix and then manually pass the
remainder to your own `findSourceMapURL`.

Another awkward part is that since Safari doesn't support eval sourceURL
exposed into `error.stack` - it means that `captureOwnerStack()` get an
empty location for server components since the fake eval doesn't work
there. That's not a big deal since these stacks are already broken even
for client modules for many because the `eval-source-map` strategy in
Webpack doesn't work in Safari for this same reason.

A lot of this refactoring is just clarifying that there's three kind of
ReactComponentInfo fields:

- `stack` - The raw stack as described on the original server.
- `debugStack` - The Error object containing the stack as represented in
the current client as fake evals.
- `debugTask` - The same thing as `debugStack` but described in terms of
a native `console.createTask`.

DiffTrain build for [b15c198](b15c198)
sebmarkbage added a commit that referenced this pull request Jul 22, 2024
Stacked on #30410.

Use "owner stacks" as the appended component stack if it is available on
the Fiber. This will only be available if the enableOwnerStacks flag is
on. Otherwise it fallback to parent stacks. In prod, there's no owner so
it's never added there.

I was going back and forth on whether to inject essentially
`captureOwnerStack` as part of the DevTools hooks or replicate the
implementation but decided to replicate the implementation.

The DevTools needs all the same information from internals to implement
owner views elsewhere in the UI anyway so we're not saving anything in
terms of the scope of internals. Additionally, we really need this
information for non-current components as well like "rendered by" views
of the currently selected component.

It can also be useful if we need to change the format after the fact
like we did for parent stacks in:
#30289

Injecting the implementation would lock us into specifics both in terms
of what the core needs to provide and what the DevTools can use.

The implementation depends on the technique used in #30369 which tags
frames to strip out with `react-stack-bottom-frame`. That's how the
implementation knows how to materialize the error if it hasn't already.

Firefox:

<img width="487" alt="Screenshot 2024-07-21 at 11 33 37 PM"
src="https://github.com/user-attachments/assets/d3539b53-4578-4fdd-af25-25698b2bcc7d">

Follow up: One thing about this view is that it doesn't include the
current actual synchronous stack. When I used to append these I would
include both the real current stack and the owner stack. That's because
the owner stack doesn't include the name of the currently executing
component. I'll probably inject the current stack too in addition to the
owner stack. This is similar to how native Async Stacks are basically
just appended onto the current stack rather than its own.
felixshiftellecon added a commit to felixshiftellecon/react that referenced this pull request Jul 24, 2024
Stacked on facebook#30410.

Use "owner stacks" as the appended component stack if it is available on
the Fiber. This will only be available if the enableOwnerStacks flag is
on. Otherwise it fallback to parent stacks. In prod, there's no owner so
it's never added there.

I was going back and forth on whether to inject essentially
`captureOwnerStack` as part of the DevTools hooks or replicate the
implementation but decided to replicate the implementation.

The DevTools needs all the same information from internals to implement
owner views elsewhere in the UI anyway so we're not saving anything in
terms of the scope of internals. Additionally, we really need this
information for non-current components as well like "rendered by" views
of the currently selected component.

It can also be useful if we need to change the format after the fact
like we did for parent stacks in:
facebook#30289

Injecting the implementation would lock us into specifics both in terms
of what the core needs to provide and what the DevTools can use.

The implementation depends on the technique used in facebook#30369 which tags
frames to strip out with `react-stack-bottom-frame`. That's how the
implementation knows how to materialize the error if it hasn't already.

Firefox:

<img width="487" alt="Screenshot 2024-07-21 at 11 33 37 PM"
src="https://github.com/user-attachments/assets/d3539b53-4578-4fdd-af25-25698b2bcc7d">

Follow up: One thing about this view is that it doesn't include the
current actual synchronous stack. When I used to append these I would
include both the real current stack and the owner stack. That's because
the owner stack doesn't include the name of the currently executing
component. I'll probably inject the current stack too in addition to the
owner stack. This is similar to how native Async Stacks are basically
just appended onto the current stack rather than its own.
felixshiftellecon added a commit to felixshiftellecon/react that referenced this pull request Jul 24, 2024
Stacked on facebook#30400 and
facebook#30369

Previously we were using fake evals to recreate a stack for console
replaying and thrown errors. However, for owner stacks we just used the
raw string that came from the server.

This means that the format of the owner stack could include different
formats. Like Spidermonkey format for the client components and V8 for
the server components. This means that this stack can't be parsed
natively by the browser like when printing them as error like in
facebook#30289. Additionally, since
there's no source file registered with that name and no source mapping
url, it can't be source mapped.

Before:

<img width="1329" alt="before-firefox"
src="https://github.com/user-attachments/assets/cbe03f9c-96ac-48fb-b58f-f3a224a774f4">

Instead, we need to create a fake stack like we do for the other things.
That way when it's printed as an Error it gets source mapped. It also
means that the format is consistently in the native format of the
current browser.

After:

<img width="753" alt="after-firefox"
src="https://github.com/user-attachments/assets/b436f1f5-ca37-4203-b29f-df9828c9fad3">

So this is nice because you can just take the result from
`captureOwnerStack()` and append it to an `Error` stack and print it
natively. E.g. this is what React DevTools will do.

If you want to parse and present it yourself though it's a bit awkward
though. The `captureOwnerStack()` API now includes a bunch of
`rsc://React/` URLs. These don't really have any direct connection to
the source map. Only the browser knows this connection from the eval.
You basically have to strip the prefix and then manually pass the
remainder to your own `findSourceMapURL`.

Another awkward part is that since Safari doesn't support eval sourceURL
exposed into `error.stack` - it means that `captureOwnerStack()` get an
empty location for server components since the fake eval doesn't work
there. That's not a big deal since these stacks are already broken even
for client modules for many because the `eval-source-map` strategy in
Webpack doesn't work in Safari for this same reason.

A lot of this refactoring is just clarifying that there's three kind of
ReactComponentInfo fields:

- `stack` - The raw stack as described on the original server.
- `debugStack` - The Error object containing the stack as represented in
the current client as fake evals.
- `debugTask` - The same thing as `debugStack` but described in terms of
a native `console.createTask`.
felixshiftellecon added a commit to felixshiftellecon/react that referenced this pull request Jul 24, 2024
facebook#30369)

Ideally we wouldn't need to filter out React internals and it'd just be
covered by ignore listing by any downstream tool. E.g. a framework using
captureOwnerStack could have its own ignore listing. Printed owner
stacks would get browser source map ignore-listing. React DevTools could
have its own ignore list for internals. However, it's nice to be able to
provide nice owner stacks without a bunch of noise by default.
Especially on the server since they have to be serialized.

We currently call each function that calls into user space and track its
stack frame. However, this needs code for checking each one and doesn't
let us work across bundles.

Instead, we can name each of these frame something predictable by giving
the function a name.

Unfortunately, it's a common practice to rename functions or inline them
in compilers. Even if we didn't, others downstream from us or a dev-mode
minifier could. I use this `.bind()` trick to avoid minifying these
functions and ensure they get a unique name added to them in all
browsers. It's not 100% fool proof since a smart enough compiler could
also discover that the `this` value is not used and strip out the
function and then inline it but nobody does this yet at least.

This lets us find the bottom stack easily from stack traces just by
looking for the name.
felixshiftellecon added a commit to felixshiftellecon/react that referenced this pull request Jul 24, 2024
Stacked on facebook#30410.

Use "owner stacks" as the appended component stack if it is available on
the Fiber. This will only be available if the enableOwnerStacks flag is
on. Otherwise it fallback to parent stacks. In prod, there's no owner so
it's never added there.

I was going back and forth on whether to inject essentially
`captureOwnerStack` as part of the DevTools hooks or replicate the
implementation but decided to replicate the implementation.

The DevTools needs all the same information from internals to implement
owner views elsewhere in the UI anyway so we're not saving anything in
terms of the scope of internals. Additionally, we really need this
information for non-current components as well like "rendered by" views
of the currently selected component.

It can also be useful if we need to change the format after the fact
like we did for parent stacks in:
facebook#30289

Injecting the implementation would lock us into specifics both in terms
of what the core needs to provide and what the DevTools can use.

The implementation depends on the technique used in facebook#30369 which tags
frames to strip out with `react-stack-bottom-frame`. That's how the
implementation knows how to materialize the error if it hasn't already.

Firefox:

<img width="487" alt="Screenshot 2024-07-21 at 11 33 37 PM"
src="https://github.com/user-attachments/assets/d3539b53-4578-4fdd-af25-25698b2bcc7d">

Follow up: One thing about this view is that it doesn't include the
current actual synchronous stack. When I used to append these I would
include both the real current stack and the owner stack. That's because
the owner stack doesn't include the name of the currently executing
component. I'll probably inject the current stack too in addition to the
owner stack. This is similar to how native Async Stacks are basically
just appended onto the current stack rather than its own.
felixshiftellecon added a commit to felixshiftellecon/react that referenced this pull request Jul 24, 2024
Stacked on facebook#30400 and
facebook#30369

Previously we were using fake evals to recreate a stack for console
replaying and thrown errors. However, for owner stacks we just used the
raw string that came from the server.

This means that the format of the owner stack could include different
formats. Like Spidermonkey format for the client components and V8 for
the server components. This means that this stack can't be parsed
natively by the browser like when printing them as error like in
facebook#30289. Additionally, since
there's no source file registered with that name and no source mapping
url, it can't be source mapped.

Before:

<img width="1329" alt="before-firefox"
src="https://github.com/user-attachments/assets/cbe03f9c-96ac-48fb-b58f-f3a224a774f4">

Instead, we need to create a fake stack like we do for the other things.
That way when it's printed as an Error it gets source mapped. It also
means that the format is consistently in the native format of the
current browser.

After:

<img width="753" alt="after-firefox"
src="https://github.com/user-attachments/assets/b436f1f5-ca37-4203-b29f-df9828c9fad3">

So this is nice because you can just take the result from
`captureOwnerStack()` and append it to an `Error` stack and print it
natively. E.g. this is what React DevTools will do.

If you want to parse and present it yourself though it's a bit awkward
though. The `captureOwnerStack()` API now includes a bunch of
`rsc://React/` URLs. These don't really have any direct connection to
the source map. Only the browser knows this connection from the eval.
You basically have to strip the prefix and then manually pass the
remainder to your own `findSourceMapURL`.

Another awkward part is that since Safari doesn't support eval sourceURL
exposed into `error.stack` - it means that `captureOwnerStack()` get an
empty location for server components since the fake eval doesn't work
there. That's not a big deal since these stacks are already broken even
for client modules for many because the `eval-source-map` strategy in
Webpack doesn't work in Safari for this same reason.

A lot of this refactoring is just clarifying that there's three kind of
ReactComponentInfo fields:

- `stack` - The raw stack as described on the original server.
- `debugStack` - The Error object containing the stack as represented in
the current client as fake evals.
- `debugTask` - The same thing as `debugStack` but described in terms of
a native `console.createTask`.
felixshiftellecon added a commit to felixshiftellecon/react that referenced this pull request Jul 24, 2024
facebook#30369)

Ideally we wouldn't need to filter out React internals and it'd just be
covered by ignore listing by any downstream tool. E.g. a framework using
captureOwnerStack could have its own ignore listing. Printed owner
stacks would get browser source map ignore-listing. React DevTools could
have its own ignore list for internals. However, it's nice to be able to
provide nice owner stacks without a bunch of noise by default.
Especially on the server since they have to be serialized.

We currently call each function that calls into user space and track its
stack frame. However, this needs code for checking each one and doesn't
let us work across bundles.

Instead, we can name each of these frame something predictable by giving
the function a name.

Unfortunately, it's a common practice to rename functions or inline them
in compilers. Even if we didn't, others downstream from us or a dev-mode
minifier could. I use this `.bind()` trick to avoid minifying these
functions and ensure they get a unique name added to them in all
browsers. It's not 100% fool proof since a smart enough compiler could
also discover that the `this` value is not used and strip out the
function and then inline it but nobody does this yet at least.

This lets us find the bottom stack easily from stack traces just by
looking for the name.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants