Skip to content

Commit

Permalink
Track Stack of JSX Calls (#29032)
Browse files Browse the repository at this point in the history
This is the first step to experimenting with a new type of stack traces
behind the `enableOwnerStacks` flag - in DEV only.

The idea is to generate stacks that are more like if the JSX was a
direct call even though it's actually a lazy call. Not only can you see
which exact JSX call line number generated the erroring component but if
that's inside an abstraction function, which function called that
function and if it's a component, which component generated that
component. For this to make sense it really need to be the "owner" stack
rather than the parent stack like we do for other component stacks. On
one hand it has more precise information but on the other hand it also
loses context. For most types of problems the owner stack is the most
useful though since it tells you which component rendered this
component.

The problem with the platform in its current state is that there's two
ways to deal with stacks:

1) `new Error().stack`
2) `console.createTask()`

The nice thing about `new Error().stack` is that we can extract the
frames and piece them together in whatever way we want. That is great
for constructing custom UIs like error dialogs. Unfortunately, we can't
take custom stacks and set them in the native UIs like Chrome DevTools.

The nice thing about `console.createTask()` is that the resulting stacks
are natively integrated into the Chrome DevTools in the console and the
breakpoint debugger. They also automatically follow source mapping and
ignoreLists. The downside is that there's no way to extract the async
stack outside the native UI itself so this information cannot be used
for custom UIs like errors dialogs. It also means we can't collect this
on the server and then pass it to the client for server components.

The solution here is that we use both techniques and collect both an
`Error` object and a `Task` object for every JSX call.

The main concern about this approach is the performance so that's the
main thing to test. It's certainly too slow for production but it might
also be too slow even for DEV.

This first PR doesn't actually use the stacks yet. It just collects them
as the first step. The next step is to start utilizing this information
in error printing etc.

For RSC we pass the stack along across over the wire. This can be
concatenated on the client following the owner path to create an owner
stack leading back into the server. We'll later use this information to
restore fake frames on the client for native integration. Since this
information quickly gets pretty heavy if we include all frames, we strip
out the top frame. We also strip out everything below the functions that
call into user space in the Flight runtime. To do this we need to figure
out the frames that represents calling out into user space. The
resulting stack is typically just the one frame inside the owner
component's JSX callsite. I also eagerly strip out things we expect to
be ignoreList:ed anyway - such as `node_modules` and Node.js internals.

DiffTrain build for [151cce3](151cce3)
  • Loading branch information
sebmarkbage committed May 9, 2024
1 parent 3229b97 commit 732aef7
Show file tree
Hide file tree
Showing 5 changed files with 7 additions and 7 deletions.
2 changes: 1 addition & 1 deletion compiled/facebook-www/JSXDEVRuntime-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -1064,7 +1064,7 @@ function elementRefGetterWithDeprecationWarning() {
*/


function ReactElement(type, key, _ref, self, source, owner, props) {
function ReactElement(type, key, _ref, self, source, owner, props, debugStack, debugTask) {
var ref;

if (enableRefAsProp) {
Expand Down
2 changes: 1 addition & 1 deletion compiled/facebook-www/JSXDEVRuntime-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,7 @@ function elementRefGetterWithDeprecationWarning() {
*/


function ReactElement(type, key, _ref, self, source, owner, props) {
function ReactElement(type, key, _ref, self, source, owner, props, debugStack, debugTask) {
var ref;

if (enableRefAsProp) {
Expand Down
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ec15267a001086deb4ab5412d3f8b7e13573d6a5
151cce37401dc2ff609701119d61a17d92fce4ab
4 changes: 2 additions & 2 deletions compiled/facebook-www/React-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ if (
) {
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
}
var ReactVersion = '19.0.0-www-classic-efefdbe4';
var ReactVersion = '19.0.0-www-classic-95a9e838';

// Re-export dynamic flags from the www version.
var dynamicFeatureFlags = require('ReactFeatureFlags');
Expand Down Expand Up @@ -1344,7 +1344,7 @@ function elementRefGetterWithDeprecationWarning() {
*/


function ReactElement(type, key, _ref, self, source, owner, props) {
function ReactElement(type, key, _ref, self, source, owner, props, debugStack, debugTask) {
var ref;

if (enableRefAsProp) {
Expand Down
4 changes: 2 additions & 2 deletions compiled/facebook-www/React-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ if (
) {
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
}
var ReactVersion = '19.0.0-www-modern-a366d2dc';
var ReactVersion = '19.0.0-www-modern-199d23a7';

// Re-export dynamic flags from the www version.
var dynamicFeatureFlags = require('ReactFeatureFlags');
Expand Down Expand Up @@ -1347,7 +1347,7 @@ function elementRefGetterWithDeprecationWarning() {
*/


function ReactElement(type, key, _ref, self, source, owner, props) {
function ReactElement(type, key, _ref, self, source, owner, props, debugStack, debugTask) {
var ref;

if (enableRefAsProp) {
Expand Down

0 comments on commit 732aef7

Please sign in to comment.