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

DevTools: Rely on sourcemaps to compute hook name of built-in hooks in newer versions #28593

Merged
merged 4 commits into from
Apr 11, 2024

Conversation

eps1lon
Copy link
Collaborator

@eps1lon eps1lon commented Mar 20, 2024

Stack:

  1. DevTools: Rely on sourcemaps to compute hook name of built-in hooks in newer versions #28593 <--- You're here
  2. Devtools: Add support for useFormStatus #28413
  3. [Devtools] Ensure initial read of useFormStatus returns NotPendingTransition #28728

Summary

As a fallback, we assume the dispatcher method name is the hook name. This holds for currently released React versions.
As a follow-up, we introduce a separate field that holds the list of possible wrappers we try to match against e.g. for useFormStatus -> dispatcher.useHostTransitionStatus (see #28413)

One downside is that sourcemaps are bugged in Jest tests and browsers (but not Node.js). See #28413 (comment). I'm trying to find out what's up with that.

How did you test this change?

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Mar 20, 2024
Comment on lines -688 to +787
// may not be the primitive. Likewise the primitive can have fewer stack frames
// such as when a call to useState got inlined to use dispatcher.useState.
// may not be the primitive.
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 don't see how this can happen since the dispatcher is private. Without the "wrapper -> dispatcher" assumption I'm out of ideas on how to enable arbitrary wrapper names like useHostTransitionStatus is trying to enable.

It also sounds like it contradicts the current implementation of findPrimitiveIndex where we always assumed there are two stack frames for the dispatcher method and the public method right next to each other.

@react-sizebot
Copy link

react-sizebot commented Mar 20, 2024

Comparing: cb6dc7a...9688f14

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 = 169.31 kB 169.31 kB = 52.76 kB 52.76 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js = 171.13 kB 171.13 kB = 53.33 kB 53.33 kB
facebook-www/ReactDOM-prod.classic.js = 589.36 kB 589.36 kB = 103.73 kB 103.73 kB
facebook-www/ReactDOM-prod.modern.js = 566.39 kB 566.39 kB = 99.64 kB 99.64 kB
oss-experimental/react-debug-tools/cjs/react-debug-tools.production.min.js +6.70% 11.24 kB 12.00 kB +3.69% 3.63 kB 3.76 kB
oss-stable-semver/react-debug-tools/cjs/react-debug-tools.production.min.js +6.70% 11.24 kB 12.00 kB +3.69% 3.63 kB 3.76 kB
oss-stable/react-debug-tools/cjs/react-debug-tools.production.min.js +6.70% 11.24 kB 12.00 kB +3.69% 3.63 kB 3.76 kB
oss-experimental/react-debug-tools/cjs/react-debug-tools.development.js +4.04% 34.29 kB 35.68 kB +3.24% 8.25 kB 8.52 kB
oss-stable-semver/react-debug-tools/cjs/react-debug-tools.development.js +4.04% 34.29 kB 35.68 kB +3.24% 8.25 kB 8.52 kB
oss-stable/react-debug-tools/cjs/react-debug-tools.development.js +4.04% 34.29 kB 35.68 kB +3.24% 8.25 kB 8.52 kB
test_utils/ReactAllWarnings.js Deleted 64.58 kB 0.00 kB Deleted 16.14 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-debug-tools/cjs/react-debug-tools.production.min.js +6.70% 11.24 kB 12.00 kB +3.69% 3.63 kB 3.76 kB
oss-stable-semver/react-debug-tools/cjs/react-debug-tools.production.min.js +6.70% 11.24 kB 12.00 kB +3.69% 3.63 kB 3.76 kB
oss-stable/react-debug-tools/cjs/react-debug-tools.production.min.js +6.70% 11.24 kB 12.00 kB +3.69% 3.63 kB 3.76 kB
oss-experimental/react-debug-tools/cjs/react-debug-tools.development.js +4.04% 34.29 kB 35.68 kB +3.24% 8.25 kB 8.52 kB
oss-stable-semver/react-debug-tools/cjs/react-debug-tools.development.js +4.04% 34.29 kB 35.68 kB +3.24% 8.25 kB 8.52 kB
oss-stable/react-debug-tools/cjs/react-debug-tools.development.js +4.04% 34.29 kB 35.68 kB +3.24% 8.25 kB 8.52 kB
test_utils/ReactAllWarnings.js Deleted 64.58 kB 0.00 kB Deleted 16.14 kB 0.00 kB

Generated by 🚫 dangerJS against 9688f14

@markerikson
Copy link
Contributor

Nice to see the sourcemaps work I did coming in handy! :)

Comment on lines +818 to +900
return [
hookStack[primitiveIndex - 1],
hookStack.slice(primitiveIndex, rootIndex - 1),
];
Copy link
Contributor

Choose a reason for hiding this comment

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

Where did this come from? Looking at the code, you are trying to use the functionName from the primitive frame, but we didn't have this before, is it a regression for some cases?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's the core of this change. Instead of computing the hook name from the hook log, we compute it from the parent of the dispatcher call since we always have PublicReactPackage.useSomeHook calling into dispatcher.useSomePotentiallyDifferentlyNamedHook.

This doesn't really do much in this commit since the name of the public hook always matches the dispatcher call. But for useFormStatus (which we support in #28413), we have ReactDOM.useFormStatus calling into useHostTransitionStatus. Since useHostTransitionStatus can be called by a hook from a different renderer that isn't called useFormStatus (see #26719), we have to compute the name for devtools from the parent frame i.e. hookStack[primitiveIndex - 1].

But we can only use the the frame from the public method with sourcemaps because public hooks get mangled e.g. exports.useFormStatus = ia. The stack frame of exports.useFormstatus() would read at Object.ia [as useFormStatus] which is what I saw in #28413 (comment) where sourcemaps were broken. When sourcemaps are not broken i.e. outside of Jest, we get the correct stackframe reading at Object.useFormStatus which we can parse and extract the expected hook name FormStatus.

packages/react-debug-tools/src/ReactDebugHooks.js Outdated Show resolved Hide resolved
Comment on lines 859 to 864
displayName =
parseHookName(primitiveFrame.functionName) ||
// Older versions of React do not have sourcemaps.
// In those versions there was always a 1:1 mapping between wrapper and dispatcher method.
parseHookName(hook.dispatcherMethodName);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
displayName =
parseHookName(primitiveFrame.functionName) ||
// Older versions of React do not have sourcemaps.
// In those versions there was always a 1:1 mapping between wrapper and dispatcher method.
parseHookName(hook.dispatcherMethodName);
}
displayName =
parseHookName(primitiveFrame.functionName) ||
// Older versions of React do not have sourcemaps.
// In those versions there was always a 1:1 mapping between wrapper and dispatcher method.
parseHookName(hook.dispatcherMethodName) ||
hook.primitive;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

At least we will be showing primitive name instead of Unknown, like it was before.

I've observed this with CustomHooks element in react-devtools-shell

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. This should be fixed by though: 3deebb2 (#28593). I'll get your suggestion in is as well since it's a nicer fallback.

@eps1lon eps1lon force-pushed the devtools/sourcempas-hook-names branch 4 times, most recently from 4f24518 to bc150ed Compare March 27, 2024 16:27
eps1lon and others added 3 commits April 3, 2024 14:22
As a fallback, we assume the dispatcher method name is the hook name.
This holds for currently released React versions.
As a follow-up, we introduce a separate field that holds the list of possible wrappers we try to match against e.g. for `useFormStatus` -> `dispatcher.useHostTransitionStatus`.
@eps1lon eps1lon merged commit 4f5c812 into facebook:main Apr 11, 2024
38 checks passed
@eps1lon eps1lon deleted the devtools/sourcempas-hook-names branch April 11, 2024 20:01
rickhanlonii pushed a commit that referenced this pull request Apr 11, 2024
rickhanlonii pushed a commit that referenced this pull request Apr 11, 2024
EdisonVan pushed a commit to EdisonVan/react that referenced this pull request Apr 15, 2024
github-actions bot pushed a commit that referenced this pull request Apr 15, 2024
facebook-github-bot pushed a commit to facebook/react-native that referenced this pull request Apr 19, 2024
Summary:
This sync includes the changes from:
- D56103750
- [TODO] A shim for SECRET_INTERNALS

This sync includes the following changes:
- **[b5e5ce8e0](facebook/react@b5e5ce8e0 )**: Update ReactNativeTypes for root options (part 2) ([#28857](facebook/react#28857)) //<Ricky>//
- **[da6ba53b1](facebook/react@da6ba53b1 )**: [UMD] Remove umd builds ([#28735](facebook/react#28735)) //<Josh Story>//
- **[0c245df1d](facebook/react@0c245df1d )**: Complete the typo fix ([#28856](facebook/react#28856)) //<Sebastian Silbermann>//
- **[f82051d7a](facebook/react@f82051d7a )**: console test utils fix: match entire string, not just first letter ([#28855](facebook/react#28855)) //<Andrew Clark>//
- **[4ca20fd36](facebook/react@4ca20fd36 )**: Test top level fragment inside lazy semantics ([#28852](facebook/react#28852)) //<Sebastian Markbåge>//
- **[c0cf7c696](facebook/react@c0cf7c696 )**: Promote ASYNC_ITERATOR symbol to React Symbols ([#28851](facebook/react#28851)) //<Sebastian Markbåge>//
- **[657428a9e](facebook/react@657428a9e )**: Add ReactNativeTypes for root options ([#28850](facebook/react#28850)) //<Ricky>//
- **[7909d8eab](facebook/react@7909d8eab )**: [Flight] Encode ReadableStream and AsyncIterables ([#28847](facebook/react#28847)) //<Sebastian Markbåge>//
- **[13eb61d05](facebook/react@13eb61d05 )**: Move enableUseDeferredValueInitialArg to canary ([#28818](facebook/react#28818)) //<Andrew Clark>//
- **[8afa144bd](facebook/react@8afa144bd )**: Enable flag disableClientCache ([#28846](facebook/react#28846)) //<Jan Kassens>//
- **[734956ace](facebook/react@734956ace )**: Devtools: Add support for useFormStatus ([#28413](facebook/react#28413)) //<Sebastian Silbermann>//
- **[17e920c00](facebook/react@17e920c00 )**: [Flight Reply] Encode Typed Arrays and Blobs ([#28819](facebook/react#28819)) //<Sebastian Markbåge>//
- **[0347fcd00](facebook/react@0347fcd00 )**: Add on(Caught|Uncaught|Recoverable) opts to RN ([#28836](facebook/react#28836)) //<Ricky>//
- **[c113503ad](facebook/react@c113503ad )**: Flush direct streams in Bun ([#28837](facebook/react#28837)) //<Kenta Iwasaki>//
- **[9defcd56b](facebook/react@9defcd56b )**: Remove redundant props assign ([#28829](facebook/react#28829)) //<Sebastian Silbermann>//
- **[ed4023603](facebook/react@ed4023603 )**: Fix mistaken "react-server" condition ([#28835](facebook/react#28835)) //<Sebastian Markbåge>//
- **[c8a035036](facebook/react@c8a035036 )**: [Fizz] hoistables should never flush before the preamble ([#28802](facebook/react#28802)) //<Josh Story>//
- **[4f5c812a3](facebook/react@4f5c812a3 )**: DevTools: Rely on sourcemaps to compute hook name of built-in hooks in newer versions ([#28593](facebook/react#28593)) //<Sebastian Silbermann>//
- **[435415962](facebook/react@435415962 )**: Backwards compatibility for string refs on WWW ([#28826](facebook/react#28826)) //<Jack Pope>//
- **[608edcc90](facebook/react@608edcc90 )**: [tests] add `assertConsole<method>Dev` helpers ([#28732](facebook/react#28732)) //<Ricky>//
- **[da69b6af9](facebook/react@da69b6af9 )**: ReactDOM.requestFormReset  ([#28809](facebook/react#28809)) //<Andrew Clark>//
- **[374b5d26c](facebook/react@374b5d26c )**: Scaffolding for requestFormReset API ([#28808](facebook/react#28808)) //<Andrew Clark>//
- **[41950d14a](facebook/react@41950d14a )**: Automatically reset forms after action finishes ([#28804](facebook/react#28804)) //<Andrew Clark>//
- **[dc6a7e01e](facebook/react@dc6a7e01e )**: [Float] Don't preload images inside `<noscript>` ([#28815](facebook/react#28815)) //<Josh Story>//
- **[3f947b1b4](facebook/react@3f947b1b4 )**: [tests] Assert scheduler log empty in internalAct ([#28737](facebook/react#28737)) //<Ricky>//
- **[bf09089f6](facebook/react@bf09089f6 )**: Remove Scheduler.log from ReactSuspenseFuzz-test ([#28812](facebook/react#28812)) //<Ricky>//
- **[84cb3b4cb](facebook/react@84cb3b4cb )**: Hardcode disableIEWorkarounds for www ([#28811](facebook/react#28811)) //<Ricky>//
- **[2243b40ab](facebook/react@2243b40ab )**: [tests] assertLog before act in useEffectEvent ([#28763](facebook/react#28763)) //<Ricky>//
- **[dfc64c6e3](facebook/react@dfc64c6e3 )**: [tests] assertLog before act in ReactUse ([#28762](facebook/react#28762)) //<Ricky>//
- **[42eff4bc7](facebook/react@42eff4bc7 )**: [tests] Fix assertions not flushed before act ([#28745](facebook/react#28745)) //<Ricky>//
- **[ed3c65caf](facebook/react@ed3c65caf )**: Warn if outdated JSX transform is detected ([#28781](facebook/react#28781)) //<Andrew Clark>//
- **[3f9e237a2](facebook/react@3f9e237a2 )**: Fix: Suspend while recovering from hydration error ([#28800](facebook/react#28800)) //<Andrew Clark>//
- **[7f5d25e23](facebook/react@7f5d25e23 )**: Fix cloneElement using string ref w no owner ([#28797](facebook/react#28797)) //<Joseph Savona>//
- **[bf40b0244](facebook/react@bf40b0244 )**: [Fizz] Stop publishing external-runtime to stable channel ([#28796](facebook/react#28796)) //<Josh Story>//
- **[7f93cb41c](facebook/react@7f93cb41c )**: [DOM] Infer react-server entries bundles if not explicitly configured ([#28795](facebook/react#28795)) //<Josh Story>//
- **[f61316535](facebook/react@f61316535 )**: Rename SECRET INTERNALS to `__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE` ([#28789](facebook/react#28789)) //<Sebastian Markbåge>//
- **[9644d206e](facebook/react@9644d206e )**: Soften useFormState warning ([#28788](facebook/react#28788)) //<Ricky>//
- **[c771016e1](facebook/react@c771016e1 )**: Rename The Secret Export of Server Internals ([#28786](facebook/react#28786)) //<Sebastian Markbåge>//
- **[d50323eb8](facebook/react@d50323eb8 )**: Flatten ReactSharedInternals ([#28783](facebook/react#28783)) //<Sebastian Markbåge>//
- **[f62cf8c62](facebook/react@f62cf8c62 )**: [Float] treat `props.async` in Float consistent with the rest of react-dom ([#26760](facebook/react#26760)) //<Josh Story>//
- **[dfd3d5af8](facebook/react@dfd3d5af8 )**: Add support for transition{run,start,cancel} events ([#27345](facebook/react#27345)) //<Hugo Sales>//
- **[1f8327f83](facebook/react@1f8327f83 )**: [Fiber] Use real event priority for hydration scheduling ([#28765](facebook/react#28765)) //<Josh Story>//
- **[97c90ed88](facebook/react@97c90ed88 )**: [DOM] Shrink ReactDOMCurrentDispatcher method names ([#28770](facebook/react#28770)) //<Josh Story>//
- **[9007fdc8f](facebook/react@9007fdc8f )**: [DOM] Shrink ReactDOMSharedInternals source representation ([#28771](facebook/react#28771)) //<Josh Story>//
- **[14f50ad15](facebook/react@14f50ad15 )**: [Flight] Allow lazily resolving outlined models ([#28780](facebook/react#28780)) //<Sebastian Markbåge>//
- **[4c12339ce](facebook/react@4c12339ce )**: [DOM] move `flushSync` out of the reconciler ([#28500](facebook/react#28500)) //<Josh Story>//
- **[8e1462e8c](facebook/react@8e1462e8c )**: [Fiber] Move updatePriority tracking to renderers ([#28751](facebook/react#28751)) //<Josh Story>//
- **[0b3b8a6a3](facebook/react@0b3b8a6a3 )**: jsx: Remove unnecessary hasOwnProperty check ([#28775](facebook/react#28775)) //<Andrew Clark>//
- **[2acfb7b60](facebook/react@2acfb7b60 )**: [Flight] Support FormData from Server to Client ([#28754](facebook/react#28754)) //<Sebastian Markbåge>//
- **[d1547defe](facebook/react@d1547defe )**: Fast JSX: Don't clone props object ([#28768](facebook/react#28768)) //<Andrew Clark>//
- **[bfd8da807](facebook/react@bfd8da807 )**: Make class prop resolution faster ([#28766](facebook/react#28766)) //<Andrew Clark>//
- **[cbb6f2b54](facebook/react@cbb6f2b54 )**: [Flight] Support Blobs from Server to Client ([#28755](facebook/react#28755)) //<Sebastian Markbåge>//
- **[f33a6b69c](facebook/react@f33a6b69c )**: Track Owner for Server Components in DEV ([#28753](facebook/react#28753)) //<Sebastian Markbåge>//
- **[e3ebcd54b](facebook/react@e3ebcd54b )**: Move string ref coercion to JSX runtime ([#28473](facebook/react#28473)) //<Andrew Clark>//
- **[fd0da3eef](facebook/react@fd0da3eef )**: Remove _owner field from JSX elements in prod if string refs are disabled ([#28739](facebook/react#28739)) //<Sebastian Markbåge>//

Changelog:
[General][Changed] - React Native sync for revisions 48b4ecc...b5e5ce8

jest_e2e[run_all_tests]
bypass-github-export-checks

Reviewed By: kassens

Differential Revision: D56251607

fbshipit-source-id: e16db2fa101fc7ed1e009158c76388206beabd5f
hoxyq added a commit that referenced this pull request May 30, 2024
…29652)

Partially reverts #28593.

While rolling out RDT 5.2.0, I've observed some issues on React Native
side: hooks inspection for some complex hook trees, like in
AnimatedView, were broken. After some debugging, I've noticed a
difference between what is in frame's source.

The difference is in the top-most frame, where with V8 it will correctly
pick up the `Type` as `Proxy` in `hookStack`, but for Hermes it will be
`Object`. This means that for React Native this top most frame is
skipped, since sources are identical.

Here I am reverting back to the previous logic, where we check each
frame if its a part of the wrapper, but also updated `isReactWrapper`
function to have an explicit case for `useFormStatus` support.
github-actions bot pushed a commit that referenced this pull request May 30, 2024
…29652)

Partially reverts #28593.

While rolling out RDT 5.2.0, I've observed some issues on React Native
side: hooks inspection for some complex hook trees, like in
AnimatedView, were broken. After some debugging, I've noticed a
difference between what is in frame's source.

The difference is in the top-most frame, where with V8 it will correctly
pick up the `Type` as `Proxy` in `hookStack`, but for Hermes it will be
`Object`. This means that for React Native this top most frame is
skipped, since sources are identical.

Here I am reverting back to the previous logic, where we check each
frame if its a part of the wrapper, but also updated `isReactWrapper`
function to have an explicit case for `useFormStatus` support.

DiffTrain build for commit fb61a1b.
github-actions bot pushed a commit that referenced this pull request May 30, 2024
…29652)

Partially reverts #28593.

While rolling out RDT 5.2.0, I've observed some issues on React Native
side: hooks inspection for some complex hook trees, like in
AnimatedView, were broken. After some debugging, I've noticed a
difference between what is in frame's source.

The difference is in the top-most frame, where with V8 it will correctly
pick up the `Type` as `Proxy` in `hookStack`, but for Hermes it will be
`Object`. This means that for React Native this top most frame is
skipped, since sources are identical.

Here I am reverting back to the previous logic, where we check each
frame if its a part of the wrapper, but also updated `isReactWrapper`
function to have an explicit case for `useFormStatus` support.

DiffTrain build for [fb61a1b](fb61a1b)
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 React 19
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants