If a discrete render results in passive effects, we should flush them
synchronously at the end of the current task so that the result is
immediately observable. For example, if a passive effect adds an event
listener, the listener will be added before the next input.
We don't need to do this for effects that don't have discrete/sync
priority, because we assume they are not order-dependent and do not
need to be observed by external systems.
For legacy mode, we will maintain the existing behavior, since it hasn't
been reported as an issue, and we'd have to do additional work to
distinguish "legacy default sync" from "discrete sync" to prevent all
passive effects from being treated this way.
* Support nesting of startTransition and flushSync
* Unset transition before entering any special execution contexts
Co-authored-by: Andrew Clark <git@andrewclark.io>
* [Fast Refresh] Support callthrough HOCs
* Add a newly failing testing to demonstrate the flaw
This shows why my initial approach doesn't make sense.
* Attach signatures at every nesting level
* Sign nested memo/forwardRef too
* Add an IIFE test
This is not a case that is important for Fast Refresh, but we shouldn't change the code semantics. This case shows the transform isn't quite correct. It's wrapping the call at the wrong place.
* Find HOCs above more precisely
This fixes a false positive that was causing an IIFE to be wrapped in the wrong place, which made the wrapping unsafe.
* Be defensive against non-components being passed to setSignature
* Fix lint
* Add onError option to Flight Server
The callback is called any time an error is generated in a server component.
This allows it to be logged on a server if needed. It'll still be rethrown
on the client so it can be logged there too but in case it never reaches
the client, here's a way to make sure it doesn't get lost.
* Add fatal error handling
See removed TODO comment. This call is no longer necessary because we
use the dispatcher to track whether we're inside a transition, not the
event priority.
When called from inside an effect, flushSync cannot synchronously flush
its updates because React is already working. So we fire a warning.
However, we should still change the priority of the updates to sync so
that they flush at the end of the current task.
This only affects useEffect because updates inside useLayoutEffect (and
the rest of the commit phase, like ref callbacks) are already sync.
The sync task queue is React-specific and doesn't really have anything
to do with Scheduler. We'd keep using it even once `postTask` exists.
By separating that part out, `SchedulerWithReactIntegration` is now
just a module that re-exports the Scheduler API. So I unforked it.
When we switch to ES Modules, we can remove this re-exporting module.
* Bump version number
* Remove Scheduler indirection
I originally kept the React PriorityLevel and Scheduler PriorityLevel
types separate in case there was a versioning mismatch between the two
modules. However, it looks like we're going to keep the Scheduler module
private in the short to medium term, and longer term the public
interface will match postTask. So, I've removed the extra indirection
(the switch statements that convert between the two types).
The host tree is a cyclical structure. Leaking a single DOM node can
retain a large amount of memory. React-managed DOM nodes also point
back to a fiber tree.
Perf testing suggests that disconnecting these fields has a big memory
impact. That suggests leaks in non-React code but since it's hard to
completely eliminate those, it may still be worth the extra work to
clear these fields.
I'm moving this to level 2 to confirm whether this alone is responsible
for the memory savings, or if there are other fields that are retaining
large amounts of memory.
In our plan for removing the alternate model, DOM nodes would not be
connected to fibers, except at the root of the whole tree, which is
easy to disconnect on deletion. So in that world, we likely won't have
to do any additional work.
* Encode tables as a special insertion mode
The table modes are special in that its children can't be created outside
a table context so we need the segment container to be wrapped in a table.
* Move formatContext from Task to Segment
It works the same otherwise. It's just that this context needs to outlive
the task so that I can use it when writing the segment.
* Use template tag for placeholders and inserted dummy nodes with IDs
These can be used in any parent. At least outside IE11. Not sure yet what
happens in IE11 to these.
Not sure if these are bad for perf since they're special nodes.
* Add special wrappers around inserted segments depending on their insertion mode
* Allow the root namespace to be configured
This allows us to insert the correct wrappers when streaming into an
existing non-HTML tree.
* Add comment
This flag was meant to avoid flushing discrete updates unnecessarily,
if multiple discrete events were dispatched in response to the same
platform event.
But since we now flush all discrete events at the end of the task, in
a microtask, it no longer has any effect.
* Add failing hard-coded fuzz test
Caught in CI by the fuzz tester.
Related to expired updates.
* Use `act` in fuzz tester to flush expired work
Expired work gets scheduled in a microtask now, so we need to use `act`
to flush it.
* Add format context
* Let the Work node hold all working state for the recursive loop
Stacks are nice and all but there's a cost to maintaining each frame
both in terms of stack size usage and writing to it.
* Move current format context into work
* Synchronously render children of a Suspense boundary
We don't have to spawn work and snapshot the context. Instead we can try
to render the boundary immediately in case it works.
* Lazily create the fallback work
Instead of eagerly create the fallback work and then immediately abort it.
We can just avoid creating it if we finish synchronously.
Makes the implementation simpler. Expiration is now a special case of
entanglement.
Also fixes an issue where expired lanes weren't batched with normal
sync updates. (See deleted TODO comment in test.)
Instead of LanePriority. Internally, EventPriority is just a lane, so
this skips an extra conversion. Since EventPriority is a "public" (to
the host config) type, I was also able to remove some deep imports
of the Lane module.
This gets us most of the way to deleting the LanePriority entirely.
This is needed to avoid mutating the DOM during hydration. This *always*
adds it even when it's just text children.
We need to avoid this overhead but it's a somewhat tricky problem to solve
so we defer the optimization to later.
The event priority constants exports by the reconciler package are
meant to be used by the reconciler (host config) itself. So it doesn't
make sense to export them from a module that requires them.
To break the cycle, we can move them to a separate module and import
that. This looks like a "deep import" of an internal module, which we
try to avoid, but conceptually these are part of the public interface
of the reconciler module. So, no different than importing from the main
`react-reconciler`.
We do need to be careful about not mixing these types of imports with
implementation details. Those are the ones to really avoid.
An unintended benefit of the reconciler fork infra is that it makes
deep imports harder. Any module that we treat as "public", like this
one, needs to account for the `enableNewReconciler` flag and forward
to the correct implementation.
* Report errors to a global handler
This allows you to log errors or set things like status codes.
* Add complete callback
* onReadyToStream callback
This is typically not needed because if you want to stream when the
root is ready you can just start writing immediately.
* Rename onComplete -> onCompleteAll
* Add feature flag: enableStrongMemoryCleanup
Add a feature flag that will test doing a recursive clean of an unmount
node. This will disconnect the fiber graph making leaks less severe.
* Detach sibling pointers in old child list
When a fiber is deleted, it's still part of the previous (alternate)
parent fiber's list of children. Because children are a linked list, an
earlier sibling that's still alive will be connected to the deleted
fiber via its alternate:
live fiber
--alternate--> previous live fiber
--sibling--> deleted fiber
We can't disconnect `alternate` on nodes that haven't been deleted
yet, but we can disconnect the `sibling` and `child` pointers.
Will use this feature flag to test the memory impact.
* Combine into single enum flag
I combined `enableStrongMemoryCleanup` and `enableDetachOldChildList`
into a single enum flag. The flag has three possible values. Each level
is a superset of the previous one and performs more aggressive clean up.
We will use this to compare the memory impact of each level.
* Add Flow type to new host config method
* Re-use existing recursive clean up path
We already have a recursive loop that visits every deleted fiber. We
can re-use that one for clean up instead of adding another one.
Co-authored-by: Andrew Clark <git@andrewclark.io>
* Use identifierPrefix to avoid conflicts within the same response
identifierPrefix as an option exists to avoid useOpaqueIdentifier conflicting
when different renders are used within one HTML response.
This lets this be configured for the DOM renderer specifically since it's DOM
specific whether they will conflict across trees or not.
* Add test for using multiple containers in one HTML document
We use SyncLane everywhere we used to use InputDiscreteLane or
InputDiscreteHydrationLane. So we can delete them now, along with their
associated lane priority levels.
Discrete event hydration doesn't need to be interruptible, since
there's nothing higher priority than discrete events. So we can use
SyncLane instead of a special hydration lane.