This sets up an initial shim implementation of useSyncExternalStore,
via the use-sync-external-store package. It's designed to mimic the
behavior of the built-in API, but is backwards compatible to any version
of React that supports hooks.
I have not yet implemented the built-in API, but once it exists, the
use-sync-external-store package will always prefer that one. Library
authors can depend on the shim and trust that their users get the
correct implementation.
See https://github.com/reactwg/react-18/discussions/86 for background
on the API.
The tests I've added here are designed to run against both the shim and
built-in implementation, using our variant test flag feature. Tests that
only apply to concurrent roots will live in a separate suite.
While testing the recently-launched named hooks feature, I noticed that one of the two big performance bottlenecks is fetching the source file. This was unexpected since the source file has already been loaded by the page. (After all, DevTools is inspecting a component defined in that same file.)
To address this, I made the following changes:
- [x] Keep CPU bound work (parsing source map and AST) in a worker so it doesn't block the main thread but move I/O bound code (fetching files) to the main thread.
- [x] Inject a function into the page (as part of the content script) to fetch cached files for the extension. Communicate with this function using `eval()` (to send it messages) and `chrome.runtime.sendMessage()` to return its responses to the extension).
With the above changes in place, the extension gets cached responses from a lot of sites- but not Facebook. This seems to be due to the following:
* Facebook's response headers include [`vary: 'Origin'`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary).
* The `fetch` made from the content script does not include an `Origin` request header.
To reduce the impact of cases where we can't re-use the Network cache, this PR also makes additional changes:
- [x] Use `devtools.network.onRequestFinished` to (pre)cache resources as the page loads them. This allows us to avoid requesting a resource that's already been loaded in most cases.
- [x] In case DevTools was opened _after_ some requests were made, we also now pre-fetch (and cache in memory) source files when a component is selected (if it has hooks). If the component's hooks are later evaluated, the source map will be faster to access. (Note that in many cases, this prefetch is very fast since it is returned from the disk cache.)
With the above changes, we've reduced the time spent in `loadSourceFiles` to nearly nothing.
lunaruan commented 3 days ago •
This PR adds separate DevTools feature flag configurations for react-devtools-core. It also breaks the builds down into facebook specific and open source flags so we can experiment in React Native.
Tested yarn build:standalone, yarn build:backend, yarn build:standalone:fb, and yarn build:backend:fb and inspected the output to make sure each package used the correct feature flags (the first two use core-oss and the latter two use fb-oss.
React currently suppress console logs in StrictMode during double rendering. However, this causes a lot of confusion. This PR moves the console suppression logic from React into React Devtools. Now by default, we no longer suppress console logs. Instead, we gray out the logs in console during double render. We also add a setting in React Devtools to allow developers to hide console logs during double render if they choose.
We use an LRU for this rather than a WeakMap because of how the "no-change" optimization works.
When the frontend polls the backend for an update on the element that's currently inspected, the backend will send a "no-change" message if the element hasn't updated (rendered) since the last time it was asked. In thid case, the frontend cache should reuse the previous (cached) value. Using a WeakMap keyed on Element generally works well for this, since Elements are mutable and stable in the Store. This doens't work properly though when component filters are changed, because this will cause the Store to dump all roots and re-initialize the tree (recreating the Element objects).
So instead we key on Element ID (which is stable in this case) and use an LRU for eviction.
## Summary
Our current logic for extracting source map urls assumed that the url contained no query params (e.g. `?foo=bar`), and when extracting the url we would cut off the query params. I noticed this during internal testing, since removing the query params would cause loading source maps to fail.
This commit fixes that behavior by ensuring that our regex captures the full url, including query params.
## Test Plan
- yarn flow
- yarn test
- yarn test-build-devtools
- added new regression tests
- named hooks still work on manual test of browser extension on a few different apps (code sandbox, create-react-app, internally).
I copied the set up we use for React.
In the www-variant test job, the Scheduler `__VARIANT__` flags will be
`true`. When writing a test, we can read the value of the flag with the
`gate` pragma and method.
Note: Since these packages are currently released in lockstep, maybe we
should remove SchedulerFeatureFlags and use ReactFeatureFlags for both.
Panning horizontally via mouse wheel used to allow you to scrub over snapshot images. This was accidentally broken by a recent change. The core of the fix for this was to update `useSmartTooltip()` to remove the dependencies array so that a newly rendered tooltip is positioned even if the mouseX/mouseY coordinates don't change (as they don't when panning via wheel).
I also cleaned a couple of unrelated things up while doing this:
* Consolidated hover reset logic formerly split between `CanvasPage` and `Surface` into the `Surface` `handleInteraction()` function.
* Cleaned up redundant ref setting code in EventTooltip.
## Summary
Before this commit, if a hook returned an array the was destructured, but without assigning a variable to the first element in the array, this would produce an error. This was detected via internal testing.
This commit fixes that and adds regression tests.
## Test Plan
- yarn flow
- yarn test
- yarn test-build-devtools
- added new regression tests
- named hooks still work on manual test of browser extension on a few different apps (code sandbox, create-react-app, internally).
## Summary
Before this commit, if a hook returned an object and we declared a variable using object destructuring on the returned value, we would produce a runtime error. This was detected via internal testing.
This commit fixes that and adds regression tests.
## Test Plan
- yarn flow
- yarn test
- yarn test-build-devtools
- added new regression tests
- named hooks still work on manual test of browser extension on a few different apps (code sandbox, create-react-app, internally).
## Summary
Follow up from https://github.com/facebook/react/pull/22010.
The initial implementation of named hooks and for looking up hook name metadata in an extended source map both assumed that the source maps would always have a `sources` field available, and didn't account for the source maps in the [Index Map](https://sourcemaps.info/spec.html#h.535es3xeprgt) format, which contain a list of `sections` and don't have the `source` field available directly.
In order to properly access metadata in extended source maps, this commit:
- Adds a new `SourceMapMetadataConsumer` api, which is a fork / very similar in structure to the corresponding [consumer in Metro](2b44ec39b4/packages/metro-symbolicate/src/SourceMetadataMapConsumer.js (L56)) (as specified by @motiz88 in https://github.com/facebook/react/issues/21782.
- Updates `parseHookNames` to use this new api
## Test Plan
- yarn flow
- yarn test
- yarn test-build-devtools
- added new regression tests covering the index map format
- named hooks still work on manual test of browser extension on a few different apps (code sandbox, create-react-app, internally).
* [Scheduler] Track start of current chunk
Currently in `shouldYield`, we compare the current time to a deadline
that is pre-computed at the beginning of the current chunk.
However, since we use different deadlines depending on whether an input
event is pending, it makes more sense to track the start of the current
chunk and check how much time has elapsed since then.
Doesn't change any behavior, just refactors the logic.
* [Scheduler] Check for continuous input events
`isInputPending` supports a `includeContinuous` option. When set to
`true`, the method will check for pending continuous inputs, like
`mousemove`, in addition to discrete ones, like `click`.
We will only check for pending continuous inputs if we've blocked the
main thread for a non-neglible amount of time. If we've only blocked
the main thread for, say, a few frames, then we'll only check for
discrete inputs.
I wrote a test for this but didn't include it because we haven't yet set
up the `gate` flag infra to work with Scheduler feature flags. For now,
I ran the test locally.
* Review nits