* Lint rule for unminified errors
Add a lint rule that fails if an invariant message is not part of the
error code map.
The goal is to be more disciplined about adding and modifiying
production error codes. Error codes should be consistent across releases
even if their wording changes, for continuity in logs.
Currently, error codes are added to the error code map via an automated
script that runs right before release. The problem with this approach is
that if someone modifies an error message in the source, but neglects to
modify the corresponding message in the error code map, then the message
will be assigned a new error code, instead of reusing the existing one.
Because the error extraction script only runs before a release, people
rarely modify the error code map in practice. By moving the extraction
step to the PR stage, it forces the author to consider whether the
message should be assigned a new error code. It also allows the reviewer
to review the changes.
The trade off is that it requires more effort and context to land new
error messages, or to modify existing ones, particular for new
contributors who are not familiar with our processes.
Since we already expect users to lint their code, I would argue the
additional burden is marginal. Even if they forget to run the lint
command locally, they will get quick feedback from the CI lint job,
which typically finishes within 2-3 minutes.
* Add unreleased error messages to map
* Generate signatures for Hooks
This currently only works one level deep. For custom Hooks, we'll need to add some way to compose signatures.
* Be more resilient to plugin conflicts
This prevents a class of problems where other plugins cause our visitor to re-run.
It's a standard Babel practice, e.g.:
https://github.com/babel/babel/blob/8c7d4b55c99ff34cb9d493d452472e59b5ed1e70/packages/babel-plugin-transform-react-constant-elements/src/index.js#L85-L86
* Remove unnecessary stuff from debugging
* Include Foo.useHookName() calls into the signature
* Add an integration test for adding/removing an effect
* Add integration test for changing custom Hook order
* Include custom Hooks into the signatures
* Fix inferred names for function expressions
* Support export default hoc(Foo) when Foo is defined separately
* Add more built-in Hooks
expirationTime has already been checked if it's sync.
timeout can now be longer than 5 seconds when a suspense config is used.
We might want to adjust the heuristics but it's not internally consistent
without this.
Sometimes the status of the `build` job is not in the first page of
the `/statuses` endpoint. The combined `/status` endpoint consolidates
the entries, though, so it always appears there.
* Refactor component search to prepare for deeper traversals
* Register HOCs with intermediate results
* Register components that are used as JSX types
* Add integration test skeleton
The integration test combines testing runtime together with the Babel plugin. It's a bit harder to debug because multiple things can go wrong, but it helps us build confidence that specific scenarios work well.
* Add HOC integration test and fix conflict with JSX transform
* Infer usage from createElement too
This helps us avoid dependency on the plugin order.
* Remove outdated comments
* Wrap tests in __DEV__
* Support export default hoc(...) for anonymous functions
* Fix test indentation
* Fix comment typo
* Use named function for test as this case is more important
* Rename ReactFiberScheduler to ReactFiberWorkLoop
The scheduling part is mostly extracted out to the scheduler package.
What's remaining is mostly around the loop around each section of work.
I name it something with Work in it because it's very related to the
BeginWork, CompleteWork and UnwindWork sections.
* Extract throwException from UnwindWork
Our throwing works more like algebraic effects in that it's a separate
phase where we find a handler and we later unwind.
* [react-native] Use path-based imports instead of Haste for the RN renderer
To move React Native to standard path-based imports instead of Haste, the RN renderer that is generated from the code in this repo needs to use path-based imports as well since the generated code is vendored by RN. This commit makes it so the interface between the generated renderers and RN does not rely on Haste and instead uses a private interface explicitly defined by RN. This inverts control of the abstraction so that RN decides the internals to export rather than React deciding what to import.
On RN's side, a new module named `react-native/Libraries/ReactPrivate/ReactNativePrivateInterface` explicitly exports the modules used by the renderers in this repo. (There is also a private module for InitializeCore so that we can import it just for the side effects.) On React's side, the various renderer modules access RN internals through the explicit private interface.
The Rollup configuration becomes slimmer since the only external package is now `react-native`, and the individual modules are instead listed out in `ReactNativePrivateInterface`.
Task description: https://github.com/facebook/react-native/issues/24770
Sister RN PR (needs to land before this one): https://github.com/facebook/react-native/pull/24782
Test Plan: Ran unit tests and Flow in this repo. Generated the renderers and manually copied them over to the RN repo. Ran the RN tests and launched the RNTester app.
* Access natively defined "nativeFabricUIManager" instead of importing it
Some places in the Fabric renderers access `nativeFabricUIManager` (a natively defined global) instead of importing UIManager. While this is coupling across repos that depends on the timing of events, it is necessary until we have a way to defer top-level imports to run after `nativeFabricUIManager` is defined. So for consistency we use `nativeFabricUIManager` everywhere (see the comment in https://github.com/facebook/react/pull/15604#pullrequestreview-236842223 for more context).
The sizebot scrapes the GitHub `/statuses` endpoint to get the lastest
CircleCI build number for master, in order to fetch the bundle size
info for that build, which are stored as build artifacts. (There's gotta
be a better way to do this, but that's what we have for now.) This
updates the script to match the name of the updated CircleCI job that
generates the bundle sizes.
* Add initial Babel plugin implementation
* Register exported functions
* Fix missing declarations
Always declare them at the bottom and rely on hoisting.
* Remove unused code
* Don't pass filename to tests
I've decided for now that the plugin doesn't need filename, and it will be handled by module runtime integration instead.
* Fix bugs
* Coalesce variable declarations
Updates the CircleCI config to use the workflows features to run jobs in
parallel, instead of the `parallelism` option. This change alone doesn't
improve the overall build time much, since almost all of the total time
is spent running the Rollup script, which runs entirely sequentially.
But it does improve reporting, and should make it easier to add
additional parallel jobs in the future.
* Fix missing return pointer assignment
I found a bug using the fuzz tester that manifested as incorrect
ordering of children in the host tree, but whose root cause was a
missing `return` pointer assignment on a work-in-progress fiber.
Usually return pointers are set during reconciliation
(`reconcileChildFibers`) but this particular assignment happens inside
the custom reconciliation implementation used by Suspense boundaries.
I would not be surprised if there were similar bugs related to incorrect
return pointers. You're supposed to update the return pointer
whenever a work-in-progress fiber is created, but there's nothing in
the contract of the `createFiber` or `createWorkInProgress` function
that implies this. I propose that we update their signatures to accept
the return fiber as an argument. I will do this in a follow-up.
In this commit, I rearranged `updateSuspenseComponent` slightly so that
every call to `createWorkInProgress` or a `createFiber*` function is
immediately followed by a return pointer assignment.
I hardcoded the fuzz test case that surfaced the bug.
* Update all progressed children in list
`progressedPrimaryChild` is a list, not a single fiber. Need to iterate
through every child and update their return pointers.
* Add a stub for React Fresh Babel plugin package
* Move ReactFresh-test into ReactFresh top level directory
* Add a stub for React Fresh Runtime entry point
* Extract Fresh runtime from tests into its entry point
* Don't traverse children when hot reloading needs a remount
If we're gonna remount that tree anyway, there is no use in traversing its children beforehand.
* Add a test verifying hot reload batches updates
Otherwise there is a risk of it being super slow due to cascades.
* Add a minimal failing test for hot reload
* Set up scaffolding for React Fresh
* Consider type family when comparing elementType
Rendering an element with stale type should not cause it to remount.
We only do this for FunctionComponent tag since checking is unnecessary for classes or host components.
* Add support for forwardRef()
Initially I thought I would compare families of inner .render functions.
However, there is a corner case where this can create false positives. Such as when you forwardRef(X) the same X twice. Those are supposed to be distinct. But if we compare .render functions, we wouldn't be able to distinguish them after first reload.
It seems safer to rely on explicit registration for those. This should be easy when forwardRef() call is in the same file, and usually it would be. For cases like HOCs and style.div`...` factories that return forwardRef(), we could have the __register__ helper itself "dig deeper" and register the inner function.
* Show how forwardRef inner identity can be inferred
The __register__ implementation can read the inner identity itself.
* Add missing __DEV__ to tests
* Add support for memo() (without fixing bailouts)
This adds rudimentary support for memo components. However, we don't actually skip bailouts yet, so this is not very useful by itself alone. Tests have TODOs that we need to remove after bailout skipping is done.
* Refactor type comparison for clarity
* Hot update shouldn't re-render ancestor components unnecessarily
My code had a bug where it checked for a wrong thing in a wrong set, leading us to always re-render.
This fixes the checks so that we only schedule updates for things that were actually edited.
* Add test coverage for memo(fn, areEqual)
* Explicitly skip bailouts for hot reloading fibers
This forces even memo() with shallow comparison to re-render on hot update.
* Refactor scheduling update to reduce duplication
* Remove unused variable in test
* Don't check presence in a set while not hot reloading
* Make scheduleHotUpdate() take named arguments
* Don't keep unedited component types in the type => family map
It's unnecessary because if they haven't been edited, there's no special reconciliation logic.
* Add signatures that force remounting
Signatures let us force a remount of a type even if from React's point of view, type is the same.
A type has one current signature. If that signature changes during next hot update, all Fibers with that type should be deleted and remounted.
We do this by mutating elementType scheduling a parent.
This will be handy to force remount of mismatching Hooks, as well as failed error boundaries.
For this to fully work, we'll need to add a way to skip built-in bailouts for all Fiber types. This will be the most invasive and annoying change. I did it for HostRoot in this PR but there's more. I'll add an automated test case that catches the missing bailout bailouts.
* Support forced remounting for all component types
This teaches all parent component types to remount their child if necessary.
It also adds tests for them.
* Remount effects while preserving state for hot reloaded components
This makes sure that changes to *code* always propagate.
It can break components that aren't resilient to useEffect over-firing, but that seems like a good constraint since you might need to add a dependency later anyway, and this helps avoid coding yourself into the corner.
* Add missing __DEV__ blocks to tests
* Fix unused variables in tests
* Remove outdated TODO
* Expose scheduleHotUpdate directly
* Inline isCompatibleType
* Run one check per component for invalidating deps
This also makes the bailouts more targeted--no need to remount useEffect for a parent component of remounted fiber.
* Resolve .type early
This moves resolving to set up the right .type early instead of doing this before render.
A bit more future-proof in case we want to restructure the begin phase later.
ForwardRef is special because its type is a wrapper but it can be hot reloaded by itself.
So we have a special overload for it that reconstucts the wrapper type if needed.
* Add a Suspense todo
* Use current.type !== workInProgress.type for ignoring deps
This gets rid of one of the sets.
* Use workInProgress.type !== current.type check for force re-render
We still use a set for forced remount though.
* Use wip.type !== current.type check in more places
This also disables the remounting tests. They need a separate approach.
* Use a dedicated remount mechanism
* Add a test for offscreen trees
It has a TODO because it seems like offscreen updates are incorrectly applied too soon.
* Enable offscreen test now that it is fixed
* Fix corner cases in the new remounting mechanism
* Remount failed error boundaries on hot reload
* Fix test now that act() flushes
This test is manual so I don't actually want act here.
* Nits
* Add comments
* Add suspendIfNeeded API and a global scope to track it
Adds a "current" suspense config that gets applied to all updates scheduled
during the current scope.
I suspect we might want to add other types of configurations to the "batch"
so I called it the "batch config".
This works across renderers/roots but they won't actually necessarily go
into the same batch.
* Add the suspenseConfig to all updates created during this scope
* Compute expiration time based on the timeout of the suspense config
* Track if there was a processed suspenseConfig this render pass
We'll use this info to suspend a commit for longer when necessary.
* Mark suspended states that should be avoided as a separate flag
This lets us track which renders we want to suspend for a short time vs
a longer time if possible.
* Suspend until the full expiration time if something asked to suspend
* Reenable an old test that we can now repro again
* Suspend the commit even if it is complete if there is a minimum delay
This can be used to implement spinners that don't flicker if the data
and rendering is really fast.
* Default timeoutMs to low pri expiration if not provided
This is a required argument in the type signature but people may not
supply it and this is a user facing object.
* Rename to withSuspenseConfig and drop the default config
This allow opting out of suspending in some nested scope.
A lot of time when you use this function you'll use it with high level
helpers. Those helpers often want to accept some additional configuration
for suspense and if it should suspend at all. The easiest way is to just
have the api accept null or a suspense config and pass it through. However,
then you have to remember that calling suspendIfNeeded has a default.
It gets simpler by just saying tat you can pass the config. You can have
your own default in user space.
* Track the largest suspense config expiration separately
This ensures that if we've scheduled lower pri work that doesn't have a
suspenseConfig, we don't consider its expiration as the timeout.
* Add basic tests for functionality using each update mechanism
* Fix issue when newly created avoided boundary doesn't suspend with delay
* Add test for loading indicator with minLoadingDurationMs option
* s/flushPassiveEffects/unstable_flushWithoutYielding
a first crack at flushing the scheduler manually from inside act(). uses unstable_flushWithoutYielding(). The tests that changed, mostly replaced toFlushAndYield(...) with toHaveYielded(). For some tests that tested the state of the tree before flushing effects (but still after updates), I replaced act() with bacthedUpdates().
* ugh lint
* pass build, flushPassiveEffects returns nothing now
* pass test-fire
* flush all work (not just effects), add a compatibility mode
of note, unstable_flushWithoutYielding now returns a boolean much like flushPassiveEffects
* umd build for scheduler/unstable_mock, pass the fixture with it
* add a comment to Shcduler.umd.js for why we're exporting unstable_flushWithoutYielding
* run testsutilsact tests in both sync/concurrent modes
* augh lint
* use a feature flag for the missing mock scheduler warning
I also tried writing a test for it, but couldn't get the scheduler to unmock. included the failing test.
* Update ReactTestUtilsAct-test.js
- pass the mock scheduler warning test,
- rewrite some tests to use Scheduler.yieldValue
- structure concurrent/legacy suites neatly
* pass failing tests in batchedmode-test
* fix pretty/lint/import errors
* pass test-build
* nit: pull .create(null) out of the act() call
Uses a dynamic flag in www's test renderer build so we can condtionally
disable the passive effects bugfix. Matches the dynamic flag used in
the www React DOM build.
PR #15650 is a bugfix but it's technically a semantic change that could
cause regressions. I don't think it will be an issue, since the
previous behavior was both broken and incoherent, but out of an
abundance of caution, let's wrap it in a flag so we can easily revert
it if necessary.
* Failing test for false positive warning
* Flush passive effects before discrete events
Currently, we check for pending passive effects inside the `setState`
method before we add additional updates to the queue, in case those
pending effects also add things to the queue.
However, the `setState` method is too late, because the event that
caused the update might not have ever fired had the passive effects
flushed before we got there.
This is the same as the discrete/serial events problem. When a serial
update comes in, and there's already a pending serial update, we have to
do it before we call the user-provided event handlers. Because the event
handlers themselves might change as a result of the pending update.
This commit moves the `flushPassiveEffects` call to before the discrete
event handlers are called, and removes it from the `setState` method.
Non-discrete events will not cause passive effects to flush, which is
fine, since by definition they are not order dependent.