Moves writing queues to renderState.
We shouldn't need the resource tracking's value. We just need to know if
that resource has already been emitted. We can use a Set for this. To
ensure that set is directly serializable we can just use a
dictionary-like object with no value.
See individual commits for special cases.
DiffTrain build for [b775564d35](https://github.com/facebook/react/commit/b775564d35d6fe9f9acead0f380bbc86bb6bdd2c)
Originally the intension was to have React assign an ID to a user
rendered DOM node inside a `fallback` while it was loading. If there
already were an explicit `id` defined on the DOM element we would reuse
that one instead. That's why this was a DOM Config option and not just
built in to Fizz.
This became tricky since it can load late and so we'd have to transfer
it down and detect it only once it finished rendering and if there is no
DOM element it doesn't work anyway. So instead, what we do in practice
is to always use a `<template>` tag with the ID. This has the downside
of an extra useless node and shifting child CSS selectors.
Maybe we'll get around to fixing this properly but it might not be worth
it.
This PR just gets rid of the SuspenseBoundaryID concept and instead we
just use the same ID number as the root segment ID of the boundary to
refer to the boundary to simplify the implementation.
This also solves the problem that SuspenseBoundaryID isn't currently
serializable (although that's easily fixable by itself if necessary).
DiffTrain build for [2807d781a0](https://github.com/facebook/react/commit/2807d781a08db8e9873687fccc25c0f12b4fb3d4)
Based on #27385.
When we error or abort during replay, that doesn't actually error the
component that errored because that has already rendered. The error only
affects any child that is not yet completed. Therefore the error kind of
gets thrown at the resumable point.
The resumable point might be a hole in the replay path, in which case
throwing there errors the parent boundary just the same as if the replay
component errored. If the hole is inside a deeper Suspense boundary
though, then it's that Suspense boundary that gets client rendered. I.e.
the child boundary. We can still finish any siblings.
In the shell all resumable points are inside a boundary since we must
have finished the shell. Therefore if you error in the root, we just
simply just turn all incomplete boundaries into client renders.
DiffTrain build for [925c66a647](https://github.com/facebook/react/commit/925c66a6472a61e7174dabf6c697a9a320ebc170)
The idea for this check is that we shouldn't flush anything before we
flush the shell. That may or may not hold true in future formats like
RN.
It is a problem for resuming because with resuming it's possible to have
root tasks that are used for resuming but the shell was already flushed
so we can have completed boundaries before the shell has fully resumed.
What matters is whether the parent has already flushed or not.
It's not technically necessary to bail early because there won't be
anything in partialBoundaries or completedBoundaries because nothing
gets added there unless the parent has already flushed.
It's not exactly slow to have to check the length of three arrays so
it's probably not a big deal.
Flush partials in an early preamble needs further consideration
regardless.
DiffTrain build for [df061b3966](https://github.com/facebook/react/commit/df061b396644bb7c27ea76bb27ae09784ce6444b)
This forks Task into ReplayTask and RenderTask.
A RenderTask is the normal mode and it has a segment to write into.
A ReplayTask doesn't have a segment to write into because that has
already been written but instead it has a ReplayState which keeps track
of the next set of paths to follow. Once we hit a "Resume" node we
convert it into a RenderTask and continue rendering from there.
We can resume at either an Element position or a Slot position. An
Element pointing to a component doesn't mean we resume that component,
it means we resume in the child position directly below that component.
Slots are slots inside arrays.
Instead of statically forking most paths, I kept using the same path and
checked for the existence of a segment or replay state dynamically at
runtime.
However, there's still quite a bit of forking here like retryRenderTask
and retryReplayTask. Even in the new forks there's a lot of duplication
like resumeSuspenseBoundary, replaySuspenseBoundary and
renderSuspenseBoundary. There's opportunity to simplify this a bit.
DiffTrain build for [a5fc797db1](https://github.com/facebook/react/commit/a5fc797db14c6e05d4d5c4dbb22a0dd70d41f5d5)
Adds a separate entry point for the react-dom package when it's accessed
from a Server Component environment, using the "react-server" export
condition.
When you're inside a Server Component module, you won't be able to
import client-only APIs like useState. This applies to almost all React
DOM exports, except for Float ones like preload.
DiffTrain build for [2d2f2af29b](https://github.com/facebook/react/commit/2d2f2af29bc3175530a158bc90189fc6f12f7855)
The permalink option of useFormState controls which page the form is
submitted to during an MPA form submission (i.e. a submission that
happens before hydration, or when JS is disabled). If the same
useFormState appears on the resulting page, and the permalink option
matches, it should receive the form state from the submission despite
the fact that the keypaths do not match.
So the logic for whether a form state instance is considered a match is:
- Both instances must be passed the same action signature
- If a permalink is provided, the permalinks must match.
- If a permalink is not provided, the keypaths must match.
Currently, if there are multiple matching useFormStates, they will all
match and receive the form state. We should probably only match the
first one, and/or warn when this happens. I've left this as a TODO for
now, pending further discussion.
DiffTrain build for [caa716d50b](https://github.com/facebook/react/commit/caa716d50bdeef3a1ac5e3e0cfcc14f4d91f2028)
Typically we assign IDs lazily when flushing to minimize the ids we have
to assign and we try to maximize inlining.
When we prerender we could always flush into a buffer before returning
but that's not actually what we do right now. We complete rendering
before returning but we don't actually run the flush path until someone
reads the resulting stream. We assign IDs eagerly when something
postpone so that we can refer to those ids in the holes without flushing
first.
This leads to some interesting conditions that needs to consider this.
This PR also deals with rootSegmentId which is the ID of the segment
that contains the body of a Suspense boundary. The boundary needs to
know this in addition to the Suspense Boundary's ID to know how to
inject the root segment into the boundary.
Why is the suspense boundary ID and the rootSegmentID just not the same
ID? Why don't segments and suspense boundary not share ID namespace?
That's a good question and I don't really remember. It might not be a
good reason and maybe they should just be the same.
DiffTrain build for [a6e4791b11](https://github.com/facebook/react/commit/a6e4791b11816374d015eb4531a82e6cf209c7f2)
During an MPA form submission, useFormState should only reuse the form
state if same action is passed both times. (We also compare the key
paths.)
We compare the identity of the inner closure function, disregarding the
value of the bound arguments. That way you can pass an inline Server
Action closure:
```js
function FormContainer({maxLength}) {
function submitAction(prevState, formData) {
'use server'
if (formData.get('field').length > maxLength) {
return { errorMsg: 'Too many characters' };
}
// ...
}
return <Form submitAction={submitAction} />
}
```
DiffTrain build for [95c9554bc7](https://github.com/facebook/react/commit/95c9554bc72813b0ee2b028774bb7cf0482887ba)
If a Server Action is passed to useFormState, the action may be
submitted before it has hydrated. This will trigger a full page
(MPA-style) navigation. We can transfer the form state to the next page
by comparing the key path of the hook instance.
`ReactServerDOMServer.decodeFormState` is used by the server to extract
the form state from the submitted action. This value can then be passed
as an option when rendering the new page. It must be passed during both
SSR and hydration.
```js
const boundAction = await decodeAction(formData, serverManifest);
const result = await boundAction();
const formState = decodeFormState(result, formData, serverManifest);
// SSR
const response = createFromReadableStream(<App />);
const ssrStream = await renderToReadableStream(response, { formState })
// Hydration
hydrateRoot(container, <App />, { formState });
```
If the `formState` option is omitted, then the state won't be
transferred to the next page. However, it must be passed in both places,
or in neither; misconfiguring will result in a hydration mismatch.
(The `formState` option is currently prefixed with `experimental_`)
DiffTrain build for [612b2b6601](https://github.com/facebook/react/commit/612b2b6601abb844248c384d1e288bb824b180b7)
This field was not being initialized. Although the property is part of
the Flow type, the type error wasn't caught because the constructor
itself is not covered by Flow, which is unfortunate. (I assume this is
related to the dev-only componentStack property.)
DiffTrain build for [69be472c11](https://github.com/facebook/react/commit/69be472c11231056d297a7b73dd0b121905606d3)
Currently, if a component suspends, the keyPath has already been
modified to include the identity of the component itself; the path is
set before the component body is called (akin to the begin phase in
Fiber). An accidental consequence is that when the promise resolves and
component is retried, the identity gets appended to the keyPath again,
leading to a duplicate node in the path.
To address this, we should only modify contexts after any code that may
suspend. For maximum safety, this should occur as late as possible:
right before the recursive renderNode call, before the children are
rendered.
I did not add a test yet because there's no feature that currently
observes it, but I do have tests in my other WIP PR for useFormState:
#27321
DiffTrain build for [d07921eeda](https://github.com/facebook/react/commit/d07921eeda62613bfcf52ecdb66322db26393567)
There's a subtle difference if you suspend before the first array or
after. In Fiber, we don't deal with this because we just suspend the
parent and replay it if lazy() or Usable are used in its child slots. In
Fizz we try to optimize this a bit more and enable resuming inside the
component.
Semantically, it's different if you suspend/postpone before the first
child array or inside that child array. Because when you resume the
inner result might be another array and either that's part of the parent
path or part of the inner slot.
There might be more clever way of structuring this but I just use -1 to
indicate that we're not yet inside the array and is in the root child
position. If that renders an element, then that's just the same as the 0
slot.
We need to also encode this in the resuming. I called that resuming the
element or resuming the slot.
DiffTrain build for [7a3cb8f9cf](https://github.com/facebook/react/commit/7a3cb8f9cf43591afc74722ece9e3216ccc98128)
When Float was first developed the internal implementation and external
interface were the same. This is problematic for a few reasons. One, the
public interface is typed but it is also untrusted and we should not
assume that it is actually respected. Two, the internal implementations
can get called from places other than the the public interface and
having to construct an options argument that ends up being destructured
to process the request is computationally wasteful and may limit JIT
optimizations to some degree. Lastly, the wire format was not as
compressed as it could be and it was untyped.
This refactor aims to address that by separating the public interface
from the internal implementations so we can solve these challenges and
also make it easier to change Float in the future
* The internal dispatcher method preinit is now preinitStyle and
preinitScript.
* The internal dispatcher method preinitModule is now
preinitModuleScript in anticipation of different implementations for
other module types in the future.
* The wire format is explicitly typed and only includes options if they
are actually used omitting undefined and nulls.
* Some function arguments are not options even if they are optional. For
instance precedence can be null/undefined because we deafult it to
'default' however we don't cosnider this an option because it is not
something we transparently apply as props to the underlying instance.
* Fixes a problem with keying images in flight where srcset and sizes
were not being taken into account.
* Moves argument validation into the ReactDOMFloat file where it is
shared with all runtimes that expose these methods
* Fixes crossOrigin serialization to use empty string except when
'use-credentials'
DiffTrain build for [bbc8530ed7](https://github.com/facebook/react/commit/bbc8530ed7a67859583a7c990ac51cd39c7746e5)
It's possible to postpone a specific node and not using a wrapper
component. Therefore we encode the resumable slot as the index slot.
When it's a plain client component that postpones, it's encoded as the
child slot inside that component which is the one that's postponed
rather than the component itself.
Since it's possible for a child slot to suspend (e.g. React.lazy's
microtask in this case) retryTask might need to keep its index around
when it resolves.
DiffTrain build for [bb1d8d1667](https://github.com/facebook/react/commit/bb1d8d166799eb97892be6c7826179270ba283d0)
A planned feature of useFormState is that if the page load is the result
of an MPA-style form submission — i.e. a form was submitted before it
was hydrated, using Server Actions — the state of the hook should
transfer to the next page.
I haven't implemented that part yet, but as a prerequisite, we need some
way for Fizz to indicate whether a useFormState hook was rendered using
the "postback" state. That way we can do all state matching logic on the
server without having to replicate it on the client, too.
The approach here is to emit a comment node for each useFormState hook.
We use one of two comment types: `<!--F-->` for a normal useFormState
hook, and `<!--F!-->` for a hook that was rendered using the postback
state. React will read these markers during hydration. This is similar
to how we encode Suspense boundaries.
Again, the actual matching algorithm is not yet implemented — for now,
the "not matching" marker is always emitted.
We can optimize this further by not emitting any markers for a render
that is not the result of a form postback, which I'll do in subsequent
PRs.
DiffTrain build for [8b26f07a88](https://github.com/facebook/react/commit/8b26f07a883bb341c20283c0099bf5ee6f87bd1f)
img tags inside picture tags should not automatically be preloaded
because usually the img is a fallback. We will consider a more
comprehensive way of preloading picture tags which may require a
technique like using an inline script to construct the image in the
browser but for now we simply omit the preloads to avoid harming load
times by loading fallbacks.
DiffTrain build for [3566de59e2](https://github.com/facebook/react/commit/3566de59e2046e7e8478462375aaa71716f1095b)
Just moving some internal code around again.
I originally encoded what type of work using startRender vs
startPrerender. I had intended to do more forking of the work loop but
we've decided not to go with that strategy. It also turns out that
forking when we start working is actually too late because of a subtle
thing where you can call abort before work begins. Therefore it's
important that starting the work comes later.
DiffTrain build for [953cb02f6d](https://github.com/facebook/react/commit/953cb02f6de2a9f3eb52456b263e11f839584bc6)
To generate IDs for useId, we modify a context variable whenever
multiple siblings are rendered, or when a component includes a useId
hook.
When this happens, we must ensure that the context is reset properly on
unwind if something errors or suspends. When I originally implemented
this, I did this by wrapping the child's rendering with a try/finally
block. But a better way to do this is by using the non-destructive
renderNode path instead of renderNodeDestructive.
DiffTrain build for [ee7f9c9351](https://github.com/facebook/react/commit/ee7f9c9351f8902e07ceacf4234ef75e7e4ecd73)
This joins the static (prerender) builds with the server builds but
doesn't change the public entry points.
The idea of two separate bundles is that we'd have a specialized build
for Fizz just for the prerender that could do a lot more. However, in
practice the code is implemented with a dynamic check so it's in both.
It's also not a lot of code.
At the same time if you do have a set up that includes both the
prerender and the render in the same build output, this just doubles the
server bundle size for no reason.
So we might as well merge them into one build. However, I don't expose
the `prerender` from `/server`. Instead it's just exposed from the
public `/static` entry point. This leaves us with the option to go back
to separate builds later if it diverges more in the future.
DiffTrain build for [2c2bdd0ffe](https://github.com/facebook/react/commit/2c2bdd0ffe54df60201ee93b29de5cb7b93ff029)
This is basically the implementation for the prerender pass.
Instead of forking basically the whole implementation for prerender, I
just add a conditional field on the request. If it's `null` it behaves
like before. If it's non-`null` then instead of triggering client
rendered boundaries it triggers those into a "postponed" state which is
basically just a variant of "pending". It's supposed to be filled in
later.
It also builds up a serializable tree of which path can be followed to
find the holes. This is basically a reverse `KeyPath` tree.
It is unfortunate that this approach adds more code to the regular Fizz
builds but in practice. It seems like this side is not going to add much
code and we might instead just want to merge the builds so that it's
smaller when you have `prerender` and `resume` in the same bundle -
which I think will be common in practice.
This just implements the prerender side, and not the resume side, which
is why the tests have a TODO. That's in a follow up PR.
DiffTrain build for [b70a0d7022](https://github.com/facebook/react/commit/b70a0d70224ceb4e277bd8ac535a2caafa5c075a)
When the `permalink` option is passed to `useFormState`, and the form is
submitted before it has hydrated, the permalink will be used as the
target of the form action, enabling MPA-style form submissions.
(Note that submitting a form without hydration is a feature of Server
Actions; it doesn't work with regular client actions.)
It does not have any effect after the form has hydrated.
DiffTrain build for [ddff504695](https://github.com/facebook/react/commit/ddff504695f33c19e8c0792bff82bd8f8b8f7c05)
This implements useFormState in Fiber. (It does not include any
progressive enhancement features; those will be added later.)
useFormState is a hook for tracking state produced by async actions. It
has a signature similar to useReducer, but instead of a reducer, it
accepts an async action function.
```js
async function action(prevState, payload) {
// ..
}
const [state, dispatch] = useFormState(action, initialState)
```
Calling dispatch runs the async action and updates the state to the
returned value.
Async actions run before React's render cycle, so unlike reducers, they
can contain arbitrary side effects.
DiffTrain build for [456d153bb5](https://github.com/facebook/react/commit/456d153bb582798effa76c09bec2405ab2e392cf)
A transition that flows into a dehydrated boundary should not suspend if
the boundary is showing a fallback.
This is related to another issue where Fizz streams in the initial HTML
after a client navigation has already happened. That issue is not fixed
by this commit, but it does make it less likely. Need to think more
about the larger issue.
DiffTrain build for [ab31a9ed28](https://github.com/facebook/react/commit/ab31a9ed28d340172440e4b12e27d2af689249b3)
Import maps need to be emitted before any scripts or preloads so the
browser can properly locate these resources.
Unlike most scripts, importmaps are singletons meaning you can only have
one per document and they must appear before any modules are loaded or
preloaded. In the future there may be a way to dynamically add more
mappings however the proposed API for this seems likely to be a
javascript API and not an html tag.
Given the unique constraints here this PR implements React's support of
importMaps as the following
1. an `importMap` option accepting a plain object mapping module
specifier to path is accepted in any API that renders a preamble (head
content). Notably this precludes resume rendering because in resume
cases the preamble should have already been produced as part of the
prerender step.
2. the importMap is stringified and emitted as a `<script
type="importmap">...</script>` in the preamble.
3. the importMap is escaped identically to how bootstrapScriptContent is
escaped, notably, isntances of `</script>` are escaped to avoid breaking
out of the script context
Users can still render importmap tags however with Float enabled this is
rather pointless as most modules will be hoisted above the importmap
that is rendered. In practice this means the only functional way to use
import maps with React is to use this config API.
DiffTrain build for [9d4582dffd](https://github.com/facebook/react/commit/9d4582dffdea5b4dcb6a6093ea848d15423c7701)
This exposes, but does not yet implement, a new experimental API called
useFormState. It's gated behind the enableAsyncActions flag.
useFormState has a similar signature to useReducer, except instead of a
reducer it accepts an (async) action function. React will wait until the
promise resolves before updating the state:
```js
async function action(prevState, payload) {
// ..
}
const [state, dispatch] = useFormState(action, initialState)
```
When used in combination with Server Actions, it will also support
progressive enhancement — a form that is submitted before it has
hydrated will have its state transferred to the next page. However, like
the other action-related hooks, it works with fully client-driven
actions, too.
DiffTrain build for [b4cdd3e892](https://github.com/facebook/react/commit/b4cdd3e8922713f8c9817b004a0dc51be47bc5df)
This exposes a `resume()` API to go with the `prerender()` (only in
experimental). It doesn't work yet since we don't yet emit the postponed
state so not yet tested.
The main thing this does is rename ResponseState->RenderState and
Resources->ResumableState. We separated out resources into a separate
concept preemptively since it seemed like separate enough but probably
doesn't warrant being a separate concept. The result is that we have a
per RenderState in the Config which is really just temporary state and
things that must be flushed completely in the prerender. Most things
should be ResumableState.
Most options are specified in the `prerender()` and transferred into the
`resume()` but certain options that are unique per request can't be.
Notably `nonce` is special. This means that bootstrap scripts and
external runtime can't use `nonce` in this mode. They need to have a CSP
configured to deal with external scripts, but not inline.
We need to be able to restore state of things that we've already emitted
in the prerender. We could have separate snapshot/restore methods that
does this work when it happens but that means we have to explicitly do
that work. This design is trying to keep to the principle that we just
work with resumable data structures instead so that we're designing for
it with every feature. It also makes restoring faster since it's just
straight into the data structure.
This is not yet a serializable format. That can be done in a follow up.
We also need to vet that each step makes sense. Notably stylesToHoist is
a bit unclear how it'll work.
DiffTrain build for [31034b6de7](https://github.com/facebook/react/commit/31034b6de73c7cd9093e92a34e384d84c082aa4e)
renderToString is a legacy server API which used a trick to avoid having
the DOCTYPE included when rendering full documents by setting the root
formatcontext to HTML_MODE rather than ROOT_HTML_MODE. Previously this
was of little consequence but with Float the Root mode started to be
used for things like determining if we could flush hoistable elements
yet. In issue #27177 we see that hoisted elements can appear before the
<html> tag when using a legacy API `renderToString`.
This change exports a DOCTYPE from FizzConfigDOM and FizzConfigDOMLegacy
respectively, using an empty chunk in the legacy case. The only runtime
perf cost here is that for legacy APIs there is an extra empty chunk to
write when rendering a top level <html> tag which is trivial enough
Fixes#27177
DiffTrain build for [86198b9231](https://github.com/facebook/react/commit/86198b923199224b60533952b636348bb0484a6d)
This fixes the regression test added in the previous commit. The
"Suspensey commit" implementation relies on the
`shouldRemainOnPreviousScreen` function to determine whether to 1)
suspend the commit 2) activate a parent fallback and schedule a retry.
The issue was that we were sometimes attempting option 2 even when there
was no parent fallback.
Part of the reason this bug landed is due to how `throwException` is
structured. In the case of Suspensey commits, we pass a special "noop"
thenable to `throwException` as a way to trigger the Suspense path. This
special thenable must never have a listener attached to it. This is not
a great way to structure the logic, it's just a consequence of how the
code evolved over time. We should refactor it into multiple functions so
we can trigger a fallback directly without having to check the type. In
the meantime, I added an internal warning to help detect similar
mistakes in the future.
DiffTrain build for [dd480ef923](https://github.com/facebook/react/commit/dd480ef923930c8906a02664b01bcdea50707b5d)
Tracks the currently executing parent path of a task, using the name of
the component and the key or index.
This can be used to uniquely identify an instance of a component between
requests - assuming nothing in the parents has changed. Even if it has
changed, if things are properly keyed, it should still line up.
It's not used yet but we'll need this for two separate features so
should land this so we can stack on top.
Can be passed to `JSON.stringify(...)` to generate a unique key.
DiffTrain build for [98f3f14d2e](https://github.com/facebook/react/commit/98f3f14d2e06fb785103296318204cd154d5d0ed)
Stacked on #27224
### Implements `ReactDOM.preloadModule()`
`preloadModule` is a function to preload modules of various types.
Similar to `preload` this is useful when you expect to use a Resource
soon but can't render that resource directly. At the moment the only
sensible module to preload is script modules along with some other `as`
variants such as `as="serviceworker"`. In the future when there is some
notion of how to preload style module script or json module scripts this
API will be extended to support those as well.
##### Arguments
1. `href: string` -> the href or src value you want to preload.
2. `options?: {...}` ->
2.1. `options.as?: string` -> the `as` property the modulepreload link
should render with. If not provided it will be omitted which will cause
the modulepreload to be treated like a script module
2.2. `options.crossOrigin?: string` -> modules always load with CORS but
you can provide `use-credentials` if you want to change the default
behavior
2.3. `options.integrity?: string` -> an integrity hash for subresource
integrity APIs
##### Rendering
each preloaded module will emit a `<link rel="modulepreload" href="..."
/>`
if `as` is specified and is something other than `"script"` the as
attribute will also be included
if crossOrigin or integrity as specified their attributes will also be
included
During SSR these script tags will be emitted before content. If we have
not yet flushed the document head they will be emitted there after
things that block paint such as font preloads, img preloads, and
stylesheets.
On the client these link tags will be appended to the document.head.
### Implements `ReactDOM.preinitModule()`
`preinitModule` is a function to loading module scripts before they are
required. It has the same use cases as `preinit`.
During SSR you would use this to tell the browsers to start fetching
code that will be used without having to wait for bootstrapping to
initiate module fetches.
ON the client you would use this to start fetching a module script early
for an anticipated navigation or other event that is likely to depend on
this module script.
the `as` property for Float methods drew inspiration from the `as`
attribute of the `<link rel="preload" ... >` tag but it is used as a
sort of tag for the kind of thing being targetted by Float methods. For
`preinitModule` we currently only support `as: "script"` and this is
also the assumed default type so you current never need to specify this
`as` value. In the future `preinitModule` will support additional module
script types such as `style` or `json`. The support of these types will
correspond to [Module Import
Attributes](https://github.com/tc39/proposal-import-attributes).
##### Arguments
1. `href: string` -> the href or src value you want to preinitialize
2. `options?: {...}` ->
2.1 `options.as?: string` -> only supports `script` and this is the
default behavior. Until we support import attributes such as `json` and
`style` there will not be much reason to provide an `as` option.
2.2. `options.crossOrigin?: string`: modules always load with CORS but
you can provide `use-credentials` if you want to change the default
behavior
2.3 `options.integrity?: string` -> an integrity hash for subresource
integrity APIs
##### Rendering
each preinitialized `script` module will emit a `<script type="module"
async="" src"...">` During SSR these will appear behind display blocking
resources such as font preloads, img preloads, and stylesheets. In the
browser these will be appende to the head.
Note that for other `as` types the rendered output will be slightly
different. `<script type="module">import "..." with {type: "json"
}</script>`. Since this tag is an inline script variants of React that
do not use inline scripts will simply omit these preinitialization tags
from the SSR output. This is not implemented in this PR but will appear
in a future update.
DiffTrain build for [e505316920](https://github.com/facebook/react/commit/e50531692010bbda2a4627b07c7c810c3770a52a)
This adds an experimental `unstable_postpone(reason)` API.
Currently we don't have a way to model effectively an Infinite Promise.
I.e. something that suspends but never resolves. The reason this is
useful is because you might have something else that unblocks it later.
E.g. by updating in place later, or by client rendering.
On the client this works to model as an Infinite Promise (in fact,
that's what this implementation does). However, in Fizz and Flight that
doesn't work because the stream needs to end at some point. We don't
have any way of knowing that we're suspended on infinite promises. It's
not enough to tag the promises because you could await those and thus
creating new promises. The only way we really have to signal this
through a series of indirections like async functions, is by throwing.
It's not 100% safe because these values can be caught but it's the best
we can do.
Effectively `postpone(reason)` behaves like a built-in [Catch
Boundary](https://github.com/facebook/react/pull/26854). It's like
`raise(Postpone, reason)` except it's built-in so it needs to be able to
be encoded and caught by Suspense boundaries.
In Flight and Fizz these behave pretty much the same as errors. Flight
just forwards it to retrigger on the client. In Fizz they just trigger
client rendering which itself might just postpone again or fill in the
value. The difference is how they get logged.
In Flight and Fizz they log to `onPostpone(reason)` instead of
`onError(error)`. This log is meant to help find deopts on the server
like finding places where you fall back to client rendering. The reason
that you pass in is for that purpose to help the reason for any deopts.
I do track the stack trace in DEV but I don't currently expose it to
`onPostpone`. This seems like a limitation. It might be better to expose
the Postpone object which is an Error object but that's more of an
implementation detail. I could also pass it as a second argument.
On the client after hydration they don't get passed to
`onRecoverableError`. There's no global `onPostpone` API to capture
postponed things on the client just like there's no `onError`. At that
point it's just assumed to be intentional. It doesn't have any `digest`
or reason passed to the client since it's not logged.
There are some hacky solutions that currently just tries to reuse as
much of the existing code as possible but should be more properly
implemented.
- Fiber is currently just converting it to a fake Promise object so that
it behaves like an infinite Promise.
- Fizz is encoding the magic digest string `"POSTPONE"` in the HTML so
we know to ignore it but it should probably just be something neater
that doesn't share namespace with digests.
Next I plan on using this in the `/static` entry points for additional
features.
Why "postpone"? It's basically a synonym to "defer" but we plan on using
"defer" for other purposes and it's overloaded anyway.
DiffTrain build for [ac1a16c67e](https://github.com/facebook/react/commit/ac1a16c67e268fcb2c52e91717cbc918c7c24446)
stacked on #27222
When I initially developed Float I was trying to be extremely clever in
how to explain when there are mismatching Resource instances. I still
think that we should do some kind of validation here but I want to
implement something much simpler. In practice there are not many cases
where you would accidentally create the same resource twice but with
differing props. Since I am going to land `preloadModule` and
`preinitModule` soon and I want to avoid adding to the overly complex
dev validation there I am going to remove it now and add something later
that is simplified.
DiffTrain build for [5ea1397b2b](https://github.com/facebook/react/commit/5ea1397b2b62875dfb521cf8de0efcc15d0df797)
imageSrcSet should have been srcSet when referencing an img tag.
imageSizes should have been sizes. This caused preloads for img tags
using srcSet and sizes to incorrectly render as having a href only,
dropping the srcSet and sizes part of the preload
DiffTrain build for [4e3618ae41](https://github.com/facebook/react/commit/4e3618ae41669c95a3377ae615c727f74f89d141)
Eventually we will treat images without `loading="lazy"` as suspensey
meaning we will coordinate the reveal of boundaries when these images
have loaded and ideally decoded. As a step in that direction this change
prioritizes these images for preloading to ensure the highest chance
that they are loaded before boundaries reveal (or initial paint). every
img rendered that is non lazy loading will emit a preload just behind
fonts.
This change implements a new resource queue for high priority image
preloads
There are a number of scenarios where we end up putting a preload in
this queue
1. If you render a non-lazy image and there are fewer than 10 high
priority image preloads
2. if you render a non-lazy image with fetchPriority "high"
3. if you preload as "image" with fetchPriority "high"
This means that by default we won't overrsaturate this queue with every
img rendered on the page but the earlier encountered ones will go first.
Essentially this is React's own implementation of fetchPriority="auto".
If however you specify that the fetchPriority is higher then in theory
an unlimited number of images can preload in this queue. This gives
users some control over queuing while still providing a good default
that does not require any opting into
Additionally we use fetchPriority "low" as a signal that an image does
not require preloading. This may end up being pointless if not using
lazy (which also opts out of preloading) because it might delay initial
paint but we'll start with this hueristic and consider changes in the
future when we have more information
DiffTrain build for [f359f9b41a](https://github.com/facebook/react/commit/f359f9b41ac1a8127f5ba505e0c04675eee0d310)
This adds a regression test for a bug where, after a store mutation, the
updated data causes the shell of the app to suspend.
When re-rendering due to a concurrent store mutation, we must check for
the RootDidNotComplete exit status again.
As a follow-up, I'm going to look into to cleaning up the places where
we check the exit status, so bugs like these are less likely. I think we
might be able to combine most of it into a single switch statement.
DiffTrain build for [201becd3d2](https://github.com/facebook/react/commit/201becd3d294b38824930a818e3412c6e04ba2eb)
Currently React attempts to prioritize certain preloads over others
based on their type. This is at odds with allowing the user to control
priority by ordering which calls are made first. There are some asset
types that generally should just be prioritized first such as fonts
since we don't know when fonts will be used and they either block
display or may lead to fallback fonts being used. But for scripts and
stylesheets we can emit them in the order received with other arbitrary
preload types.
We will eventually add support for emitting suspensey image preloads
before other resources because these also block display however that
implementation will look at which images are actually rendered rather
than simply preloaded.
DiffTrain build for [cb3404a0cc](https://github.com/facebook/react/commit/cb3404a0ccd8b5edf5d2b90bd844742090e38f42)
Generally scripts should not be preloaded before images but if they
arrive earlier than image preloads (or images) the network (or server)
may be saturated responding to inflight script preloads and not
sufficiently prioritize images arriving later. This change marks the
preloaded bootstrap script with a `low` fetch priority to signal to
supporting browsers that the request should be deprioritized. This
should make the preload operate similar to async script fetch priority
which is low by default according to https://web.dev/fetch-priority/
Additionally the bootstrap script preloads will emit before
preinitialized scripts do. Normal script preloads will continue to be
prioritized after stylesheets
This change can land separatrely but is part of a larger effort to
implement elevating image loading and making script loading less
blocking. Later changes will emit used suspensey images earlier in the
queue and will stop favoring scripts over images that are explicitly
preloaded
DiffTrain build for [9edf470d6e](https://github.com/facebook/react/commit/9edf470d6ed1f3ac12c23a248e64293f367c1797)
Fixes: #27200
preloads for images that appear before the viewport meta may be loaded
twice because the proper device image information is not used with the
preload but is with the image itself. The viewport meta should be
emitted earlier than all preloads to avoid this.
this change moves the queue for the viewport meta to preconnects which
already has the right priority for this tag
DiffTrain build for [ea17cc18f4](https://github.com/facebook/react/commit/ea17cc18f458010c89c1bf494be08bb782d034af)