We typecheck the reconciler against each one of our host configs.
`yarn flow dom` checks it against the DOM renderer, `yarn flow native`
checks it against the native renderer, and so on.
To do this, we generate separate flowconfig files.
Currently, there is no root-level host config, so running Flow
directly via `flow` CLI doesn't work. You have to use the `yarn flow`
command and pick a specific renderer.
A drawback of this design, though, is that our Flow setup doesn't work
with other tooling. Namely, editor integrations.
I think the intent of this was maybe so you don't run Flow against a
renderer than you intended, see it pass, and wrongly think you fixed
all the errors. However, since they all run in CI, I don't think this
is a big deal. In practice, I nearly always run Flow against the same
renderer (DOM), and I'm guessing that's the most common workflow for
others, too.
So what I've done in this commit is modify the `yarn flow` command to
copy the generated `.flowconfig` file into the root directory. The
editor integration will pick this up and show Flow information for
whatever was the last renderer you checked.
Everything else about the setup is the same, and all the renderers will
continue to be checked by CI.
* Migrate conditional tests to gate pragma
I searched through the codebase for this pattern:
```js
describe('test suite', () => {
if (!__EXPERIMENTAL__) { // or some other condition
test("empty test so Jest doesn't complain", () => {});
return;
}
// Unless we're in experimental mode, none of the tests in this block
// will run.
})
```
and converted them to the `@gate` pragma instead.
The reason this pattern isn't preferred is because you end up disabling
more tests than you need to.
* Add flag for www release channels
Using a heuristic where I check a flag that is known to only be enabled
in www. I left a TODO to instead set the release channel explicitly in
each test config.
* Add pragma for feature testing: @gate
The `@gate` pragma declares under which conditions a test is expected to
pass.
If the gate condition passes, then the test runs normally (same as if
there were no pragma).
If the conditional fails, then the test runs and is *expected to fail*.
An alternative to `it.experimental` and similar proposals.
Examples
--------
Basic:
```js
// @gate enableBlocksAPI
test('passes only if Blocks API is available', () => {/*...*/})
```
Negation:
```js
// @gate !disableLegacyContext
test('depends on a deprecated feature', () => {/*...*/})
```
Multiple flags:
```js
// @gate enableNewReconciler
// @gate experimental
test('needs both useEvent and Blocks', () => {/*...*/})
```
Logical operators (yes, I'm sorry):
```js
// @gate experimental && (enableNewReconciler || disableSchedulerTimeoutBasedOnReactExpirationTime)
test('concurrent mode, doesn\'t work in old fork unless Scheduler timeout flag is disabled', () => {/*...*/})
```
Strings, and comparion operators
No use case yet but I figure eventually we'd use this to gate on
different release channels:
```js
// @gate channel === "experimental" || channel === "modern"
test('works in OSS experimental or www modern', () => {/*...*/})
```
How does it work?
I'm guessing those last two examples might be controversial. Supporting
those cases did require implementing a mini-parser.
The output of the transform is very straightforward, though.
Input:
```js
// @gate a && (b || c)
test('some test', () => {/*...*/})
```
Output:
```js
_test_gate(ctx => ctx.a && (ctx.b || ctx.c, 'some test'), () => {/*...*/});
```
It also works with `it`, `it.only`, and `fit`. It leaves `it.skip` and
`xit` alone because those tests are disabled anyway.
`_test_gate` is a global method that I set up in our Jest config. It
works about the same as the existing `it.experimental` helper.
The context (`ctx`) argument is whatever we want it to be. I set it up
so that it throws if you try to access a flag that doesn't exist. I also
added some shortcuts for common gating conditions, like `old`
and `new`:
```js
// @gate experimental
test('experimental feature', () => {/*...*/})
// @gate new
test('only passes in new reconciler', () => {/*...*/})
```
Why implement this as a pragma instead of a runtime API?
- Doesn't require monkey patching built-in Jest methods. Instead it
compiles to a runtime function that composes Jest's API.
- Will be easy to upgrade if Jest ever overhauls their API or we switch
to a different testing framework (unlikely but who knows).
- It feels lightweight so hopefully people won't feel gross using it.
For example, adding or removing a gate pragma will never affect the
indentation of the test, unlike if you wrapped the test in a
conditional block.
* Compatibility with console error/warning tracking
We patch console.error and console.warning to track unexpected calls
in our tests. If there's an unexpected call, we usually throw inside
an `afterEach` hook. However, that's too late for tests that we
expect to fail, because our `_test_gate` runtime can't capture the
error. So I also check for unexpected calls inside `_test_gate`.
* Move test flags to dedicated file
Added some instructions for how the flags are set up and how to
use them.
* Add dynamic version of gate API
Receives same flags as the pragma.
If we ever decide to revert the pragma, we can codemod them to use
this instead.
* Implement component stack extraction hack
* Normalize errors in tests
This drops the requirement to include owner to pass the test.
* Special case tests
* Add destructuring to force toObject which throws before the side-effects
This ensures that we don't double call yieldValue or advanceTime in tests.
Ideally we could use empty destructuring but ES lint doesn't like it.
* Cache the result in DEV
In DEV it's somewhat likely that we'll see many logs that add component
stacks. This could be slow so we cache the results of previous components.
* Fixture
* Add Reflect to lint
* Log if out of range.
* Fix special case when the function call throws in V8
In V8 we need to ignore the first line. Normally we would never get there
because the stacks would differ before that, but the stacks are the same if
we end up throwing at the same place as the control.
Modules that belong to one fork should not import modules that belong to
the other fork.
Helps make sure you correctly update imports when syncing changes across
implementations.
Also could help protect against code size regressions that might happen
if one of the forks accidentally depends on two copies of the same
module.
Adds command `yarn merge-fork`.
```sh
yarn merge-fork --base-dir=packages/react-reconciler/src ReactFiberWorkLoop
```
This will take all the changes in `ReactFiberWorkLoop.new.js` and apply
them to `ReactFiberWorkLoop.old.js`.
You can merge multiple modules at a time:
```sh
yarn merge-fork \
--base-dir=packages/react-reconciler/src \
ReactFiberWorkLoop \
ReactFiberBeginWork \
ReactFiberCompleteWork \
ReactFiberCommitWork
```
You can provide explicit "old" and "new" file names. This only works
for one module at a time:
```sh
yarn merge-fork \
--base-dir=packages/react-reconciler/src \
--old=ReactFiberExpirationTime.js \
--new=ReactFiberLane.js
```
The default is to merge changes from the new module to the old one. To
merge changes in the opposite direction, use `--reverse`.
```sh
yarn merge-fork \
--reverse \
--base-dir=packages/react-reconciler/src \
ReactFiberWorkLoop
```
By default, the changes are compared to HEAD. You can use `--base-ref`
to compare to any rev. For example, while working on a PR, you might
make multiple commits to the new fork before you're ready to backport
them to the old one. In that case, you want to compare to the merge
base of your PR branch:
```sh
yarn merge-fork \
--base-ref=$(git merge-base HEAD origin/master)
--base-dir=packages/react-reconciler/src \
ReactFiberWorkLoop
```
Some of our internal reconciler types have leaked into other packages.
Usually, these types are treated as opaque; we don't read and write
to its fields. This is good.
However, the type is often passed back to a reconciler method. For
example, React DOM creates a FiberRoot with `createContainer`, then
passes that root to `updateContainer`. It doesn't do anything with the
root except pass it through, but because `updateContainer` expects a
full FiberRoot, React DOM is still coupled to all its fields.
I don't know if there's an idiomatic way to handle this in Flow. Opaque
types are simlar, but those only work within a single file. AFAIK,
there's no way to use a package as the boundary for opaqueness.
The immediate problem this presents is that the reconciler refactor will
involve changes to our internal data structures. I don't want to have to
fork every single package that happens to pass through a Fiber or
FiberRoot, or access any one of its fields. So my current plan is to
share the same Flow type across both forks. The shared type will be a
superset of each implementation's type, e.g. Fiber will have both an
`expirationTime` field and a `lanes` field. The implementations will
diverge, but not the types.
To do this, I lifted the type definitions into a separate module.
* Add useOpaqueIdentifier Hook
We currently use unique IDs in a lot of places. Examples are:
* `<label for="ID">`
* `aria-labelledby`
This can cause some issues:
1. If we server side render and then hydrate, this could cause an
hydration ID mismatch
2. If we server side render one part of the page and client side
render another part of the page, the ID for one part could be
different than the ID for another part even though they are
supposed to be the same
3. If we conditionally render something with an ID , this might also
cause an ID mismatch because the ID will be different on other
parts of the page
This PR creates a new hook `useUniqueId` that generates a different
unique ID based on whether the hook was called on the server or client.
If the hook is called during hydration, it generates an opaque object
that will rerender the hook so that the IDs match.
Co-authored-by: Andrew Clark <git@andrewclark.io>
* Lazily initialize models as they're read intead of eagerly when received
This ensures that we don't spend CPU cycles processing models that we're
not going to end up rendering.
This model will also allow us to suspend during this initialization if
data is not yet available to satisfy the model.
* Refactoring carefully to ensure bundles still compile to something optimal
* Remove generic from Response
The root model needs to be cast at one point or another same as othe
chunks. So we can parameterize the read instead of the whole Response.
* Read roots from the 0 key of the map
The special case to read the root isn't worth the field and code.
* Store response on each Chunk
Instead of storing it on the data tuple which is kind of dynamic, we store
it on each Chunk. This uses more memory. Especially compared to just making
initializeBlock a closure, but overall is simpler.
* Rename private fields to underscores
Response objects are exposed.
* Encode server components as delayed references
This allows us to stream in server components one after another over the
wire. It also allows parallelizing their fetches and resuming only the
server component instead of the whole parent block.
This doesn't yet allow us to suspend deeper while waiting on this content
because we don't have "lazy elements".
* Eject CRA from Flight
We need to eject because we're going to add a custom Webpack Plugin.
We can undo this once the plugin has upstreamed into CRA.
* Add Webpack plugin build
I call this entry point "webpack-plugin" instead of "plugin" even though
this is a webpack specific package. That's because there will also be a
Node.js plugin to do the server transform.
* Add Flight Webpack plugin to fixture
* Rm UMD builds
* Transform classes
* Rename webpack-plugin to plugin
This avoids the double webpack name. We're going to reuse this for both
server and client.
* Remove /dist/ UMD builds
We publish UMDs to npm (and we're considering stopping even that).
This means we'll stop publishing to http://react.zpao.com/builds/master/latest/
* Update fixture paths
* Enable prefer-const rule
Stylistically I don't like this but Closure Compiler takes advantage of
this information.
* Auto-fix lints
* Manually fix the remaining callsites
* Upgrade Closure
There are newer versions but they don't yet have corresponding releases
of google-closure-compiler-osx.
* Configure build
* Refactor ReactSymbols a bit
Provides a little better output.
This is equivalent to the jsx-runtime in that this is what the compiled
output on the server is supposed to target.
It's really just the same code for all the different Flights, but they
have different types in their arguments so each one gets their own entry
point. We might use this to add runtime warnings per entry point.
Unlike the client-side React.block call this doesn't provide the factory
function that curries the load function. The compiler is expected to wrap
this call in the currying factory.
* Formalize the Wakeable and Thenable types
We use two subsets of Promises throughout React APIs. This introduces
the smallest subset - Wakeable. It's the thing that you can throw to
suspend. It's something that can ping.
I also use a shared type for Thenable in the cases where we expect a value
so we can be a bit more rigid with our us of them.
* Make Chunks into Wakeables instead of using native Promises
This value is just going from here to React so we can keep it a lighter
abstraction throughout.
* Renamed thenable to wakeable in variable names
DevTools previously used the NPM events package for dispatching events. This package has an unfortunate flaw though- if a listener throws during event dispatch, no subsequent listeners are called. I've replaced that event dispatcher with my own implementation that ensures all listeners are called before it re-throws an error.
This commit replaces that event emitter with a custom implementation that calls all listeners before re-throwing an error.
This reverts commit cf0081263c.
The changes to the test code relate to changes in JSDOM that come with Jest 25:
* Several JSDOM workarounds are no longer needed.
* Several tests made assertions to match incorrect JSDOM behavior (e.g. setAttribute calls) that JSDOM has now patched to match browsers.
* https://codesandbox.io/s/resets-value-of-datetime-input-to-fix-bugs-in-ios-safari-1ppwh
* JSDOM no longer triggers default actions when dispatching click events.
* https://codesandbox.io/s/beautiful-cdn-ugn8f
* JSDOM fixed (jsdom/jsdom#2700) a bug so that calling focus() on an already focused element does not dispatch a FocusEvent.
* JSDOM now supports passive events.
* JSDOM has improved support for custom CSS properties.
* But requires jsdom/cssstyle#112 to land to support webkit prefixed properties.
* Resolve Server-side Blocks instead of Components
React elements should no longer be used to extract arbitrary data but only
for prerendering trees.
Blocks are used to create asynchronous behavior.
* Resolve Blocks in the Client
* Tests
* Bug fix relay JSON traversal
It's supposed to pass the original object and not the new one.
* Lint
* Move Noop Module Test Helpers to top level entry points
This module has shared state. It needs to be external from builds.
This lets us test the built versions of the Noop renderer.
This is a really old one and all callers have since been codemodded away
anyway because of problems.
This file is not really as rigorously maintained as the official Flow types
but has a few more specifics. However, the inconsistency causes problems
when you try to pass files typed using the built-in Flow typing for React
and mix it with these.
We just happen to get away with it because we compile out the types. If we
didn't we would hit those problems by even using these in our renderers.
* Rename lower case isomorphic default exports modules to upper case named exports
We're somewhat inconsistent here between e.g. ReactLazy and memo.
Let's pick one.
This also moves the responder, fundamental, scope creators from shared
since they're isomorphic and same as the other creators.
* Move some files that are specific to the react-reconciler from shared
Individual renderers are allowed to deep require into the reconciler.
* Move files specific to react-dom from shared
react-interactions is right now dom specific (it wasn't before) so we can
type check it together with other dom stuff. Avoids the need for
a shared ReactDOMTypes to be checked by RN for example.
* Move ReactWorkTags to the reconciler
* Move createPortal to export from reconciler
Otherwise Noop can't access it since it's not allowed deep requires.
Follow ups from https://github.com/facebook/react/pull/18334
I also introduced the concept of a module reference on the client too.
We don't need this for webpack so that gets compiled out but we need it
for www. Similarly I also need a difference between preload and load.
Error codes don't need to be pulled from CI anymore because the ones
in source are already expected to match the build output.
I noticed this when running the 16.13.1 release. Patch releases are cut
with the commit used to build the previous release as a base. So the
publish script accidentally reverted the changes that had landed to
the error codes file since then.
* Move unsubscribe fork to EventListener
That way we can statically compile out more of these indirections.
* Don't use the EventListener fork for Modern WWW builds
This PR adds the jsx-runtime and jsx-dev-runtime modules for the JSX Babel Plugin. WWW still relies on jsx/jsxs/jsxDEV from the "react" module, so once we refactor the code to point to the runtime modules we will remove jsx/jsxs/jsxDEV from the "react" module.
* ReactFiberReconciler -> ReactFiberReconciler.old
* Set up infra for react-reconciler fork
We're planning to land some significant refactors of the reconciler.
We want to be able to gradually roll out the new implementation side-by-
side with the existing one. So we'll create a short lived fork of the
react-reconciler package. Once the new implementation has stabilized,
we'll delete the old implementation and promote the new one.
This means, for as long as the fork exists, we'll need to maintain two
separate implementations. This sounds painful, but since the forks will
still be largely the same, most changes will not require two separate
implementations. In practice, you'll implement the change in the old
fork and then copy paste it to the new one.
This commit only sets up the build and testing infrastructure. It does
not actually fork any modules. I'll do that in subsequent PRs.
The forked version of the reconciler will be used to build a special
version of React DOM. I've called this build ReactDOMForked. It's only
built for www; there's no open source version.
The new reconciler is disabled by default. It's enabled in the
`yarn test-www-variant` command. The reconciler fork isn't really
related to the "variant" feature of the www builds, but I'm piggy
backing on that concept to avoid having to add yet another
testing dimension.
useMutableSource hook
useMutableSource() enables React components to safely and efficiently read from a mutable external source in Concurrent Mode. The API will detect mutations that occur during a render to avoid tearing and it will automatically schedule updates when the source is mutated.
RFC: reactjs/rfcs#147