## Summary
Follow up from https://github.com/facebook/react/pull/22010.
As suggested by @motiz88, update the way the react sources metadata is stored within the fb sources metadata. Specifically, instead of `x_facebook_sources` directly containing a hook map in the second position of the metadata tuple for a given source, it contains the react sources metadata itself, which is also a tuple of react sources metadata for a given source, and which contains the hook map in the first position. This way the react sources metadata tuple can be extended to contain more react-specific metadata without taking up more positions in the top-level facebook sources metadata.
As part of this change:
- Adds more precise Flow types, mostly borrowed from Metro
- Fixes the facebook sources field name (we were using `x_fb_sources` but it should be `x_facebook_sources`
## Test Plan
- yarn flow
- yarn test
- yarn test-build-devtools
## Summary
Adds support for statically extracting names for hook calls from source code, and extending source maps with that information so that DevTools does not have to directly parse source code at runtime, which will speed up the Named Hooks feature and allow it to be enabled by default.
Specifically, this PR includes the following parts:
- [x] Adding logic to statically extract relevant hook names from the parsed source code (i.e. the babel ast). Note that this logic differs slightly from the existing logic in that the existing logic also uses runtime information from DevTools (such as whether given hooks are a custom hook) to extract names for hooks, whereas this code is meant to run entirely at build time, so it does not rely on that information.
- [x] Generating an encoded "hook map", which encodes the information about a hooks *original* source location, and it's corresponding name. This "hook map" will be used to generate extended source maps, included tentatively under an extra `x_react_hook_map` field. The map itself is formatted and encoded in a very similar way as how the `names` and `mappings` fields of a standard source map are encoded ( = Base64 VLQ delta coding representing offsets into a string array), and how the "function map" in Metro is encoded, as suggested in #21782. Note that this initial version uses a very basic format, and we are not implementing our own custom encoding, but reusing the `encode` function from `sourcemap-codec`.
- [x] Updating the logic in `parseHookNames` to check if the source maps have been extended with the hook map information, and if so use that information to extract the hook names without loading the original source code. In this PR we are manually generating extended source maps in our tests in order to test that this functionality works as expected, even though we are not actually generating the extended source maps in production.
The second stage of this work, which will likely need to occur outside this repo, is to update bundlers such as Metro to use these new primitives to actually generate source maps that DevTools can use.
### Follow-ups
- Enable named hooks by default when extended source maps are present
- Support looking up hook names when column numbers are not present in source map.
- Measure performance improvement of using extended source maps (manual testing suggests ~4 to 5x faster)
- Update relevant bundlers to generate extended source maps.
## Test Plan
- yarn flow
- Tests still pass
- yarn test
- yarn test-build-devtools
- Named hooks still work on manual test of browser extension on a few different apps (code sandbox, create-react-app, facebook).
- For new functionality:
- New tests for statically extracting hook names.
- New tests for using extended source maps to look up hook names at runtime.
* Show soft errors when a text string or number is supplied as a child instead of throwing an error
* bring __DEV__ check first so that things inside get removed in prod.
* fix lint
* Scheduling Profiler: Updated instructions to mentioned v18+ requirement
* Moved long-event warning to post processing
This lets us rule out non-React work or React work that started before the event and finished quickly during the event.
Also added unit tests for this warning and the various cases.
* Moved long-event warning to post processing
This lets us rule out non-React work or React work that started before the event and finished quickly during the event.
Also added unit tests for this warning and the various cases.
* Updated nested update warning text
* Udpate warning about suspending outside of a transition
Handle edge case where component suspends before the first commit (and label metadata) has been logged.
Add unit tests.
* Fixed logic error in getBatchRange() with minStartTime
* PR feedback: Combined a conditional statement
## Summary
This commit fixes an issue where DevTools would currently not correctly extract the hook names for a hook call when the hook call was nested under *another* hook call, e.g.:
```javascript
function Component() {
const InnerComponent = useMemo(() => () => {
const [state, setState] = useState(0);
return state;
});
return null;
};
```
Although it seems pretty rare to encounter this case in actual product code, DevTools wasn't handling it correctly:
**Expected Names:**
- `InnerComponent` for the `useMemo()` call.
- `state` for the `useState()` call.
**Actual**
- `InnerComponent` for the `useMemo()` call.
- `InnerComponent` for the `useState()` call.
The reason that we were extracting the name for the nested hook call incorrectly is that the `checkNodeLocation` function (which attempts to check if the location of the hook matches the location in the original source code), was too "lenient" and would return a match even if the start lines of the locations didn't match.
Specifically, for our example, it would consider that the location of the outer hook call "matched" the location of the inner hook call (even though they were on different lines), and would then return the wrong hook name.
### Fix
The fix in this commit is to update the `checkNodeLocation` function to more strictly check for matching start lines. The assumption here is that if 2 locations are on different starting lines, they can't possibly correspond to the same hook call.
## Test Plan
- yarn flow
- Tests still pass
- yarn test
- yarn test-build-devtools
- new regression tests added
- named hooks still work on manual test of browser extension on a few different apps (code sandbox, create-react-app, internally).



* Re-add old Fabric Offscreen impl behind flag
There's a chance that #21960 will affect layout in a way that we don't
expect, so I'm adding back the old implementation so we can toggle the
feature with a flag.
The flag should read from the ReactNativeFeatureFlags shim so that we
can change it at runtime. I'll do that separately.
* Import dynamic RN flags from external module
Internal feature flags that we wish to control with a GK can now be
imported from an external module, which I've called
"ReactNativeInternalFeatureFlags".
We'll need to add this module to the downstream repo.
We can't yet use this in our tests, because we don't have a test
configuration that runs against the React Native feature flags fork. We
should set up that up the same way we did for www.
Without this style property, the layout of the children is messed up.
The goal is for the container view to have no layout at all. It should
be a completely transparent wrapper, except for when we set `display:
none` to hide its contents. On the web, the equivalent (at least in the
spec) is `display: contents`.
After some initial testing, this seems to be close enough to the desired
behavior that we can ship it. We'll try rolling it out behind a flag.
## Summary
Adds a new unit test to `parseHookNames-test` which verifies that we correctly give names to hooks when they are used indirectly:
e.g.
```
const countState = useState(0);
const count = countState[0];
const setCount = countState[1];
```
Should produce `count` as the name.
## Test plan
```
yarn test
yarn test-build-devtools
yarn test-build-devtools parseHookNames
```
* Add reparenting invariant to React Noop
Fabric does not allow nodes to be reparented, so I added the equivalent
invariant to React Noop's so we can catch regressions.
This causes some tests to fail, which I'll fix in the next step.
* Fix: Use getOffscreenContainerProps
The type of these props is different per renderer. An oversight
from #21960. Unfortunately wasn't caught by Flow because fiber props
are `any`-typed.
* [Fabric] Fix reparenting in legacy Suspense mount
Fixes a weird case during legacy Suspense mount where the offscreen host
container of a tree that suspends during initial mount is recreated
instead of cloned, since there's no current fiber to clone from.
Fabric considers this a reparent even though the parent from the first
pass never committed.
Instead we can override the props from the first pass before the
container completes. It's a bit of a hack, but no more so than the rest
of the legacy root Suspense implementation — the hacks are designed
to make it usable by non-strict mode-compliant trees.
* Extract early bailout to separate function
This block is getting hard to read so I moved it to a separate function.
I'm about to refactor the logic that wraps around this path.
Ideally this early bailout path would happen before the begin phase
phase. Perhaps during reconcilation of the parent fiber's children.
* Extract state and context check to separate function
The only reason we pass `updateLanes` to some begin functions is to
check if we can perform an early bail out. But this is also available
as `current.lanes`, so we can read it from there instead.
I think the only reason we didn't do it this way originally is because
components that have two phases — error and Suspense boundaries —
use `workInProgress.lanes` to prevent a bail out, since during the
initial render there is no `current`. But we can check the `DidCapture`
flag instead, which we use elsewhere to detect the second phase.
Also highlight events that have synchronous updates inside of them. (We may want to relax this highlighting later to not warn about event handlers that are still fast enough.)
* Fix type of Offscreen props argument
Fixes an oversight from a previous refactor. The fiber that wraps
a Suspense component's children used to be a Fragment but now it's on
Offscreen fiber, so its props type has changed. There's a special
hydration path where I forgot to update this. This isn't observable
because we don't ever end up rendering this particular fiber (because
the Suspense boundary is in its fallback state) but we should fix it
anyway to avoid a potential regression in the future.
* Extract createOffscreenFromFiber logic
...into a new method called `createWorkInProgressOffscreenFiber`. Just
for symmetry with `updateWorkInProgressOffscreenFiber`. Doesn't change
any behavior.
* [Fabric] Use container node to hide/show tree
This changes how we hide and show the contents of Offscreen boundaries
in the React Fabric renderer (persistent mode), and also Suspense
boundaries which use the same feature.=
The way it used to work was that when a boundary is hidden, in the
complete phase, instead of calling the normal `cloneInstance` method
inside `appendAllChildren`, we would call a forked method called
`cloneHiddenInstance` for each of the nearest host nodes within the
subtree. This design was largely based on how it works in React DOM
(mutation mode), where instead of cloning the nearest host nodes, we
mutate their `style.display` property.
The motivation for doing it this way in React DOM was because there's no
built-in browser API for hiding a collection of DOM nodes without
affecting their layout.
In Fabric, however, there is no such limitation, so we can instead wrap
in an extra host node and apply a hidden style.
The immediate motivation for this change is that Fabric on Android has a
view pooling mechanism for instances that relies on the assumption that
a current Fiber that is cloned and replaced by a new Fiber will never
appear in a future commit. When this assumption is broken, it may cause
crashes. In the current implementation, that can indeed happen when a
node that was previously hidden is toggled back to visible. Although
this change sidesteps the issue, we may introduce in other features in
the future that would benefit from being able to revert back to an older
node without cloning it again, such as animations.
The way I've implemented this is to insert an additional HostComponent
fiber as the child of each OffscreenComponent. The extra fiber is not
ideal — the way I'd prefer to do it is to attach the host instance to
the OffscreenComponent. However, the native Fabric implementation
currently expects a 1:1 correspondence between HostComponents and host
instances, so I've deferred that optimization to a future PR to derisk
fixing the Fabric pooling crash. I left a TODO in the host config with a
description of the remaining steps, but this alone should be sufficient
to unblock.
When a new node is added to an already hidden Offscreen tree, we need
to schedule a visibility effect to hide it. Previously we would only
hide when the boundary initially switches from visible to hidden, which
meant that newly inserted nodes would be visible.
We need to do the same thing for nodes that are updated, because the
update might affect the DOM node's `style.display` property.
The implementation is to check the `subtreeFlags` for an Insertion or
Update effect.
This only affects Offscreen, not Suspense, because Suspense boundaries
cannot be updated while in their fallback (hidden) state.
And it only affects mutation mode, because in persistent mode we
implement hiding by cloning the host tree during the complete phase,
which already happens on every update.
This PR exports a new top-level API, getInspectorDataForInstance, for React Native (both development and production). Although this change adds a new export to the DEV bundle, it only impacts the production bundle for internal builds (not what's published to NPM).
LegacyHidden is a transitional API that we added to replace the old
`<div hidden={true} />` API that we used to use for pre-rendering. The
plan is to replace this with the Offscreen component, once it's ready.
The idea is that LegacyHidden has identical behavior to Offscreen except
that it doesn't change the behavior of effects. (Which is basically how
`<div hidden={true} />` worked — it prerendered the hidden content in
the background, but nothing else.) That way, while we're rolling this
out, we could toggle the feature behind a feature flag either for
performance testing or as a kill switch.
It looks like we accidentally enabled the effects flag for both
Offscreen _and_ LegacyHidden. I suppose it's a good thing that nobody
has complained yet, since we eventually do want to ship this
behavior everywhere?
But I do think we should remove it from LegacyHidden, and roll it out by
gating the component type in the downstream repo. That way if there's an
issue related to the use of LegacyHidden, we can disable that without
disabling the behavior for Suspense boundaries.
In retrospect, I might have implemented this as an unstable prop on
Offscreen instead of a completely separate type — though at the time,
Offscreen didn't exist. I originally added LegacyHidden to unblock the
Lanes refactor, so I could move the deprioritization logic out of the
HostComponent implementation.
Not a big deal since we're going to remove this soon. The implementation
is almost the same regardless: before disconnecting or reconnecting
the effects, check the fiber tag. The rest of the logic is the same.