* Tests run in experimental mode by default
For local development, you usually want experiments enabled. Unless
the release channel is set with an environment variable, tests will
run with __EXPERIMENTAL__ set to `true`.
* Remove concurrent APIs from stable builds
Those who want to try concurrent mode should use the experimental
builds instead.
I've left the `unstable_` prefixed APIs in the Facebook build so we
can continue experimenting with them internally without blessing them
for widespread use.
* Turn on SSR flags in experimental build
* Remove prefixed concurrent APIs from www build
Instead we'll use the experimental builds when syncing to www.
* Remove "canary" from internal React version string
* Don't bother including `unstable_` in error
The method names don't get stripped out of the production bundles
because they are passed as arguments to the error decoder.
Let's just always use the unprefixed APIs in the messages.
* Set up experimental builds
The experimental builds are packaged exactly like builds in the stable
release channel: same file structure, entry points, and npm package
names. The goal is to match what will eventually be released in stable
as closely as possible, but with additional features turned on.
Versioning and Releasing
------------------------
The experimental builds will be published to the same registry and
package names as the stable ones. However, they will be versioned using
a separate scheme. Instead of semver versions, experimental releases
will receive arbitrary version strings based on their content hashes.
The motivation is to thwart attempts to use a version range to match
against future experimental releases. The only way to install or depend
on an experimental release is to refer to the specific version number.
Building
--------
I did not use the existing feature flag infra to configure the
experimental builds. The reason is because feature flags are designed
to configure a single package. They're not designed to generate multiple
forks of the same package; for each set of feature flags, you must
create a separate package configuration.
Instead, I've added a new build dimension called the **release
channel**. By default, builds use the **stable** channel. There's
also an **experimental** release channel. We have the option to add more
in the future.
There are now two dimensions per artifact: build type (production,
development, or profiling), and release channel (stable or
experimental). These are separate dimensions because they are
combinatorial: there are stable and experimental production builds,
stable and experimental developmenet builds, and so on.
You can add something to an experimental build by gating on
`__EXPERIMENTAL__`, similar to how we use `__DEV__`. Anything inside
these branches will be excluded from the stable builds.
This gives us a low effort way to add experimental behavior in any
package without setting up feature flags or configuring a new package.
* Remove "Invariant Violation" from dev errors
When I made the change to compile `invariant` to throw expressions, I
left a small runtime to set the error's `name` property to "Invariant
Violation" to maintain the existing behavior.
I think we can remove it. The argument for keeping it is to preserve
continuity in error logs, but this only affects development errors,
anyway: production error messages are replaced with error codes.
* Pass prod error messages directly to constructor
Updates the `invariant` transform to pass an error message string
directly to the Error constructor, instead of mutating the
message property.
Turns this code:
```js
invariant(condition, 'A %s message that contains %s', adj, noun);
```
into this:
```js
if (!condition) {
throw Error(
__DEV__
? `A ${adj} message that contains ${noun}`
: formatProdErrorMessage(ERR_CODE, adj, noun)
);
}
```
* Rename lowPriorityWarning to lowPriorityWarningWithoutStack
This maintains parity with the other warning-like functions.
* Duplicate the toWarnDev tests to test toLowPriorityWarnDev
* Make a lowPriorityWarning version of warning.js
* Extract both variants in print-warning
Avoids parsing lowPriorityWarning.js itself as the way it forwards the
call to lowPriorityWarningWithoutStack is not analyzable.
* Add Event Replaying Infra
* Wire up Roots and Suspense boundaries, to retry events, after they commit
* Replay discrete events in order in a separate scheduler callback
* Add continuous events
These events only replay their last target if the target is not yet
hydrated. That way we don't have to wait for a previously hovered
boundary before invoking the current target.
* Enable tests from before
These tests were written with replaying in mind and now we can properly
enable them.
* Unify replaying and dispatching
* Mark system flags as a replay and pass to legacy events
That way we can check if this is a replay and therefore needs a special
case. One such special case is "mouseover" where we check the
relatedTarget.
* Eagerly listen to all replayable events
To minimize breakages in a minor, I only do this for the new root APIs
since replaying only matters there anyway. Only if hydrating.
For Flare, I have to attach all active listeners since the current
system has one DOM listener for each. In a follow up I plan on optimizing
that by only attaching one if there's at least one active listener
which would allow us to start with only passive and then upgrade.
* Desperate attempt to save bytese
* Add test for mouseover replaying
We need to check if the "relatedTarget" is mounted due to how the old
event system dispatches from the "out" event.
* Fix for nested boundaries and suspense in root container
This is a follow up to #16673 which didn't have a test because it wasn't
observable yet. This shows that it had a bug.
* Rename RESPONDER_EVENT_SYSTEM to PLUGIN_EVENT_SYSTEM
* Add trusted types to react on client side
* Implement changes according to review
* Remove support for trusted URLs, change TrustedTypes to trustedTypes
* Add support for deprecated trusted URLs
* Apply PR suggesstions
* Warn only once, remove forgotten check, put it behind a flag
* Move comment
* Fix PR comments
* Fix html toString concatenation
* Fix forgotten else branch
* Fix PR comments
* Don't invoke listeners on parent of dehydrated event target
* Move Suspense boundary check to getClosestInstanceFromNode
Now getClosestInstanceFromNode can return either a host component,
host text component or suspense component when the suspense
component is dehydrated.
We then use that to ignore events on a suspense component.
* Attach the HostRoot fiber to the DOM container
This lets us detect if an event happens on this root's subtree before it
has rendered something.
* Add todo
The approach of checking isFiberMounted answers if we might be in an
in-progress hydration but it doesn't answer which root or boundary
might be in-progress so we don't know what to wait for.
This needs some refactoring.
* Refactor isFiberMountedImpl to getNearestMountedFiber
We'll need the nearest boundary for event replaying so this prepares for
that.
This surfaced an issue that we attach Hydrating tag on the root but normally
this (and Placement) is attached on the child. This surfaced an issue
that this can lead to both Placement and Hydrating effects which is not
supported so we need to ensure that we only ever use one or the other.
* Add todo for bug I spotted
* Cache tags
* Check the ContainerInstanceKey before the InstanceKey
The container is inside the instance, so we must find it before the
instance, since otherwise we'll miss it.
This is a partial replacement for the 'Press' responder:
1. `useTap` is scoped to pointers (no keyboard support). Our current thinking is
that "responders" should be limited to working with pointers, and that they can
be combined with 'useKeyboard' in user-space. For example, we might create a
'usePress' hook in user-space that combines 'useTap' with 'useKeyboard' to react
to both pointers and keyboard interactions.
2. `useTap` cancels the gesture once the pointer moves over an element that is
not within the responder target's subtree. This differs from `usePress` (and
React Native), where the gesture remains active after the pointer exits the
target's subtree and is restarted once the pointer reenters. One of the
drawbacks with the `usePress` behavior is that it requires repeatedly measuring
DOM elements (which can cause jank) to perform hit region tests. `useTap` avoids
doing this and relies on `document.elementFromPoint` only to support the
TouchEvent fallbacks.
3. `useTap` calls `onTapUpdate` when the active gesture's state changes,
`onTapEnd` when the gesture successfully completes. and `onTapCancel` when it
fails. There is no `onTap` callback. `usePress` did not explicitly report back
when the gesture failed, and product developers were confused about the
difference between `onPress` and `onPressEnd`.
4. `useTap` explicitly separates the PointerEvent implementation from the
MouseEvent/TouchEvent fallback.
5. `useTap` has better unit test coverage . All pointer types and the fallback
environment are tested. The shape of the gesture state object is also defined
and tested.
* Refactor a bit to use less property access
* Add test for invoking an event before mount
* Add Hydration effect tag
This is equivalent to a "Placement" effect in that it's a new insertion
to the tree but it doesn't need an actual mutation.
It is only used to determine if a subtree has actually mounted yet.
* Use the Hydration flag for Roots
Previous roots had a Placement flag on them as a hack for this case but
since we have a special flag for it now, we can just use that.
* Add Flare test
Provides partial support for React.lazy() components from the existing PartialRenderer server-side renderer.
Lazy components which are already resolved (or rejected), perhaps with something like `react-ssr-prepass`, can be continued into synchronously. If they have not yet been initialized, they'll be initialized before checking, opening the possibility to exploit this capability with a babel transform. If they're pending (which will typically be the case for a just initialized async ctor) then the existing invariant continues to be thrown.
* Move dehydrated to be child of regular SuspenseComponent
We now store the comment node on SuspenseState instead and that indicates
that this SuspenseComponent is still dehydrated.
We also store a child but that is only used to represent the DOM node for
deletions and getNextHostSibling.
* Move logic from DehydratedSuspenseComponent to SuspenseComponent
Forked based on SuspenseState.dehydrated instead.
* Retry logic for dehydrated boundary
We can now simplify the logic for retrying dehydrated boundaries without
hydrating. This is becomes simply a reconciliation against the dehydrated
fragment which gets deleted, and the new children gets inserted.
* Remove dehydrated from throw
Instead we use the regular Suspense path. To save code, we attach retry
listeners in the commit phase even though technically we don't have to.
* Pop to nearest Suspense
I think this is right...?
* Popping hydration state should skip past the dehydrated instance
* Split mount from update and special case suspended second pass
The DidCapture flag isn't used consistently in the same way. We need
further refactor for this.
* Reorganize update path
If we remove the dehydration status in the first pass and then do a second
pass because we suspended, then we need to continue as if it didn't
previously suspend. Since there is no fragment child etc.
However, we must readd the deletion.
* Schedule context work on the boundary and not the child
* Warn for Suspense hydration in legacy mode
It does a two pass render that client renders the content.
* Rename DehydratedSuspenseComponent -> DehydratedFragment
This now doesn't represent a suspense boundary itself. Its parent does.
This Fiber represents the fragment around the dehydrated content.
* Refactor returns
Avoids the temporary mutable variables. I kept losing track of them.
* Add a comment explaining the type.
Placing it in the type since that's the central point as opposed to spread
out.
When React schedules a rendering task, it passes a `timeout` option
based on its expiration time. This is intended to avoid starvation
by other React updates. However, it also affects the relative priority
of React tasks and other Scheduler tasks at the same level, like
data processing.
This adds a feature flag to disable passing a `timeout` option to
Scheduler. React tasks will always append themselves to the end of
the queue, without jumping ahead of already scheduled tasks.
This does not affect the order in which React updates within a single
root are processed, but it could affect updates across multiple roots.
This also doesn't remove the expiration from Scheduler. It only means
that React tasks are not given special treatment.
* Add a feature flag to disable legacy context
* Address review
- invariant -> warning
- Make this.context and context argument actually undefined
* Increase test coverage for lifecycles
* Also disable it on the server is flag is on
* Make this.context {} when disabled, but function context is undefined
* Move checks inside
In this PR, for tests (specifically, code inside an `act()` scope), we immediately trigger work that would have otherwise required a timeout. This makes it simpler to tests loading/spinner states, and makes tests resilient to changes in React.
For some of our tests(specifically, ReactSuspenseWithNoopRenderer-test.internal), we _don't_ want fallbacks to immediately trigger, because we're testing intermediate states and such. Added a feature flag `flushSuspenseFallbacksInTests` to disable this behaviour on a per case basis.
Concurrent/Batched mode tests should always be run with a mocked scheduler (v17 or not). This PR adds a warning for the same. I'll put up a separate PR to the docs with a page detailing how to mock the scheduler.
This adds a 'SuspenseCallback' feature flag. When the property is set on
a suspense component it will be called during the commit phase with a
set of the immediate thenable for this component. This will allow user
code to build runtime tracing of the cause for a suspense boundary.