* Move Flight DOM to Webpack Specific Packagee
We'll have Webpack specific coupling so we need to ensure that it can be
versioned separately from various Webpack versions. We'll also have builds
for other bundlers in the future.
* Move to peerDep
* Move DOM Flight Tests
* Merge ReactFlightIntegration into ReactFlightDOM
This was an integration test. We can add to it.
* Fix fixture paths
* Regression test: Effects dropped across roots
See #17066
* [Bugfix] Passive effects loop
The bug
-------
In a multi-root app, certain passive effects (`useEffect`) are never
fired. See #17066.
The underlying problem
----------------------
The implicit contract of `flushPassiveEffects` is that, right after
calling it, there should be no pending passive effects. In the normal
case, in concurrent mode, this is true. But the current implementation
fails to account for the case where a passive effect schedules
synchronous work, which in turn schedules additional passive effects.
This led to `rootWithPendingPassiveEffects` being overwritten in the
commit phase, because an assignment that assumed it was replacing null
was actually replacing a reference to another root, which has the
consequence of dropping passive effects on that root.
The fix
-------
The fix I've chosen here is, at the beginning of the commit phase, keep
flushing passive effects in a loop until there are no more.
This doesn't not change the "public" implementation of
`flushPassiveEffects`, though it arguably should work this way, too. I
say "public" because it's only used by implementation layers on top of
React which we control: mainly, the legacy version of `act` that does
not use the mock Scheduler build. So there's probably still a bug
in that `act` implementation.
I will address `act` in a follow-up. The ideal solution is to replace
the legacy `act` with one implemented directly in the renderer, using a
special testing-only build of React DOM. Since that requires a breaking
change, we'll need an interim solution. We could make the "public" `act`
recursively flush effects in a loop, as I've done for the commit phase.
However, I think a better solution is to stop automatically flushing the
synchronous update queue at the end of `flushPassiveEffects`, and
instead require the caller to explicitly call `flushSyncUpdateQueue` (or
the equivalent) if needed. This follows the same pattern we use
internally in the work loop, which is designed to avoid factoring
hazards like the one that resulted in this bug.
* Unify fields used for createRoot warning and event system
* Warn when doing createRoot twice on the same node
* Stricter check for modern roots
* Unmark asynchronously
* Fix Flow
* [Flight] Basic Integration Test
* Just act()
* Lint
* Remove unnecessary acts
* Use Concurrent Mode
* it.experimental
* Fix prod test by advancing time
* Don't observe initial state
* Return whether to keep flowing in Host config
* Emit basic chunk based streaming in the Flight server
When something suspends a new chunk is created.
* Add reentrancy check
The WHATWG API is designed to be pulled recursively.
We should refactor to favor that approach.
* Basic streaming Suspense support on the client
* Add basic suspense in example
* Add comment describing the protocol that the server generates
* Rename ReactFlightStreamer -> ReactFlightServer
* Unify Browser/Node stream tests into one file and use the client reader
* Defer to the actual ReactDOM for HTML rendering for now
This will need to use a variant of Fizz to do inline SSR in Flight.
However, I don't want to build the whole impl right now but also don't
want to exclude the use case yet. So I outsource it to the existing
renderer. Ofc, this doesn't work with Suspense atm.
* Change demo to server
* Expose client in package.json
* Reorganize tests
We don't want unit tests but instead test how both server and clients work
together. So this merges server/client test files.
* Fill in the client implementation a bit
* Use new client in fixture
* Add Promise/Uint8Array to lint rule
I'll probably end up deleting these deps later but they're here for now.
`it.experimental` marks that a test only works in Experimental builds.
It also asserts that a test does *not* work in the stable builds. The
main benefit is that we're less likely to accidentally expose an
experimental API before we intend. It also forces us to un- mark an
experimental test once it become stable.
* Add Flight Build and Unify HostFormat Config between Flight and Fizz
* Add basic resolution of models
* Add basic Flight fixture
Demonstrates the streaming protocol.
* Rename to flight-server to distinguish from the client parts
* Add Flight Client package and entry point
* Fix fixture
Special version of Jest's `it` for experimental tests. Tests marked as
experimental will run **both** stable and experimental modes. In
experimental mode, they work the same as the normal Jest methods. In
stable mode, they are **expected to fail**. This means we can detect
when a test previously marked as experimental can be un-marked when the
feature becomes stable. It also reduces the chances that we accidentally
add experimental APIs to the stable builds before we intend.
I added corresponding methods for the focus and skip APIs:
- `fit` -> `fit.experimental`
- `it.only` -> `it.only.experimental` or `it.experimental.only`
- `xit` -> `xit.experimental`
- `it.skip` -> `it.skip.experimental` or `it.experimental.skip`
Since `it` is an alias of `test`, `test.experimental` works, too.
Without the enableSuspenseServerRenderer flag there will never be a boundary match. Also when it is enabled, there might not be a boundary match if something was conditionally rendered by mistake.
With this PR it will now client render the content of a Suspense boundary in that case and issue a DEV only hydration warning. This is the only sound semantics for this case.
Unfortunately, landing this will once again break #16938. It will be less bad though because at least it'll just work by client rendering the content instead of hydrating and issue a DEV only warning.
However, we must land this before enabling the enableSuspenseServerRenderer flag since it does this anyway.
I did notice that we special case fallback={undefined} due to our unfortunate semantics for that. So technically a workaround that works is actually setting the fallback to undefined on the server and during hydration. Then flip it on only after hydration. That could be a workaround if you want to be able to have a Suspense boundary work only after hydration for some reason.
It's kind of unfortunate but at least those semantics are internally consistent. So I added a test for that.
This changes the "default" retryTime to NoWork which schedules at Normal
pri.
Dehydrated bouundaries normally hydrate at Never priority except when they
retry where we accidentally increased them to Normal because Never was used
as the default value. This changes it so NoWork is the default.
Dehydrated boundaries however get initialized to Never as the default.
Therefore they now hydrate as Never pri unless their priority gets
increased by a forced rerender or selective hydration.
This revealed that erroring at this Never priority can cause an infinite
rerender. So I fixed that too.
I'm doing this here instead of in the downstream repo so that if the
sync diff gets reverted, it doesn't revert this, too.
Once the sync has landed, and the callers are updated in www, I will
remove this.
* 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 dormant createBatch experiment
In a hybrid React app with multiple roots, `createBatch` is used to
coordinate an update to a root with its imperative container.
We've pivoted away from multi-root, hybrid React apps for now to focus
on single root apps.
This PR removes the API from the codebase. It's possible we'll add back
some version of this feature in the future.
* Remove unused export
Adds an API to explicitly prioritize hydrating the path to a particular DOM node without relying on events to do it.
The API uses the current scheduler priority to schedule it. For the same priority, the last one wins. This allows a similar effect as continuous events. This is useful for example to hydrate based on scroll position, or prioritize components that will upgrade to client-rendered-only content.
I considered having an API that explicitly overrides the current target(s). However that makes it difficult to coordinate across components in an app.
This just hydrates one target at a time but if it is blocked on I/O we could consider increasing priority of later targets too.
Currently, when a node suspends, if its subtree contains a portal, the portal is not hidden. This hides portals in the subtree when it's not wrapped in a host component .
* Prioritize the last continuous target
This ensures that the current focus target is always hydrated first.
Slightly higher than the usual Never expiration time used for hydration.
The priority increases with each new queued item so that the last always
wins.
* Don't export the moving target
It's not useful for comparison purposes anyway.