Commit Graph
19463 Commits
Author SHA1 Message Date
Joe Savona 8e8ae96478 Support empty catch clause
Adds support for empty catch clauses in a try/catch. We add a new block kind 
`catch` which prevents the empty catch block from being merged with other types 
of blocks, preserving the block structure within the HIR and allowing us to 
reconstruct the empty catch.
2023-10-04 12:24:19 -07:00
Andrew Clark 44d40a077a Remove prefix from formState option (#27460)
`useFormState` is now in canary.
2023-10-04 15:17:37 -04:00
Andrew Clark bfefb22842 Upgrade Server Actions to canary (#27459)
Upgrades the stability of Server Actions from experimental to canary.

- Turns on enableAsyncActions and enableFormActions
- Removes "experimental_" prefix from useOptimistic, useFormStatus, and
useFormState
2023-10-04 14:51:36 -04:00
Mofei Zhang 1ec1a0ceb8 [fix] JSXElement identifiers now included in lambda capture deps
--- 

`gatherCapturedDeps` previously did not visit JSXElements, so Forget did not 
read any local JSX identifiers as dependencies (in lambdas)
2023-10-04 14:01:34 -04:00
Mofei Zhang 3f8831fb5c [snap][QoL] Pragma for implicit debug mode
--- 

Implements popular feature request ✨ per feedback from a majority of snap users. 

**Add `@debug` to the first line of your `testfilter.txt` file to opt into 
implicit debug mode**, in which debug logging is enabled anytime filter mode is 
on + only one fixture file is found. 

- live edits to testfilter.txt are reflected in watch mode, so you can add / 
remove `@debug` to `testfilter.txt` in the middle of a watch session 

- I personally don't use debug mode all the time (I often a single file filtered 
+ a lot of console log traces), but it should be easy to add `@debug` to the top 
of `testfilter.txt` and leave it there forever.
2023-10-04 14:01:33 -04:00
Mofei Zhang 05d620521b [ez] missed TODO bailout for holey arrays
--- 

Missed this when adding #2114 (only found when running thousands of fb 
components through forget and noticing that holey arrays still fail)
2023-10-04 14:01:33 -04:00
Andrew Clark 88d56b8e81 Warn if optimistic state is updated outside of a transition (#27454)
### Based on #27453 

If optimistic state is updated, and there's no startTransition on the
stack, there are two likely scenarios.

One possibility is that the optimistic update is triggered by a regular
event handler (e.g. `onSubmit`) instead of an action. This is a mistake
and we will warn.

The other possibility is the optimistic update is inside an async
action, but after an `await`. In this case, we can make it "just work"
by associating the optimistic update with the pending async action.

Technically it's possible that the optimistic update is unrelated to the
pending action, but we don't have a way of knowing this for sure because
browsers currently do not provide a way to track async scope. (The
AsyncContext proposal, if it lands, will solve this in the future.)
However, this is no different than the problem of unrelated transitions
being grouped together — it's not wrong per se, but it's not ideal.

Once AsyncContext starts landing in browsers, we will provide better
warnings in development for these cases.
2023-10-04 10:57:55 -04:00
Pieter De Baets bd6891742c Remove unreferenced react-native-host-hooks from flow (#27457)
## Summary

These modules are no longer referenced in the React codebase. We should
remove them to limit the API surface area between React and React
Native.

## How did you test this change?

`yarn flow native && yarn flow fabric`
2023-10-04 15:57:43 +01:00
Joe Savona 91dab8e19c [be] Dont copy default config values if key is present 2023-10-03 16:57:00 -07:00
Joe Savona b96dd26be1 [qol] Improve printing of ReactiveFunction
This is a (hopefully) better approach at printing ReactiveFunction. The nesting 
wasn't always clear in the previous version, this should help. See playground to 
experiment.
2023-10-03 16:56:59 -07:00
Joe Savona 870a378ebb [be] Rename EnvironmentConfig type after refactor 2023-10-03 15:01:55 -07:00
Joe Savona 44a6a602a7 [be] Update feature defaults to reflect usage
Updates feature flag default values to match the flags we have turned on 
internally for some time w/o issues.
2023-10-03 14:49:58 -07:00
Joe Savona 34890556ec [be] Improve feature flags setup
Updates `Environment` to store all feature flags on a single `config` object. We 
now also define an object with all the default config values, and use this to 
populate defaults for any missing values in the user-provided config.
2023-10-03 13:58:05 -07:00
Andrew Clark 85c2b519b5 Fix: Optimistic update does not get reset (#27453)
I found a bug where if an optimistic update causes a component to
rerender, and there are no other state updates during that render, React
bails out without applying the update.

Whenever a hook detects a change, we must mark the component as dirty to
prevent a bailout. We check for changes by comparing the new state to
`hook.memoizedState`. However, when implementing optimistic state
rebasing, I incorrectly reset `hook.memoizedState` to the incoming base
state, even though I only needed to reset `hook.baseState`. This was
just a mistake on my part.

This wasn't caught by the existing tests because usually when the
optimistic state changes, there's also some other state that marks the
component as dirty in the same render.

I fixed the bug and added a regression test.
2023-10-03 15:01:40 -04:00
Joe Savona 9fc5e55b56 [be] Access all features flags through Environment
Most of our feature flags are accessed via the `Environment`, but a few cases 
have slipped in where we look at the `config` object directly. The problem is 
that the config object doesn't set defaults, so the check is effectively 
encoding what the default is. 

This PR moves to always accessing flags off of the environment, and adds a few 
flags that weren't yet defined there.
2023-10-03 11:46:33 -07:00
Sathya Gunasekaran 0f97ea4c71 [sprout] Make mutate incrementally attempt to add property
Previously, we stored a global count variable that was updated every time we 
added a property to the `arg` object. This was added to prevent collisions, and 
make sure we do actually mutate the object. 

But the count value was shared by the forget compiled and uncompiled versions, 
so the same object mutated in either versions would result in having different 
properties leading to potential test failures. 

Instead, let's make count local and attempt to incrementally mutate the object 
with different keys.
2023-10-04 12:36:04 +05:30
Sathya Gunasekaran 3aaf8be25c [hir] Use a stable identity for undefined value
InferReferenceEffects uses object identity to merge states, which breaks when we 
create a new object to model `undefined`. 

Two value objects representing `undefined` are not equal due to referential 
equality. 

Instead, let's use a singleton to represent `undefined` value.
2023-10-04 12:11:35 +05:30
Joe Savona b7a14ecc8d Detect unknown hooks on React namespace
Handles an edge-case from earlier in the stack. When looking up a property on a 
shape, if the property is defined we return it. But if it isn't defined, and the 
property name is a hook, we treat it like a default custom hook.
2023-10-03 09:00:11 -07:00
Joe Savona a2dd376820 Drop useMemo/useCallback when called via React namespace
Teaches `DropUseMemo` (which also handles useCallback) to also transform the 
methodcall case.
2023-10-03 08:47:07 -07:00
Joe Savona 989eaafa43 Support hooks/methods on the React namespace
Allows using hooks/methods off of the `React` namespace, for example 
`React.useState(sathya)`. Thanks to the previous PR we correctly handle things 
like validation of hooks called via propertyload syntax. The main change here is 
to teach the compiler about the `React` namespace. This is a bit of a hack since 
we treat it as a global, but we're transforming React code so this seems 
reasonable (?). 

There are a few additional touch-ups which I'll do in subsequent PRs to make 
review easier. For example, we need to teach our useMemo/useCallback flattening 
logic to also handle the case of `React.useMemo()` etc.
2023-10-03 08:47:06 -07:00
Joe Savona b3d5e92667 Support/validate hooks called as methods
Hooks can be called via method call syntax, eg `Foo.useBar(sathya)`. This PR 
teaches the compiler about this form of hooks for things like flattening scopes 
with hooks, validating conditional hooks, etc. 

Note that we still disallow calls on the React namespace, so things like 
`React.useState()` continue to error. That's the next PR in the stack!
2023-10-03 08:47:05 -07:00
Joe Savona 216696dd88 Allow context values to be mutated in callbacks
Adds a new type for representing context values, which is transitive. So 
`useContext(a).b.c` also gets inferred as a context type. This allows us to 
refine our inference, and allow passing callbacks that modify context where a 
"frozen" lambda is exepcted.
2023-10-03 08:47:04 -07:00
Joe Savona 41cb3c722e Fix duplicate declaration from MergeConsecutiveScopes
This is a distilled version of the duplicate declaration @mofeiZ and I saw when 
trying to sync latest Forget internally, plus a fix to avoid the duplicate 
instruction.
2023-10-02 16:40:33 -07:00
Joe Savona 4779fa0f81 Wrap MergeConsecutiveScopes in a feature flag
I should have done this originally, but figured if there were issues on the sync 
we could just do it then. Here we are!
2023-10-02 15:46:04 -07:00
Joe Savona e54e2e6ee4 Distinguish JSXText/StringLiteral during codegen
Fixes an issue with incorrect spacing where spaces were getting dropped, despite 
an explicit `{" "}` in the input. The issue is that we didn't maintain JSXText 
all the way through compilation. BuildHIR distinguishes string literals (such as 
the above, inside an expressioncontainer) from JSXText, and we propagate this 
distinction all the way through to codegen. 

But then codegen stores temporary values as `t.Expression` nodes, which means we 
have to convert the JSXText nodes to StringLiteral and we lose the distinction. 
This PR updates codegen to save temporaries as `t.Expression | t.JSXText` so 
that we can preserve the difference. In most places we just coerce the value to 
an expression, but the code for emitting JSX child items looks at the raw value 
so it can distinguish them. JSXText is emitted as-is, while StringLiterals are 
always wrapped in an expression container. 

See the new test case which demonstrates the expression being preserved.
2023-10-02 14:58:05 -07:00
Sathya Gunasekaran e49a62c15d [hir] Do not memoize object methods separately
Object methods must not be cached independently, so this PR flattens the 
reactive scope to prevent memoization. 

In the future, we can combine the FlattenScopesWithObjectMethods and 
FlattedScopesWithHooks passes by making them more modular. But this works for 
now.
2023-10-02 20:22:49 +01:00
Sophie Alpert db69f95e48 Fix checkbox and radio hydration (#27401)
Fixes whatever part of https://github.com/facebook/react/issues/26876
and https://github.com/vercel/next.js/issues/49499 that
https://github.com/facebook/react/pull/27394 didn't fix, probably.

From manual tests I believe this behavior brings us back to parity with
latest stable release (18.2.0). It's awkward that we keep the user's
state even for controlled inputs, so the DOM is out of sync with React
state.

Previously the .defaultChecked assignment done in updateInput() was
changing the actual checkedness because the dirty flag wasn't getting
set, meaning that hydrating could change which radio button is checked,
even in the absence of user interaction! Now we go back to always
detaching again.
2023-10-02 11:38:44 -07:00
Sophie Alpert 4f4c52a3c8 Fix controlled radios, maybe for real this time (#27443)
Fixes #26876 for real?

In 18.2.0 (last stable), we set .checked unconditionally:


https://github.com/facebook/react/blob/v18.2.0/packages/react-dom/src/client/ReactDOMInput.js#L129-L135

This is important because if we are updating two radios' checkedness
from (false, true) to (true, false), we need to make sure that
input2.checked is explicitly set to false, even though setting
`input1.checked = true` already unchecks input2.

I think this fix is not complete because there is no guarantee that all
the inputs rerender at the same time? Hence the TODO. But in practice
they usually would and I _think_ this is comparable to what we had
before.

Also treating function and symbol as false like we used to and like we
do on initial mount.
2023-10-02 11:38:13 -07:00
Sathya Gunasekaran f8996476ff [hir] Wire up object method lowering and codegen
Object methods are lowered to functions and added to ObjectExpression. The 
codegen is interesting because we shouldn't emit code that lowers the object 
method into a separate statement and then stores it into an object expression. 

An shorthand object method has different semantics than an object method using 
the function syntax, so we need to preserve the shorthand object method syntax 
in the generated code. 

To do this, we don't immediately generate an AST node for the ObjectMethod but 
instead store it in a side table during codegen. Only when emitting code for an 
ObjectExpression, we lookup this side table and emit the object method inline in 
the body.
2023-10-02 19:26:00 +01:00
Sebastian Markbåge 843ec07021 [Flight] Taint APIs (#27445)
This lets a registered object or value be "tainted", which we block from
crossing the serialization boundary. It's only allowed to stay
in-memory.

This is an extra layer of protection against mistakes of transferring
data from a data access layer to a client. It doesn't provide perfect
protection, because it doesn't trace through derived values and
substrings. So it shouldn't be used as the only security layer but more
layers are better.

`taintObjectReference` is for specific object instances, not any nested
objects or values inside that object. It's useful to avoid specific
objects from getting passed as is. It ensures that you don't
accidentally leak values in a specific context. It can be for security
reasons like tokens, privacy reasons like personal data or performance
reasons like avoiding passing large objects over the wire.

It might be privacy violation to leak the age of a specific user, but
the number itself isn't blocked in any other context. As soon as the
value is extracted and passed specifically without the object, it can
therefore leak.

`taintUniqueValue` is useful for high entropy values such as hashes,
tokens or crypto keys that are very unique values. In that case it can
be useful to taint the actual primitive values themselves. These can be
encoded as a string, bigint or typed array. We don't currently check for
this value in a substring or inside other typed arrays.

Since values can be created from different sources they don't just
follow garbage collection. In this case an additional object must be
provided that defines the life time of this value for how long it should
be blocked. It can be `globalThis` for essentially forever, but that
risks leaking memory for ever when you're dealing with dynamic values
like reading a token from a database. So in that case the idea is that
you pass the object that might end up in cache.

A request is the only thing that is expected to do any work. The
principle is that you can derive values from out of a tainted
entry during a request. Including stashing it in a per request cache.
What you can't do is store a derived value in a global module level
cache. At least not without also tainting the object.
2023-10-02 13:55:39 -04:00
Rubén Norte 54baa7997c Add feature flag to use microtasks in the React Native Fabric renderer (#27364)
## Summary

This is part of an effort to align the event loop in React Native with
its behavior on the Web. In this case, we're going to test enabling
microtasks in React Native (Fabric) and we need React to schedule work
using microtasks if available there. This just adds a feature flag to
configure that behavior at runtime.

## How did you test this change?

* Reviewed the generated code, which looks ok.
* Did a manual sync of this PR to Meta's internal infra and tested it
with my changes to enable microtasks in RN/Hermes.
2023-10-02 17:12:35 +01:00
Sebastian MarkbågeandJosh Story a6ed60a8eb [Fizz] Don't double replay elements when it's the postpone point (#27440)
The `resumeElement` function wasn't actually doing the correct thing
because it was resuming the element itself but what the child slot means
is that we're supposed to resume in the direct child of the element.
This is difficult to check for since it's all the possible branches that
the element can render into, so instead we just check this in
renderNode. It means the hottest path always checks the task which is
unfortunate.

And also, resuming using the correct nextSegmentId.

Fixes two bugs surfaced by this test.

---------

Co-authored-by: Josh Story <josh.c.story@gmail.com>
2023-09-29 18:24:38 -04:00
Sebastian Markbåge c7ba8c0988 Enforce that the "react-server" build of "react" is used (#27436)
I do this by simply renaming the secret export name in the "subset"
bundle and this renamed version is what the FlightServer uses.

This requires us to be more diligent about always using the correct
instance of "react" in our tests so there's a bunch of clean up for
that.
2023-09-29 18:24:05 -04:00
Andrew Clark d900fadbf9 Bugfix: Selective hydration triggers false update loop error (#27439)
This adds a regression test and fix for a case where a sync update
triggers selective hydration, which then leads to a "Maximum update
depth exceeded" error, even though there was only a single update. This
happens when a single sync update flows into many sibling dehydrated
Suspense boundaries.

This fix is, if a commit was the result of selective hydration, we
should not increment the nested update count, because those renders
conceptually are not updates.

Ideally, they wouldn't even be in a separate commit — we should be able
to hydrate a tree and apply an update on top of it within the same
render phase. We could do this once we implement resumable context
stacks.
2023-09-29 14:29:35 -04:00
Andrew Clark 13d0225c7d Pass lanes as argument to performSyncWorkOnRoot (#26763)
`performSyncWorkOnRoot` has only a single caller, and the caller already
computes the next lanes (`getNextLanes`) before deciding to call the
function. So we can pass them as an argument instead of computing the
lanes again.

There was already a TODO comment about this, but it was mostly perf
related. However, @rickhanlonii noticed a discrepancy where the inner
`getNextLanes` call was not being passed the current work-in- progress
lanes. Usually this shouldn't matter because there should never be
work-in-progress sync work; it should finish immediately. There is one
case I'm aware of where we exit the work loop without finishing a sync
render, which is selective hydration, but even then it should switch to
the sync hydration lane, not the normal sync lane. So something else is
probably going on. I suspect it might be related to the
`enableUnifiedSyncLane` experiment.

This is likely related to a regression found internally at Meta. We're
still working on getting a proper regression test; I can come up with a
contrived one but I'm not confident it'll be the same as the actual
regression until we get a better repro.
2023-09-28 16:29:32 -04:00
Andrew Clark 62512bafc6 Test fix: Add missing warning assertion (#27434)
Adds a missing test assertion for Server Context deprecation.

The PR that added this warning was based on an older revision than the
PR that added the test.
2023-09-28 12:21:11 -04:00
Josh Story 5b5665342b Restore "publish react-server-dom-turbopack to canary channels (#27427)" (#27433)
Reverts facebook/react#27428 which restores facebook/react#27427

this will allow the publish script to publish
`react-server-dom-turbopack`
2023-09-28 09:13:49 -07:00
Sebastian MarkbågeandJosh Story 1ebedbec2b Add Server Context deprecation warning (#27424)
As agreed, we're removing Server Context. This was never official
documented.

We've found that it's not that useful in practice. Often the better
options are:

- Read things off the url or global scope like params or cookies.
- Use the module system for global dependency injection.
- Use `React.cache()` to dedupe multiple things instead of computing
once and passing down.

There are still legit use cases for Server Context but you have to be
very careful not to pass any large data, so in generally we recommend
against it anyway.

Yes, prop drilling is annoying but it's not impossible for the cases
this is needed. I would personally always pick it over Server Context
anyway.

Semantically, Server Context also blocks object deduping due to how it
plays out with Server Components that can't be deduped. This is much
more important feature.

Since it's already in canary along with the rest of RSC, we're adding a
warning for a few versions before removing completely to help migration.

---------

Co-authored-by: Josh Story <josh.c.story@gmail.com>
2023-09-28 11:03:19 -04:00
Josh Story 34de2986df Revert "publish react-server-dom-turbopack to canary channels (#27427) (#27428)
Build fails because the package does not exist. I've added the package
to npm but we need the publishing account to be a collaborator. I'm
reverting until we get that sorted so we don't block canaries
2023-09-27 16:17:45 -07:00
Joe Savona b61cfc01a5 Support rest params
Adds support for lowering rest element parameters to spreads. We eagerly create 
a temporary, similar to the approach for destructuring. In theory we could do 
something more optimal if you have a `...foo` (rest element where the argument 
is an Identifier) but it doesn't seem worth optimizing yet.
2023-09-27 14:04:02 -04:00
Joe Savona be7f98d584 Scaffolding for rest params
Updates HIR to represent rest element function parameters. We don't construct 
these yet.
2023-09-27 14:04:02 -04:00
Joe Savona 687ad02e43 Dont merge if scope output is not guaranteed to change
As noted earlier in the stack and in chat, there are some cases where the output 
of a reactive scope is not guaranteed to change just because its inputs did. 
Consider a function `foo(x) { return x < 10 }`. If x was 0 and changes to 1, the 
result of `foo(x)` won't change. 

For code such as `[foo(x)]`, then, merging the scope for `t0 = foo(x)` and `t1 = 
[t0]` into a single `t1 = [foo(x)]` could cause us to invalidate `t1` 
unnecessarily. For example, x changing from 0 to 1 would allocate a new array of 
`[true]` even though the value didn't meaningfully change. This is the second 
category of merging, where we merge scopes A and B if the outputs of A are the 
inputs to B. 

With this change, we only do this type of merge if the outputs of the first 
scope are known to invalidate whenever the input does. We're conservative about 
this, and only consider function expressions, arrays, object, and jsx to always 
invalidate. Function calls _may_ invalidate, but as w the `foo()` example here 
they also may not. 

Note that this is purely about optimization and not correctness. We could always 
merge in this case (per earlier in the stack) but that might invalidate more 
often than we would like.
2023-09-27 14:04:01 -04:00
Joe Savona 66841aa9ac Prune declarations not used after merging
This is the optimization mentioned in #2113. When we merge scopes, often the 
declarations from the first scope become unnecessary, since those values are 
only consumed by the subsequent, now-merged scope. There's no point emitting 
those values as outputs of the merged scope since no one can consume them. 

Thanks to the logic in #2116 we now know the last place each identifier is used. 
We use that again here, to prune declarations that aren't used past the end of 
the merged scope. This is a pretty dramatic win on cache slots used.
2023-09-27 14:04:00 -04:00
Joe Savona d1355179d7 Improved check for intermediate values for merged scopes
We can't merge scopes if the intervening instructions produce values that are 
used later on. This PR improves the mechanism for detecting this case: first we 
build up a mapping of the last time (max instruction id) each identifier is 
used. Then when we're about to merge scopes we check if all the intervening 
lvalues are last used at or before the scope. If so that means it's safe to 
merge. 

I can't think of any edge cases that are problematic in the old behavior, but 
this version is more trivially correct and should allow us to extend to other 
types of instructions more easily.
2023-09-27 14:04:00 -04:00
Joe Savona 959c6685be Merge consecutive reactive scopes that invalidate together
This is something we've wanted to do for a while, and which @sophiebits also 
brought up. The idea is to merge consecutive reactive scopes that will always 
invalidate together. There are two cases of this: 

* Both scopes have the exact same inputs 

* Or the inputs of the second scope are the outputs of the first 

In both these cases it's pure overhead to keep the scopes separate. In the first 
case where both scopes have the same inputs, merging allows us to check the 
inputs once instead of 2+ times. In the second case, we know that the second 
scope will invalidate when the first does so it's wasteful to recheck the 
outputs of the first scope for changes. 

This is already cutting down on memoization quite a bit in the fixtures. Note 
that there's an additional optimization we can do after merging the second 
category, which is to remove the first scope's declarations if they were only 
referenced by the second scope. I'll add that in a follow-up.
2023-09-27 14:03:59 -04:00
Josh Story 25fc9eaf8c publish react-server-dom-turbopack to canary channels (#27427)
When I added react-server-dom-turbopack I failed to mark the package as
stable so it did not get published with the canary release. This adds
the package to the stable set so it will be published correctly
2023-09-27 10:39:04 -07:00
Josh Story f81c0f1ed9 [Flight] Implement react-server-dom-turbopack (#27315)
stacked on #27314 

Turbopack requires a different module loading strategy than Webpack and
as such this PR implements a new package `react-server-dom-turbopack`
which largely follows the `react-server-dom-webpack` but is implemented
for this new bundler
2023-09-27 10:03:57 -07:00
Josh Story 701ac2e572 [Flight][Float] Preinitialize module imports during SSR (#27314)
Currently when we SSR a Flight response we do not emit any resources for
module imports. This means that when the client hydrates it won't have
already loaded the necessary scripts to satisfy the Imports defined in
the Flight payload which will lead to a delay in hydration completing.

This change updates `react-server-dom-webpack` and
`react-server-dom-esm` to emit async script tags in the head when we
encounter a modules in the flight response.

To support this we need some additional server configuration. We need to
know the path prefix for chunk loading and whether the chunks will load
with CORS or not (and if so with what configuration).
2023-09-27 09:53:31 -07:00
Josh Story 49eba01930 [Fizz][Float] Refactor Resources (#27400)
Refactors Resources to have a more compact and memory efficient
struture. Resources generally are just an Array of chunks. A resource is
flushed when it's chunks is length zero. A resource does not have any
other state.

Stylesheets and Style tags are different and have been modeled as a unit
as a StyleQueue. This object stores the style rules to flush as part of
style tags using precedence as well as all the stylesheets associated
with the precedence. Stylesheets still need to track state because it
affects how we issue boundary completion instructions. Additionally
stylesheets encode chunks lazily because we may never write them as html
if they are discovered late.

The preload props transfer is now maximally compact (only stores the
props we would ever actually adopt) and only stores props for
stylesheets and scripts because other preloads have no resource
counterpart to adopt props into. The ResumableState maps that track
which keys have been observed are being overloaded. Previously if a key
was found it meant that a resource already exists (either in this render
or in a prior prerender). Now we discriminate between null and object
values. If map value is null we can assume the resource exists but if it
is an object that represents a prior preload for that resource and the
resource must still be constructed.
2023-09-26 09:59:39 -07:00
Sebastian Markbåge bff6be8eb1 [Fizz] Track postpones in fallbacks (#27421)
This fixes so that you can postpone in a fallback. This postpones the
parent boundary. I track the fallbacks in a separate replay node so that
when we resume, we can replay the fallback itself and finish the
fallback and then possibly later the content. By doing this we also
ensure we don't complete the parent too early since now it has a render
task on it.

There is one case that this surfaces that isn't limited to
prerender/resume but also render/hydrateRoot. I left todos in the tests
for this.

If you postpone in a fallback, and suspend in the content but eventually
don't postpone in the content then we should be able to just skip
postponing since the content rendered and we no longer need the
fallback. This is a bit of a weird edge case though since fallbacks are
supposed to be very minimal.

This happens because in both cases the fallback starts rendering early
as soon as the content suspends. This also ensures that the parent
doesn't complete early by increasing the blocking tasks. Unfortunately,
the fallback will irreversibly postpone its parent boundary as soon as
it hits a postpone.

When you suspend, the same thing happens but we typically deal with this
by doing a "soft" abort on the fallback since we don't need it anymore
which unblocks the parent boundary. We can't do that with postpone right
now though since it's considered a terminal state.

I think I'll just leave this as is for now since it's an edge case but
it's an annoying exception in the model. Makes me feel I haven't quite
nailed it just yet.
2023-09-25 19:02:25 -04:00