-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Float][Flight] Flight support for Float #26502
Conversation
8622483
to
b880f09
Compare
2487dd5
to
2365024
Compare
resolved now |
1a541ec
to
9a6f82e
Compare
d9db635
to
6917df1
Compare
@@ -46,6 +47,10 @@ function processFullRow(response: Response, row: string): void { | |||
resolveModule(response, id, row.slice(colon + 2)); | |||
return; | |||
} | |||
case 'D': { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's what I had in mind for typed arrays:
- C - Int8Array (char)
- c - Uint8Array (unsigned char)
- Z - Uint8ClampedArray (unsigned clamped char)
- S - Int16Array (short)
- s - Uint16Array (unsigned short)
- L - Int32Array (long)
- l - Uint32Array (unsigned long)
- F - Float32Array (float)
- D - Float64Array (double)
- N - BigInt64Array (number)
- n - BigUint64Array (unsigned number)
- A - ArrayBuffer
As you can see D
represents double
in this pattern. I'm not 100% sure this plan will work but can we pick another character?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's possible we just end up encoding this in the reference instead though. (Where D is also already used by Date.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think !
is a nice alternative
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Punctuations could apply to everything. Why is this one special? It can't be anything that's in the first character of JSON since that's implicitly a model. So that eliminates [
, {
, "
and numbers. So we quickly run out of punctuators. And since punctuators generally means JSON it's extra confusing.
Let's pick an A-Z character. I think P
is open since we now encode Context Providers in the reference.
Although my other question is if we should just encode preload/preconnect/preinit etc into this character instead of the payload.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I picked !
for emphasis that these things should be acted on immediately. A letter is fine though :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We decided to go with H
+ char to represent Hints of various types.
HD
-> prefetchDNS
HC
-> preconnect
HL
-> preload
HI
-> preinit
can support more (up to alphabet limit) which should be more than enough.
Also note there is no longer a general "Directive" concept. these are hints. This will be helpful in allowing for deduping, and specialized re-ordering of hints
packages/react-server-dom-relay/src/ReactFlightServerConfigDOMRelay.js
Outdated
Show resolved
Hide resolved
packages/react-server-dom-relay/src/ReactFlightServerConfigDOMRelay.js
Outdated
Show resolved
Hide resolved
8847629
to
0763c04
Compare
packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js
Outdated
Show resolved
Hide resolved
packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js
Outdated
Show resolved
Hide resolved
packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js
Outdated
Show resolved
Hide resolved
@@ -1250,6 +1276,21 @@ function flushCompletedChunks( | |||
} | |||
} | |||
importsChunks.splice(0, i); | |||
|
|||
// Next comes directives. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's interesting that these come after JS but in Fizz they don't. Does it make sense that a preconnect
or prefetchDNS
comes after?
It kind of makes sense that CSS is actually lower pri that JS in the client-only scenario, because you need the JS to render but then you can wait for the CSS at the end with suspensey CSS.
But we don't know this is only CSS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bunch of nits but mainly the scheduling thing needs to be fixed.
a49e59d
to
da37fbf
Compare
|
||
// If new resources are identified out of band with a task we can still potentially | ||
// emit them early by pinging them. | ||
export function pingRequest(): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too generic. Unclear what this will mean when it changes.
There are optimizations we can do if we assume that this is resources.
flushResources()
maybe? It's not really a sync flush but close enough.
// emit them early by pinging them. | ||
export function pingRequest(): void { | ||
const request = resolveCurrentRequest(); | ||
if (request) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If request.destination === null
we don't have to schedule work because there's nothing to flush too. That's an assumption we can make because we know we're not going to work on anything.
In fact, really we should probably just be scheduling flushCompletedQueues
since we're not doing any work but that's maybe over optimizing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored this a bit. Ended up just flushing queues/chunks because the flush can run outside of the dispatcher and context scoping that happens during performWork so it must not rely on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nits
Supporting Float methods such as ReactDOM.preload() are challenging for flight because it does not have an easy means to convey direct executions in other environments. Because the flight wire format is a JSON-like serialization that is expected to be rendered it currently only describes renderable elements. We need a way to convey a function invocation that gets run in the context of the client environment whether that is Fizz or Fiber. Fiber is somewhat straightforward because the HostDispatcher is always active and we can just have the FlightClient dispatch the serialized directive. Fizz is much more challenging becaue the dispatcher is always scoped but the specific request the dispatch belongs to is not readily available. Environments that support AsyncLocalStorage (or in the future AsyncContext) we will use this to be able to resolve directives in Fizz to the appropriate Request. For other environments directives will be elided. Right now this is pragmatic and non-breaking because all directives are opportunistic and non-critical. If this changes in the future we will need to reconsider how widespread support for async context tracking is. For Flight, if AsyncLocalStorage is available Float methods can be called before and after await points and be expected to work. If AsyncLocalStorage is not available float methods called in the sync phase of a component render will be captured but anything after an await point will be a noop. If a float call is dropped in this manner a DEV warning should help you realize your code may need to be modified.
This PR implements preloading of CSS from RSC. 1. The underlying Flight protocol was extended in facebook/react#26502 to allow sending hints from RSC to SSR and Client runtimes. React was updated to include these changes. 2. We now call `ReactDOM.preload()` for each stylesheet used in a layout/page layer There are a few implementation details to take note of 1. we were previously using the `.browser` variant of a few React packages. This was a holdover from when there was just browser and node and we wanted to use the browser variant b/c we wanted the same code to work in edge/node runtimes. React now publishes a `.edge` variant which is like `.browser` but expects to be server only. This is necessary to get the opt-in for `AsyncLocalStorage`. 2. Even with the above change, AsyncLocalStorage was not patched on the global scope until after React was loaded. I moved this into a module which is loaded first 3. The component passed to RSC's `renderToReadableStream` is not actually part of the RSC module graph. If I tried to call `ReactDOM.preload()` inside that function or any other function defined inside `app-render.tsx` file it would actually see the wrong instance of `react-dom`. I added a new export on the RSC top level export which exposes a `preloadStyle(...)` function which just delegates to `ReactDOM.preload(...)`. This makes the preload run in the right module graph ~There are a couple of bugs in React that this work uncovered that I will upstream. We may want to delay merging until they are addressed. I'll update this comment when that is complete.~ 1. ~React, during SSR, can emit a preload for a style twice in some circumstances because late discovered stylesheets don't consider whether a preload has already been flushed when preparing to reveal a boundary they are within~ 2. ~React, during RSC updates on the client, can preload a style that is already in the document because it currently only looks for existing preload links and does not consider if there is a stylesheet link with the same href.~ ~both of these issues will not break functionality, they just make the network tab look at bit more noisy. We would expect network deduping to prevent multiple actual loads~ The above React bugs were fixed and included now in the React update in this PR --------- Co-authored-by: Shu Ding <g@shud.in>
Stacked on facebook#26557 Supporting Float methods such as ReactDOM.preload() are challenging for flight because it does not have an easy means to convey direct executions in other environments. Because the flight wire format is a JSON-like serialization that is expected to be rendered it currently only describes renderable elements. We need a way to convey a function invocation that gets run in the context of the client environment whether that is Fizz or Fiber. Fiber is somewhat straightforward because the HostDispatcher is always active and we can just have the FlightClient dispatch the serialized directive. Fizz is much more challenging becaue the dispatcher is always scoped but the specific request the dispatch belongs to is not readily available. Environments that support AsyncLocalStorage (or in the future AsyncContext) we will use this to be able to resolve directives in Fizz to the appropriate Request. For other environments directives will be elided. Right now this is pragmatic and non-breaking because all directives are opportunistic and non-critical. If this changes in the future we will need to reconsider how widespread support for async context tracking is. For Flight, if AsyncLocalStorage is available Float methods can be called before and after await points and be expected to work. If AsyncLocalStorage is not available float methods called in the sync phase of a component render will be captured but anything after an await point will be a noop. If a float call is dropped in this manner a DEV warning should help you realize your code may need to be modified. This PR also introduces a way for resources (Fizz) and hints (Flight) to flush even if there is not active task being worked on. This will help when Float methods are called in between async points within a function execution but the task is blocked on the entire function finishing. This PR also introduces deduping of Hints in Flight using the same resource keys used in Fizz. This will help shrink payload sizes when the same hint is attempted to emit over and over again
Stacked on #26557 Supporting Float methods such as ReactDOM.preload() are challenging for flight because it does not have an easy means to convey direct executions in other environments. Because the flight wire format is a JSON-like serialization that is expected to be rendered it currently only describes renderable elements. We need a way to convey a function invocation that gets run in the context of the client environment whether that is Fizz or Fiber. Fiber is somewhat straightforward because the HostDispatcher is always active and we can just have the FlightClient dispatch the serialized directive. Fizz is much more challenging becaue the dispatcher is always scoped but the specific request the dispatch belongs to is not readily available. Environments that support AsyncLocalStorage (or in the future AsyncContext) we will use this to be able to resolve directives in Fizz to the appropriate Request. For other environments directives will be elided. Right now this is pragmatic and non-breaking because all directives are opportunistic and non-critical. If this changes in the future we will need to reconsider how widespread support for async context tracking is. For Flight, if AsyncLocalStorage is available Float methods can be called before and after await points and be expected to work. If AsyncLocalStorage is not available float methods called in the sync phase of a component render will be captured but anything after an await point will be a noop. If a float call is dropped in this manner a DEV warning should help you realize your code may need to be modified. This PR also introduces a way for resources (Fizz) and hints (Flight) to flush even if there is not active task being worked on. This will help when Float methods are called in between async points within a function execution but the task is blocked on the entire function finishing. This PR also introduces deduping of Hints in Flight using the same resource keys used in Fizz. This will help shrink payload sizes when the same hint is attempted to emit over and over again DiffTrain build for commit 36e4cbe.
Stacked on #26557
Supporting Float methods such as ReactDOM.preload() are challenging for flight because it does not have an easy means to convey direct executions in other environments. Because the flight wire format is a JSON-like serialization that is expected to be rendered it currently only describes renderable elements. We need a way to convey a function invocation that gets run in the context of the client environment whether that is Fizz or Fiber.
Fiber is somewhat straightforward because the HostDispatcher is always active and we can just have the FlightClient dispatch the serialized directive.
Fizz is much more challenging becaue the dispatcher is always scoped but the specific request the dispatch belongs to is not readily available. Environments that support AsyncLocalStorage (or in the future AsyncContext) we will use this to be able to resolve directives in Fizz to the appropriate Request. For other environments directives will be elided. Right now this is pragmatic and non-breaking because all directives are opportunistic and non-critical. If this changes in the future we will need to reconsider how widespread support for async context tracking is.
For Flight, if AsyncLocalStorage is available Float methods can be called before and after await points and be expected to work. If AsyncLocalStorage is not available float methods called in the sync phase of a component render will be captured but anything after an await point will be a noop. If a float call is dropped in this manner a DEV warning should help you realize your code may need to be modified.
This PR also introduces a way for resources (Fizz) and hints (Flight) to flush even if there is not active task being worked on. This will help when Float methods are called in between async points within a function execution but the task is blocked on the entire function finishing.
This PR also introduces deduping of Hints in Flight using the same resource keys used in Fizz. This will help shrink payload sizes when the same hint is attempted to emit over and over again