-
Notifications
You must be signed in to change notification settings - Fork 47k
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
Basic Fizz Architecture #20970
Basic Fizz Architecture #20970
Conversation
Comparing: bd245c1...a530636 Critical size changesIncludes critical production bundles, as well as any change greater than 2%:
Significant size changesIncludes any change greater than 0.2%: Expand to show
|
fed8ac8
to
0aa20bf
Compare
This allow very fine grained flushing but in practice I think we'll want to throttle both flushing of partial segments on the server, and throttle the client from inserting suspense boundaries too often (causes layout thrash). Ultimately the main goal isn't to squeeze out every little piece of perf (although this lets you do that too) but to make it just work well out of the box whether you happen to have a few slow/large data sources or not. |
I want to leave the note that inline script tags might be a bit unpleasant for CSP. Maybe the runtime could use Also why using |
@sokra The actual implementation actually embeds the runtime in the first inline script too. Our primary goal is to support cases where you’ve never visited the site before at all. In that case, referring to an external runtime with a second request/round trip can leave a lot of cycles on the floor when it could’ve been inserted earlier. Ofc, you probably do a secondary round trip for CSS anyway but the scripting is slower. We could play with an optional CSP friendly mode using an external script too, as an option, but I don’t think it’ll be the primary one. Unless there’s a CSP friendly way of setting up the MutationObserver in a single request. Regarding “div hidden” instead of “fragment”. The original plan was to use fragment but we’ve actually used this strategy at FB for a long time already and ran experiments on each approach. Turns out that hidden divs perform better in these scenarios. Something about preparing as much as possible of those internal nodes as early as possible but not triggering layout. We can always reevaluate if the calculus changes though. |
We should however have the script nonce be configurable for CSP purposes. We could possibly use an inline mutation observer script to avoid the overhead of multiple script tags. It's a bit tricky because we have to figure out where in the document we are and such and avoid listening to everything in the doc. The have some convention in the IDs for picking up react things. |
Can you show a few pointers of how it comes up in the code? Where does this decision show up? |
} | ||
|
||
// This object is used to lazily reuse the ID of the first generated node, or assign one. | ||
// This is very specific to DOM where we can't assign an ID to. |
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.
What do you mean we can't assign an ID?
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 probably had a meeting interrupt finishing the sentence.
This part isn't implemented yet but the gist is that I plan to reuse the id if the user rendered a DOM node like <div id="foo">
. We can't override it since the user provided it and expects it to be there.
We could insert a hidden node but this is deep in the tree. We try hard not to mess with the DOM structure so that css pseudo selectors can keep working etc. That's a little fragile with suspense anyway since it can be either fallback or content and both can exist at the same time. So maybe that's not such a big concern.
In fact, I don't know how to do it if there are zero DOM nodes. Eg <Suspense fallback="Loading...">
. In that case we'll need to inject a dummy dom node anyway.
There's no efficient api for querying a comment.
The ideal would be if Suspense was a reified fragment or <r-suspense>
custom tag. It would make everything much simpler but then we have the issue with css and display: contents et al not working properly in browsers.
|
||
type Segment = { | ||
status: 0 | 1 | 2 | 3, | ||
parentFlushed: boolean, // typically a segment will be flushed by its parent, except if its parent was already flushed |
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.
Do I understand correctly that "parent flushed" is when we've already emitted the fallback for the content of this segment?
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.
Not quite. If you have a large tree we can emit the parent tree without all the leaves. We can leave holes in there.
In that case the parent has already been flushed.
Normally a child gets flushed by its parent being flushed but in this case that can't happen so we need to schedule it to be emitted later.
// TODO: This needs to be contextually aware and switch tag since not all parents allow for spans like | ||
// <select> or <tbody>. E.g. suspending a component that renders a table row. |
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.
Why not an inline comment rather than a <span>
to avoid this problem?
Edit: "There's no efficient api for querying a comment." is probably why
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.
Yea
writeChunk(destination, placeholder1); | ||
// TODO: Use the identifierPrefix option to make the prefix configurable. | ||
writeChunk(destination, placeholder2); | ||
const formattedID = convertStringToBuffer(id.toString(16)); | ||
writeChunk(destination, formattedID); | ||
return writeChunk(destination, placeholder3); |
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'm curious if it's an intentional part of the design that you write a lot of really small chunks (separate function calls for placeholder 1, 2, etc) ? Are there performance implications of enqueueing all of these individually or would you expect it to be negligible.
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 depends a bit on the underlying API. If it's suboptimal, like Node.js certainly is, then yea there can be quite a bit of overhead in the additional calls. There's opportunities to lower it but there will always be some.
However, when the thing you're writing is a static chunk like the ones I hoisted, you save quite a bit compared to trying to merge them together. If it's dynamic strings then you might be better off concatenating the strings. If the static chunks are small, then it might also be worth while tacking them onto a dynamic string.
It's a tradeoff we can play with.
I intentionally start with the most esoteric form that people aren't aware of to make everyone aware of the option to e.g. use a hoisted shared array buffer as a technique.
Also, converting everything to buffers using convertStringToBuffer is suboptimal because certain sinks can use the string as is and emit it directly to the underlying buffer. However, some APIs (like web streams) don't support this yet.
I plan on backtracking this to mix strings and buffers if both are available (like on Node). Mostly buffers for static stuff so that it can safely skip the encoding part and mostly strings for dynamic stuff so that the implementation can choose if it can go direct or needs to convert it.
Again, buffers is the least idiomatic one so I start with that to preserve the option to use them.
segment.chunks.push(formatChunk(type, props)); | ||
} else if (type === REACT_SUSPENSE_TYPE) { | ||
// Each time we enter a suspense boundary, we split out into a new segment for | ||
// the fallback so that we can later replace that segment with the content. |
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.
What happens if a fallback itself Suspends? Did you have to build anything special for this case?
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 should just work if my calculations are correct. (Ofc, I have tested any of this.)
A fallback is rendered just like plain children in the parent of the Suspense boundary would be. So they end up being children of the fallback.
You can also have a suspense boundary within the fallback. If the main content gets deleted before that resolves, then it won't find the fallback in getElementById so it won't insert it. This is no different than if React deleted the tree such as if the user switches tab before the full page has loaded.
However, there is an outstanding to do here to cancel the fallback if the main content succeeds. Right now we have to compute the fallback before showing the main content which is not ideal. So I need to build in cancelation. That doesn't affect the suspended fallback though.
The fallback suspending adds to the ref count (pendingWork) of the parent so therefore we end up waiting on the fallback even if the main content succeeds. I explored other options but I think cancelation is the right strategy to solve this.
|
||
const newBoundary = createSuspenseBoundary(); | ||
|
||
const insertionIndex = segment.chunks.length; |
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.
Do I understand correctly that you're never using indexes for DOM manipulation, and it's just something used while building a tree? Thinking of a case where some browser extension messes with the DOM before we've had a chance to load all the follow-up content. I guess that would break hydration anyway..
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.
Yea, this index is completely internal to the Fizz data structures. It's just a way to model that the child Segments are in the middle of the list of Chunks. It could also be modeled as Array<Segment | Chunk>
but it's slightly less efficient.
const completeSegmentFunction = | ||
'function $RS(b,f){var a=document.getElementById(b),c=document.getElementById(f);for(a.parentNode.removeChild(a);a.firstChild;)c.parentNode.insertBefore(a.firstChild,c);c.parentNode.removeChild(c)}'; | ||
const completeBoundaryFunction = | ||
'function $RC(b,f){var a=document.getElementById(b),c=document.getElementById(f);c.parentNode.removeChild(c);if(a){do a=a.previousSibling;while(8!==a.nodeType||"$?"!==a.data);var h=a.parentNode,d=a.nextSibling,g=0;do{if(d&&8===d.nodeType){var e=d.data;if("/$"===e)if(0===g)break;else g--;else"$"!==e&&"$?"!==e&&"$!"!==e||g++}e=d.nextSibling;h.removeChild(d);d=e}while(d);for(;c.firstChild;)h.insertBefore(c.firstChild,d);a.data="$";a._reactRetry&&a._reactRetry()}}'; | ||
const clientRenderFunction = | ||
'function $RX(b){if(b=document.getElementById(b)){do b=b.previousSibling;while(8!==b.nodeType||"$?"!==b.data);b.data="$!";b._reactRetry&&b._reactRetry()}}'; |
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.
Any security implications here? (if some library <script>
sneakily defines global functions like $RC
to intercept ours)
Could we randomize these names or something (since we send both the functions and the calls to them as well)?
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.
Seems like if some library can define these functions, you run its code — so you're trusting it anyway. Cause it could just hijack object prototype or other built-ins.
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.
Right. The reason I ask here though is that it seems like it would be easier to do this in a sneaky way. Overriding a prototype seems pretty obvious. Defining an $RC
function not so much. "nothing in my client bundle even calls this function. nbd"
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'm not aware of any threat models against this but worth looking out for. If we just duplicated all this code in each script tag, we wouldn't even need 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.
If we just duplicated all this code in each script tag, we wouldn't even need it.
I don't think this is true.
Defining a function on window
will override a function defined within a script tag and called within that script tag.
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 we duplicated we wouldn't name it we'd call it. (function() { ... })("123", "456")
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.
Ah I misunderstood what you were saying. Didn't realize you were saying we'd use an IIFE.
// This segment is the actual child content. We can start rendering that immediately. | ||
const contentRootSegment = createPendingSegment(request, 0, null); | ||
// We mark the root segment as having its parent flushed. It's not really flushed but there is | ||
// no parent segment so there's nothing to wait on. |
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.
This is confusing. Could the property be named differently?
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.
This was one of the hardest part of model. I just figured it out two days ago. The modeling of the roots are pretty confusing regardless. Not sure it helps and might just make the nested case harder to get.
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 this was named after what should happen instead of what has happened, would that be something like shouldFlushOnCompletion
(BecauseParentHasAlreadyFlushed)?
Not proposing a name change since I see what you mean that it could make other parts more confusing, just trying to understand.
@@ -376,5 +376,13 @@ | |||
"385": "A mutable source was mutated while the %s component was rendering. This is not supported. Move any mutations into event handlers or effects.", | |||
"386": "The current renderer does not support microtasks. This error is likely caused by a bug in React. Please file an issue.", | |||
"387": "Should have a current fiber. This is a bug in React.", | |||
"388": "Expected to find a bailed out fiber. This is a bug in React." | |||
"388": "Expected to find a bailed out fiber. This is a bug in React.", | |||
"389": "There can only be one root segment. This is a bug in React.", |
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 (boundary.pendingWork === 0) { | ||
// This must have been the last segment we were waiting on. This boundary is now complete. | ||
if (segment.parentFlushed) { | ||
// Our parent segment already flushed, so we need to schedule this segment to be emitted. |
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.
Is "flushed" and "emitted" the same thing?
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.
Yea. I guess emitted is a bit more vague because it's a process of also flushing its children. Not just flushing this segment.
} | ||
case COMPLETED: { | ||
segment.status = FLUSHED; | ||
let r = true; |
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.
What is this? What does the returned boolean mean?
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 it returns false, it indicates that the underlying buffer was full by the time we finished writing and we shouldn't write anything more. We'll still finish buffering up the remainder of this tree so that we have a complete tree but after that we should stop.
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.
Would be nice to clarify in the naming
for (; chunkIdx < nextChild.index; chunkIdx++) { | ||
writeChunk(destination, chunks[chunkIdx]); | ||
} | ||
r = flushSegment(request, destination, nextChild); |
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.
Why do we reassign a boolean in a loop without using its value? It's gonna be the last one, is that intentional?
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.
Yea. It's intentional since it'll keep returning full if the buffer is full but we can't stop early because we need to finish a complete tree.
|
||
const boundaryEnd = new Uint8Array(0); | ||
return writeChunk(destination, boundaryEnd); | ||
} else if (boundary.byteSize > request.maxBoundarySize) { |
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.
This is a really neat feature
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.
This is how you'd take advantage of streaming even with SSG.
This is a good callout and why I bothered mentioning it. It doesn't really come up directly in any one place and in fact, it will be difficult to maintain this because nothing about the code or our testing enforces it to be true. It's pretty easy to break. The easiest way to break this is to add something to one of the queues or immediately flush something from within the rendering or when data resolves. If the data loads in a different order, then it would end up being queued differently. In fact, this naturally happens when you stream as you go. It's only fully deterministic if you wait until the end to flush, but it's also deterministic in pieces. Like if you have one suspense boundary and flush when you reach it and then flush again at the end. But if you have two suspense boundaries, and flush when you hit either of them then both might be done or just one - which is not deterministic. But to answer your question directly, things aren't added to queues unless their parent is already flushed. Otherwise they get written through their parent. So there's two ways things can get emitted. Either through the queues or through the tree. The order of the tree is based on the order of the component tree, so is therefore deterministic. If you wait until the end to flush, there will only every be one thing scheduled to flush - the root. As a result, that thing will be written in the order of the tree. Another subtle place where this happens is that the "large suspense boundary" heuristic is applied in the flush phase. It's the only thing that queues something in the flush phase. We need to do that to ensure that the queueing of those happen in the order of the tree. If we did that in the render phase, then they would end up being queued in the order of when they completed. We might not always use the order of the tree. We might add priority hints to different boundaries, and if we did that, then we'd have to deterministically emit them in the order of priority. |
In the DOM this is implemented as script tags. The first time it's emitted it includes the function. Future calls invoke the same function. The side of the complete boundary function in particular is unfortunately large.
This is implemented not as a serialized protocol but by-passing the serialization when possible and instead it's like a live tree being built.
This is not wired up. I just need something for the flow types since Flight and Fizz are both handled by the isServerSupported flag. Might as well add something though. The principle of this format is the same structure as for HTML but a simpler binary format. Each entry is a tag followed by some data and terminated by null.
Sorry for the false alarm and added noise. I did some more testing and gzip-streaming on the fly does seem to work at least for all major modern browsers. In my previous testing I think I might have conflated this with how Safari and Firefox only flushes the initial chunk to the DOM after a certain size has been received, making it seem like it all rendered at once. 😊 |
Very good idea. We can even use worker to accept streaming data. |
Summary: This sync includes the following changes: - **[b9c4a01f7](facebook/react@b9c4a01f7 )**: Allow the streaming config to decide how to precompute or compute chunks ([#21008](facebook/react#21008)) //<Sebastian Markbåge>// - **[c06d245fc](facebook/react@c06d245fc )**: Update devtools-extensions build script to reflect changes in local b… ([#21004](facebook/react#21004)) //<Hector Rincon>// - **[14e4fd1ff](facebook/react@14e4fd1ff )**: [Fizz] Move DOM/Native format configs to their respective packages ([#20994](facebook/react#20994)) //<Sebastian Markbåge>// - **[f2b6bf7c8](facebook/react@f2b6bf7c8 )**: [Fizz] Destroy the stream with an error if the root throws ([#20992](facebook/react#20992)) //<Sebastian Markbåge>// - **[10cc40018](facebook/react@10cc40018 )**: Basic Fizz Architecture ([#20970](facebook/react#20970)) //<Sebastian Markbåge>// - **[b7e631066](facebook/react@b7e631066 )**: Stop tracking roots with pending discrete updates ([#20978](facebook/react#20978)) //<Andrew Clark>// - **[860f673](facebook/react@860f673a7 )**: Remove Blocking Mode (again) ([#20974](facebook/react#20974)) //<Ricky>// - **[acde65469](facebook/react@acde65469 )**: Unify InputDiscreteLane with SyncLane ([#20968](facebook/react#20968)) //<Ricky>// - **[6556e2a87](facebook/react@6556e2a87 )**: Cleaned up unused PassiveUnmountPendingDev DEV flag ([#20973](facebook/react#20973)) //<Brian Vaughn>// - **[60182d64c](facebook/react@60182d64c )**: Cleanup tests using runWithPriority. ([#20958](facebook/react#20958)) //<Ricky>// - **[e4d4b7074](facebook/react@e4d4b7074 )**: Land enableNativeEventPriorityInference ([#20955](facebook/react#20955)) //<Ricky>// - **[73e900b0e](facebook/react@73e900b0e )**: Land enableDiscreteEventMicroTasks ([#20954](facebook/react#20954)) //<Ricky>// - **[41e62e771](facebook/react@41e62e771 )**: Remove runWithPriority internally //<Rick Hanlon>// - **[431e76e2d](facebook/react@431e76e2d )**: Switch callsites over to update lane priority //<Rick Hanlon>// - **[e89d74ee6](facebook/react@e89d74ee6 )**: Remove decoupleUpdatePriorityFromScheduler //<Rick Hanlon>// - **[ca15606d8](facebook/react@ca15606d8 )**: chore(build): Ensure experimental builds exists on windows ([#20933](facebook/react#20933)) //<Sebastian Silbermann>// - **[d74559746](facebook/react@d74559746 )**: Use update lane priority to set pending updates on roots ([#20918](facebook/react#20918)) //<Ricky>// - **[f04bcb813](facebook/react@f04bcb813 )**: [Bugfix] Reset `subtreeFlags` in `resetWorkInProgress` ([#20948](facebook/react#20948)) //<Andrew Clark>// - **[c7b449798](facebook/react@c7b449798 )**: [Experiment] Lazily propagate context changes ([#20890](facebook/react#20890)) //<Andrew Clark>// - **[258b375a4](facebook/react@258b375a4 )**: Move context comparison to consumer //<Andrew Clark>// - **[7df65725b](facebook/react@7df65725b )**: Split getComponentName into getComponentNameFromFiber and getComponentNameFromType ([#20940](facebook/react#20940)) //<Brian Vaughn>// - **[ee4326357](facebook/react@ee4326357 )**: Revert "Remove blocking mode and blocking root ([#20888](facebook/react#20888))" ([#20916](facebook/react#20916)) //<Andrew Clark>// - **[de0ee76db](facebook/react@de0ee76db )**: Add unstable_strictModeLevel to test renderer ([#20914](facebook/react#20914)) //<Andrew Clark>// - **[d857f9e4d](facebook/react@d857f9e4d )**: Land enableSetImmediate feature flag ([#20906](facebook/react#20906)) //<Dan Abramov>// - **[553440bd1](facebook/react@553440bd1 )**: Remove blocking mode and blocking root ([#20888](facebook/react#20888)) //<Ricky>// - **[38f392ced](facebook/react@38f392ced )**: typo fix for the word 'Psuedo' ([#20894](facebook/react#20894)) //<Bowen>// - **[0cf9fc10b](facebook/react@0cf9fc10b )**: Fix React Native flow types ([#20889](facebook/react#20889)) //<Ricky>// - **[c581cdd48](facebook/react@c581cdd48 )**: Schedule sync updates in microtask ([#20872](facebook/react#20872)) //<Ricky>// - **[90bde6505](facebook/react@90bde6505 )**: Add SuspenseList to react-is ([#20874](facebook/react#20874)) //<Brian Vaughn>// - **[8336f19aa](facebook/react@8336f19aa )**: Update React Native types ([#20883](facebook/react#20883)) //<Rubén Norte>// - **[9209c30ff](facebook/react@9209c30ff )**: Add StrictMode level prop and createRoot unstable_strictModeLevel option ([#20849](facebook/react#20849)) //<Brian Vaughn>// - **[e5f6b91d2](facebook/react@e5f6b91d2 )**: Add Lane labels to scheduling profiler marks ([#20808](facebook/react#20808)) //<Brian Vaughn>// - **[c62986cfd](facebook/react@c62986cfd )**: Add additional messaging for RulesOfHooks lint error ([#20692](facebook/react#20692)) //<Anthony Garritano>// - **[78d2f2d30](facebook/react@78d2f2d30 )**: Fabric-compatible implementation of `JSReponder` feature ([#20768](facebook/react#20768)) //<Valentin Shergin>// - **[4d28eca97](facebook/react@4d28eca97 )**: Land enableNonInterruptingNormalPri ([#20859](facebook/react#20859)) //<Ricky>// - **[8af27aeed](facebook/react@8af27aeed )**: Remove scheduler sampling profiler shared array buffer ([#20840](facebook/react#20840)) //<Brian Vaughn>// - **[af3d52611](facebook/react@af3d52611 )**: Disable (unstable) scheduler sampling profiler for OSS builds ([#20832](facebook/react#20832)) //<Brian Vaughn>// - **[8fa0ccca0](facebook/react@8fa0ccca0 )**: fix: use SharedArrayBuffer only when cross-origin isolation is enabled ([#20831](facebook/react#20831)) //<Toru Kobayashi>// - **[099164792](facebook/react@099164792 )**: Use setImmediate when available over MessageChannel ([#20834](facebook/react#20834)) //<Dan Abramov>// - **[e2fd460cc](facebook/react@e2fd460cc )**: Bailout in sync task if work is not sync ([#20813](facebook/react#20813)) //<Andrew Clark>// - **[1a7472624](facebook/react@1a7472624 )**: Add `supportsMicrotasks` to the host config ([#20809](facebook/react#20809)) //<Andrew Clark>// - **[696e736be](facebook/react@696e736be )**: Warn if static flag is accidentally cleared ([#20807](facebook/react#20807)) //<Andrew Clark>// - **[483358c38](facebook/react@483358c38 )**: Omit TransitionHydrationLane from TransitionLanes ([#20802](facebook/react#20802)) //<Andrew Clark>// - **[78ec97d34](facebook/react@78ec97d34 )**: Fix typo ([#20466](facebook/react#20466)) //<inokawa>// - **[6cdc35972](facebook/react@6cdc35972 )**: fix comments of markUpdateLaneFromFiberToRoot ([#20546](facebook/react#20546)) //<neroneroffy>// - **[47dd9f441](facebook/react@47dd9f441 )**: Remove fakeCallbackNode ([#20799](facebook/react#20799)) //<Andrew Clark>// - **[114ab5295](facebook/react@114ab5295 )**: Make remaining empty lanes Transition lanes ([#20793](facebook/react#20793)) //<Andrew Clark>// - **[d3d2451a0](facebook/react@d3d2451a0 )**: Use a single lane per priority level ([#20791](facebook/react#20791)) //<Andrew Clark>// - **[eee874ce6](facebook/react@eee874ce6 )**: Cross-fork lint: Support named export declaration ([#20784](facebook/react#20784)) //<Andrew Clark>// - **[3b870b1e0](facebook/react@3b870b1e0 )**: Lane enableTransitionEntanglement flag ([#20775](facebook/react#20775)) //<Andrew Clark>// - **[d1845ad0f](facebook/react@d1845ad0f )**: Default updates should not interrupt transitions ([#20771](facebook/react#20771)) //<Andrew Clark>// - **[3499c343a](facebook/react@3499c343a )**: Apply #20778 to new fork, too ([#20782](facebook/react#20782)) //<Andrew Clark>// - **[3d10eca24](facebook/react@3d10eca24 )**: Move scheduler priority check into ReactDOM ([#20778](facebook/react#20778)) //<Dan Abramov>// - **[97fce318a](facebook/react@97fce318a )**: Experiment: Infer the current event priority from the native event ([#20748](facebook/react#20748)) //<Dan Abramov>// - **[6c526c515](facebook/react@6c526c515 )**: Don't shift interleaved updates to separate lane ([#20681](facebook/react#20681)) //<Andrew Clark>// - **[35f7441d3](facebook/react@35f7441d3 )**: Use Lanes instead of priority event constants ([#20762](facebook/react#20762)) //<Dan Abramov>// - **[a014c915c](facebook/react@a014c915c )**: Parallel transitions: Assign different lanes to consecutive transitions ([#20672](facebook/react#20672)) //<Andrew Clark>// - **[77754ae61](facebook/react@77754ae61 )**: Decouple event priority list from event name list ([#20760](facebook/react#20760)) //<Dan Abramov>// - **[b5bac1821](facebook/react@b5bac1821 )**: Align event group constant naming with lane naming ([#20744](facebook/react#20744)) //<Dan Abramov>// - **[4ecf11977](facebook/react@4ecf11977 )**: Remove the Fundamental internals ([#20745](facebook/react#20745)) //<Dan Abramov>// - **[eeb1325b0](facebook/react@eeb1325b0 )**: Fix UMD bundles by removing usage of global ([#20743](facebook/react#20743)) //<Dan Abramov>// - **[0935a1db3](facebook/react@0935a1db3 )**: Delete consolidateBundleSizes script ([#20724](facebook/react#20724)) //<Andrew Clark>// - **[7cb9fd7ef](facebook/react@7cb9fd7ef )**: Land interleaved updates change in main fork ([#20710](facebook/react#20710)) //<Andrew Clark>// - **[dc27b5aaa](facebook/react@dc27b5aaa )**: useMutableSource: Use StrictMode double render to detect render phase mutation ([#20698](facebook/react#20698)) //<Andrew Clark>// - **[bb1b7951d](facebook/react@bb1b7951d )**: fix: don't run effects if a render phase update results in unchanged deps ([#20676](facebook/react#20676)) //<Sebastian Silbermann>// - **[766a7a28a](facebook/react@766a7a28a )**: Improve React error message when mutable sources are mutated during render ([#20665](facebook/react#20665)) //<Brian Vaughn>// - **[a922f1c71](facebook/react@a922f1c71 )**: Fix cache refresh bug that broke DevTools ([#20687](facebook/react#20687)) //<Andrew Clark>// - **[e51bd6c1f](facebook/react@e51bd6c1f )**: Queue discrete events in microtask ([#20669](facebook/react#20669)) //<Ricky>// - **[aa736a0fa](facebook/react@aa736a0fa )**: Add queue microtask to host configs ([#20668](facebook/react#20668)) //<Ricky>// - **[deeeaf1d2](facebook/react@deeeaf1d2 )**: Entangle overlapping transitions per queue ([#20670](facebook/react#20670)) //<Andrew Clark>// - **[e316f7855](facebook/react@e316f7855 )**: RN: Implement `sendAccessibilityEvent` in RN Renderer that proxies between Fabric/non-Fabric ([#20554](facebook/react#20554)) //<Joshua Gross>// - **[9c32622cf](facebook/react@9c32622cf )**: Improve tests that use discrete events ([#20667](facebook/react#20667)) //<Ricky>// - **[d13f5b953](facebook/react@d13f5b953 )**: Experiment: Unsuspend all lanes on update ([#20660](facebook/react#20660)) //<Andrew Clark>// - **[a511dc709](facebook/react@a511dc709 )**: Error for deferred value and transition in Server Components ([#20657](facebook/react#20657)) //<Sebastian Markbåge>// - **[fb3f63f1a](facebook/react@fb3f63f1a )**: Remove lazy invokation of segments ([#20656](facebook/react#20656)) //<Sebastian Markbåge>// - **[895ae67fd](facebook/react@895ae67fd )**: Improve error boundary handling for unmounted subtrees ([#20645](facebook/react#20645)) //<Brian Vaughn>// - **[f15f8f64b](facebook/react@f15f8f64b )**: Store interleaved updates on separate queue until end of render ([#20615](facebook/react#20615)) //<Andrew Clark>// - **[0fd6805c6](facebook/react@0fd6805c6 )**: Land rest of effects refactor in main fork ([#20644](facebook/react#20644)) //<Andrew Clark>// - **[a6b5256a2](facebook/react@a6b5256a2 )**: Refactored recursive strict effects method to be iterative ([#20642](facebook/react#20642)) //<Brian Vaughn>// - **[3957853ae](facebook/react@3957853ae )**: Re-add "strict effects mode" for legacy roots only ([#20639](facebook/react#20639)) //<Brian Vaughn>// - **[fceb75e89](facebook/react@fceb75e89 )**: Delete remaining references to effect list ([#20625](facebook/react#20625)) //<Andrew Clark>// - **[741dcbdbe](facebook/react@741dcbdbe )**: Schedule passive phase whenever there's a deletion ([#20624](facebook/react#20624)) //<Andrew Clark>// - **[11a983fc7](facebook/react@11a983fc7 )**: Remove references to Deletion flag ([#20623](facebook/react#20623)) //<Andrew Clark>// - **[2e948e0d9](facebook/react@2e948e0d9 )**: Avoid .valueOf to close #20594 ([#20617](facebook/react#20617)) //<Dima Tisnek>// - **[2a646f73e](facebook/react@2a646f73e )**: Convert snapshot phase to depth-first traversal ([#20622](facebook/react#20622)) //<Andrew Clark>// - **[fb3e158a6](facebook/react@fb3e158a6 )**: Convert ReactSuspenseWithNoopRenderer tests to use built-in cache ([#20601](facebook/react#20601)) //<Andrew Clark>// - **[e0fd9e67f](facebook/react@e0fd9e67f )**: Use update lane priority in work loop ([#20621](facebook/react#20621)) //<Ricky>// - **[58e830448](facebook/react@58e830448 )**: Remove custom error message from hook access error ([#20604](facebook/react#20604)) //<Andrew Clark>// - **[9043626f0](facebook/react@9043626f0 )**: Cache tests: Make it easier to test many caches ([#20600](facebook/react#20600)) //<Andrew Clark>// - **[af0bb68e8](facebook/react@af0bb68e8 )**: Land #20595 and #20596 in main fork ([#20602](facebook/react#20602)) //<Andrew Clark>// - **[2b6985114](facebook/react@2b6985114 )**: build-combined: Fix failures when renaming across devices ([#20620](facebook/react#20620)) //<Sebastian Silbermann>// - **[af16f755d](facebook/react@af16f755d )**: Update DevTools to use getCacheForType API ([#20548](facebook/react#20548)) //<Brian Vaughn>// - **[95feb0e70](facebook/react@95feb0e70 )**: Convert mutation phase to depth-first traversal ([#20596](facebook/react#20596)) //<Andrew Clark>// - **[6132919bf](facebook/react@6132919bf )**: Convert layout phase to depth-first traversal ([#20595](facebook/react#20595)) //<Andrew Clark>// - **[42e04b46d](facebook/react@42e04b46d )**: Fix: Detach deleted fiber's alternate, too ([#20587](facebook/react#20587)) //<Andrew Clark>// - **[a656ace8d](facebook/react@a656ace8d )**: Deletion effects should fire parent -> child ([#20584](facebook/react#20584)) //<Andrew Clark>// - **[e6ed2bcf4](facebook/react@e6ed2bcf4 )**: Update package.json versions as part of build step ([#20579](facebook/react#20579)) //<Andrew Clark>// - **[eb0fb3823](facebook/react@eb0fb3823 )**: Build stable and experimental with same command ([#20573](facebook/react#20573)) //<Andrew Clark>// - **[e8eff119e](facebook/react@e8eff119e )**: Fix ESLint crash on empty react effect hook ([#20385](facebook/react#20385)) //<Christian Ruigrok>// - **[27659559e](facebook/react@27659559e )**: Add useRefresh hook to react-debug-tools ([#20460](facebook/react#20460)) //<Brian Vaughn>// - **[99554dc36](facebook/react@99554dc36 )**: Add Flight packages to experimental allowlist ([#20486](facebook/react#20486)) //<Andrew Clark>// - **[efc57e5cb](facebook/react@efc57e5cb )**: Add built-in Suspense cache with support for invalidation (refreshing) ([#20456](facebook/react#20456)) //<Andrew Clark>// - **[00a5b08e2](facebook/react@00a5b08e2 )**: Remove PassiveStatic optimization //<Andrew Clark>// - **[a6329b105](facebook/react@a6329b105 )**: Don't clear static flags in resetWorkInProgress //<Andrew Clark>// - **[1cf59f34b](facebook/react@1cf59f34b )**: Convert passive unmount phase to tree traversal //<Andrew Clark>// - **[ab29695a0](facebook/react@ab29695a0 )**: Defer more field detachments to passive phase //<Andrew Clark>// - **[d37d7a4bb](facebook/react@d37d7a4bb )**: Convert passive mount phase to tree traversal //<Andrew Clark>// - **[19e15a398](facebook/react@19e15a398 )**: Add PassiveStatic to trees with passive effects //<Andrew Clark>// - **[ff17fc176](facebook/react@ff17fc176 )**: Don't clear other flags when adding Deletion //<Andrew Clark>// - **[5687864eb](facebook/react@5687864eb )**: Add back disableSchedulerTimeoutInWorkLoop flag ([#20482](facebook/react#20482)) //<Ricky>// - **[9f338e5d7](facebook/react@9f338e5d7 )**: clone json obj in react native flight client host config parser ([#20474](facebook/react#20474)) //<Luna Ruan>// - **[4e62fd271](facebook/react@4e62fd271 )**: clone json obj in relay flight client host config parser ([#20465](facebook/react#20465)) //<Luna Ruan>// - **[070372cde](facebook/react@070372cde )**: [Flight] Fix webpack watch mode issue ([#20457](facebook/react#20457)) //<Dan Abramov>// - **[0f80dd148](facebook/react@0f80dd148 )**: [Flight] Support concatenated modules in Webpack plugin ([#20449](facebook/react#20449)) //<Dan Abramov>// - **[daf38ecdf](facebook/react@daf38ecdf )**: [Flight] Use lazy reference for existing modules ([#20445](facebook/react#20445)) //<Dan Abramov>// - **[3f9205c33](facebook/react@3f9205c33 )**: Regression test: SuspenseList causes lost unmount ([#20433](facebook/react#20433)) //<Andrew Clark>// - **[cdfde3ae1](facebook/react@cdfde3ae1 )**: Always rethrow original error when we replay errors ([#20425](facebook/react#20425)) //<Sebastian Markbåge>// - **[b15d6e93e](facebook/react@b15d6e93e )**: [Flight] Make PG and FS server-only ([#20424](facebook/react#20424)) //<Dan Abramov>// - **[40ff2395e](facebook/react@40ff2395e )**: [Flight] Prevent non-Server imports of aliased Server entrypoints ([#20422](facebook/react#20422)) //<Dan Abramov>// - **[94aa365e3](facebook/react@94aa365e3 )**: [Flight] Fix webpack plugin to use chunk groups ([#20421](facebook/react#20421)) //<Dan Abramov>// - **[842ee367e](facebook/react@842ee367e )**: [Flight] Rename the shared entry point ([#20420](facebook/react#20420)) //<Dan Abramov>// - **[dbf40ef75](facebook/react@dbf40ef75 )**: Put .server.js at the end of bundle filenames ([#20419](facebook/react#20419)) //<Dan Abramov>// - **[03126dd08](facebook/react@03126dd08 )**: [Flight] Add read-only fs methods ([#20412](facebook/react#20412)) //<Dan Abramov>// - **[b51a686a9](facebook/react@b51a686a9 )**: Turn on double effects for www test renderer ([#20416](facebook/react#20416)) //<Brian Vaughn>// - **[56a632adb](facebook/react@56a632adb )**: Double Invoke Effects in __DEV__ (in old reconciler fork) ([#20415](facebook/react#20415)) //<Brian Vaughn>// - **[1a2422337](facebook/react@1a2422337 )**: fixed typo ([#20351](facebook/react#20351)) //<togami2864>// - **[a233c9e2a](facebook/react@a233c9e2a )**: Rename internal cache helpers ([#20410](facebook/react#20410)) //<Dan Abramov>// - **[6a4b12b81](facebook/react@6a4b12b81 )**: [Flight] Add rudimentary FS binding ([#20409](facebook/react#20409)) //<Dan Abramov>// - **[7659949d6](facebook/react@7659949d6 )**: Clear `deletions` in `detachFiber` ([#20401](facebook/react#20401)) //<Andrew Clark>// - **[b9680aef7](facebook/react@b9680aef7 )**: Cache react-fetch results in the Node version ([#20407](facebook/react#20407)) //<Dan Abramov>// - **[cdae31ab8](facebook/react@cdae31ab8 )**: Fix typo ([#20279](facebook/react#20279)) //<inokawa>// - **[51a7cfe21](facebook/react@51a7cfe21 )**: Fix typo ([#20300](facebook/react#20300)) //<Hollow Man>// - **[373b297c5](facebook/react@373b297c5 )**: fix: Fix typo in react-reconciler docs ([#20284](facebook/react#20284)) //<Sam Zhou>// - **[1b5ca9906](facebook/react@1b5ca9906 )**: Fix module ID deduplication ([#20406](facebook/react#20406)) //<Dan Abramov>// - **[5fd9db732](facebook/react@5fd9db732 )**: [Flight] Rename react-transport-... packages to react-server-... ([#20403](facebook/react#20403)) //<Sebastian Markbåge>// - **[ce40f1dc2](facebook/react@ce40f1dc2 )**: Use assets API + writeToDisk instead of directly writing to disk ([#20402](facebook/react#20402)) //<Sebastian Markbåge>// - **[b66ae09b6](facebook/react@b66ae09b6 )**: Track subtreeFlags et al with bubbleProperties //<Andrew Clark>// - **[de75315d7](facebook/react@de75315d7 )**: Track deletions using an array on the parent //<Andrew Clark>// - **[1377e465d](facebook/react@1377e465d )**: Add Placement bit without removing others ([#20398](facebook/react#20398)) //<Andrew Clark>// - **[18d7574ae](facebook/react@18d7574ae )**: Remove `catch` from Scheduler build ([#20396](facebook/react#20396)) //<Andrew Clark>// - **[30dfb8602](facebook/react@30dfb8602 )**: [Flight] Basic scan of the file system to find Client modules ([#20383](facebook/react#20383)) //<Sebastian Markbåge>// - **[9b8060041](facebook/react@9b8060041 )**: Error when the number of parameters to a query changes ([#20379](facebook/react#20379)) //<Dan Abramov>// - **[60e4a76](facebook/react@60e4a76fa )**: [Flight] Add rudimentary PG binding ([#20372](facebook/react#20372)) //<Dan Abramov>// - **[88ef95712](facebook/react@88ef95712 )**: Fork ReactFiberLane ([#20371](facebook/react#20371)) //<Andrew Clark>// - **[41c5d00fc](facebook/react@41c5d00fc )**: [Flight] Minimal webpack plugin ([#20228](facebook/react#20228)) //<Dan Abramov>// - **[e23673b51](facebook/react@e23673b51 )**: [Flight] Add getCacheForType() to the dispatcher ([#20315](facebook/react#20315)) //<Dan Abramov>// - **[555eeae33](facebook/react@555eeae33 )**: Add disableNativeComponentFrames flag ([#20364](facebook/react#20364)) //<Philipp Spiess>// - **[148ffe3cf](facebook/react@148ffe3cf )**: Failing test for Client reconciliation ([#20318](facebook/react#20318)) //<Dan Abramov>// - **[a2a025537](facebook/react@a2a025537 )**: Fixed invalid DevTools work tags ([#20362](facebook/react#20362)) //<Brian Vaughn>// - **[5711811da](facebook/react@5711811da )**: Reconcile element types of lazy component yielding the same type ([#20357](facebook/react#20357)) //<Sebastian Markbåge>// - **[3f73dcee3](facebook/react@3f73dcee3 )**: Support named exports from client references ([#20312](facebook/react#20312)) //<Sebastian Markbåge>// - **[565148d75](facebook/react@565148d75 )**: Disallow *.server.js imports from any other files ([#20309](facebook/react#20309)) //<Sebastian Markbåge>// - **[e6a0f2763](facebook/react@e6a0f2763 )**: Profiler: Improve nested-update checks ([#20299](facebook/react#20299)) //<Brian Vaughn>// - **[d93b58a5e](facebook/react@d93b58a5e )**: Add flight specific entry point for react package ([#20304](facebook/react#20304)) //<Sebastian Markbåge>// - **[a81c02ac1](facebook/react@a81c02ac1 )**: Profiler onNestedUpdateScheduled accepts id as first param ([#20293](facebook/react#20293)) //<Brian Vaughn>// - **[ac2cff4b1](facebook/react@ac2cff4b1 )**: Warn if commit phase error thrown in detached tree ([#20286](facebook/react#20286)) //<Andrew Clark>// - **[0f83a64ed](facebook/react@0f83a64ed )**: Regression test: Missing unmount after re-order ([#20285](facebook/react#20285)) //<Andrew Clark>// - **[ebf158965](facebook/react@ebf158965 )**: Add best-effort documentation for third-party renderers ([#20278](facebook/react#20278)) //<Dan Abramov>// - **[82e99e1b0](facebook/react@82e99e1b0 )**: Add Node ESM Loader and Register Entrypoints ([#20274](facebook/react#20274)) //<Sebastian Markbåge>// - **[bf7b7aeb1](facebook/react@bf7b7aeb1 )**: findDOMNode: Remove return pointer mutation ([#20272](facebook/react#20272)) //<Andrew Clark>// - **[369c3db62](facebook/react@369c3db62 )**: Add separate ChildDeletion flag ([#20264](facebook/react#20264)) //<Andrew Clark>// - **[765e89b90](facebook/react@765e89b90 )**: Reset new fork to old fork ([#20254](facebook/react#20254)) //<Andrew Clark>// - **[7548dd573](facebook/react@7548dd573 )**: Properly reset Profiler nested-update flag ([#20253](facebook/react#20253)) //<Brian Vaughn>// - **[b44e4b13a](facebook/react@b44e4b13a )**: Check for deletions in `hadNoMutationsEffects` ([#20252](facebook/react#20252)) //<Andrew Clark>// - **[3ebf05183](facebook/react@3ebf05183 )**: Add new effect fields to old fork, and vice versa ([#20246](facebook/react#20246)) //<Andrew Clark>// - **[2fbcc9806](facebook/react@2fbcc9806 )**: Remove cycle between ReactFiberHooks and ReactInternalTypes ([#20242](facebook/react#20242)) //<Paul Doyle>// - **[504222dcd](facebook/react@504222dcd )**: Add Node ESM build option ([#20243](facebook/react#20243)) //<Sebastian Markbåge>// - **[1b96ee444](facebook/react@1b96ee444 )**: Remove noinline directives from new commit phase ([#20241](facebook/react#20241)) //<Andrew Clark>// - **[760d9ab57](facebook/react@760d9ab57 )**: Scheduling profiler tweaks ([#20215](facebook/react#20215)) //<Brian Vaughn>// - **[9403c3b53](facebook/react@9403c3b53 )**: Add Profiler callback when nested updates are scheduled ([#20211](facebook/react#20211)) //<Brian Vaughn>// - **[62efd9618](facebook/react@62efd9618 )**: [email protected] //<Dan Abramov>// - **[e7006d67d](facebook/react@e7006d67d )**: Widen peer dependency range of use-subscription ([#20225](facebook/react#20225)) //<Billy Janitsch>// - **[15df051c9](facebook/react@15df051c9 )**: Add warning if return pointer is inconsistent ([#20219](facebook/react#20219)) //<Andrew Clark>// - **[9aca239f1](facebook/react@9aca239f1 )**: Improved dev experience when DevTools hook is disabled ([#20208](facebook/react#20208)) //<Alphabet Codes>// - **[12627f93b](facebook/react@12627f93b )**: Perform hasOwnProperty check in Relay Flight ([#20220](facebook/react#20220)) //<Sebastian Markbåge>// - **[163199d8c](facebook/react@163199d8c )**: Dedupe module id generation ([#20172](facebook/react#20172)) //<Sebastian Markbåge>// - **[76a6dbcb9](facebook/react@76a6dbcb9 )**: [Flight] Encode Symbols as special rows that can be referenced by models … ([#20171](facebook/react#20171)) //<Sebastian Markbåge>// - **[35e53b465](facebook/react@35e53b465 )**: [Flight] Simplify Relay row protocol ([#20168](facebook/react#20168)) //<Sebastian Markbåge>// - **[16e6dadba](facebook/react@16e6dadba )**: Encode throwing server components as lazy throwing references ([#20217](facebook/react#20217)) //<Sebastian Markbåge>// - **[c896cf961](facebook/react@c896cf961 )**: Set return pointer when reusing current tree ([#20212](facebook/react#20212)) //<Andrew Clark>// - **[089866015](facebook/react@089866015 )**: Add version of scheduler that only swaps MessageChannel for postTask ([#20206](facebook/react#20206)) //<Ricky>// - **[393c452e3](facebook/react@393c452e3 )**: Add "nested-update" phase to Profiler API ([#20163](facebook/react#20163)) //<Brian Vaughn>// - **[13a62feab](facebook/react@13a62feab )**: Fix path for SchedulerFeatureFlags ([#20200](facebook/react#20200)) //<Ricky>// - **[7a73d6a0f](facebook/react@7a73d6a0f )**: (Temporarily) revert unmounting error boundaries changes ([#20147](facebook/react#20147)) //<Brian Vaughn>// - **[c29710a57](facebook/react@c29710a57 )**: fix: useImperativeMethods to useImperativeHandle ([#20194](facebook/react#20194)) //<Jack Works>// - **[f8979e0e2](facebook/react@f8979e0e2 )**: Revert 'Fabric-compatible implementation of feature' and have Fabric noop when setJSResponder is called for now ([#21009](facebook/react#21009)) //<Joshua Gross>// - **[c9f6d0a3a](facebook/react@c9f6d0a3a )**: Sync `ReactNativeTypes` from React Native ([#21015](facebook/react#21015)) //<Timothy Yung>// Changelog: [General][Changed] - React Native sync for revisions c3e20f1...c9f6d0a jest_e2e[run_all_tests] Reviewed By: PeteTheHeat Differential Revision: D27051885 fbshipit-source-id: 5b232f6093f5f2527f3b321bc8b5487934e92d70
* Copy some infra structure patterns from Flight * Basic data structures * Move structural nodes and instruction commands to host config * Move instruction command to host config In the DOM this is implemented as script tags. The first time it's emitted it includes the function. Future calls invoke the same function. The side of the complete boundary function in particular is unfortunately large. * Implement Fizz Noop host configs This is implemented not as a serialized protocol but by-passing the serialization when possible and instead it's like a live tree being built. * Implement React Native host config This is not wired up. I just need something for the flow types since Flight and Fizz are both handled by the isServerSupported flag. Might as well add something though. The principle of this format is the same structure as for HTML but a simpler binary format. Each entry is a tag followed by some data and terminated by null. * Check in error codes * Comment
Does that imply that some details of this feature are specific to Node.js, or will it work with other runtimes too and you're just targeting Node for an initial prototype? |
We have separate builds for separate environments since not all APIs are standardized. Another environment that's supported is Service Workers which uses W3C streams. Not all environments are equal because there are subtle details in the implementations or limitations in the low level capabilities that they expose. None are really exposing all the perfect details so we can't get perfect. Subtle flaws in each. Node's specific APIs expose the most feature complete. Service Worker streams have some flaws like the scheduling doesn't provide I/O batching guarantees. Also the way readable streams are set up, they buffer into a queue immediately and not when the receiver is actually able to use them. There's also no way to flush gzip streams early so if we have some data ready but not enough to fill a window, we can't send that early. I guess you're asking about ASP.NET. We probably could support a build there if there's a sufficient scheduling API and streaming API we could target. See this file for the kind of things that needs to be implemented. https://github.com/facebook/react/blob/master/packages/react-server/src/ReactServerStreamConfigNode.js |
@sebmarkbage I wasn't thinking of anything in particular, I was just curious about whether there's any hard dependency on Node. There's a bunch of common JS runtimes that aren't Node.js, for example:
Cloudflare workers in particular is pretty interesting in that the worker code runs on their CDN nodes all over the world, which can really help with perf in some cases (as the code is running closer to the user). |
A lot of those examples use standard W3C streams which we already support in the "browser" build. It's just a matter of getting the automatic environment configuration set up. Conditional exports probably. Unfortunately they have the limitations mentioned above, so we can't get the ideal performance out of those environments. But it'll work. If they exposed some other API or extended API we could add special builds for each environment. |
* Copy some infra structure patterns from Flight * Basic data structures * Move structural nodes and instruction commands to host config * Move instruction command to host config In the DOM this is implemented as script tags. The first time it's emitted it includes the function. Future calls invoke the same function. The side of the complete boundary function in particular is unfortunately large. * Implement Fizz Noop host configs This is implemented not as a serialized protocol but by-passing the serialization when possible and instead it's like a live tree being built. * Implement React Native host config This is not wired up. I just need something for the flow types since Flight and Fizz are both handled by the isServerSupported flag. Might as well add something though. The principle of this format is the same structure as for HTML but a simpler binary format. Each entry is a tag followed by some data and terminated by null. * Check in error codes * Comment
This doesn't actually work yet but I need some initial shell to anchor the infra on and this seems like a good point to check it in.
This includes the structure of everything novel about Fizz. I'll still need to pull in a bunch of stuff from Partial Renderer but everything that's sync can behave more or less the same.
TL;DR
Basically, we want to support suspense on the server. The simple mode would just be to wait until everything resolves and then write it. The advanced mode emits a shell in place HTML and uses embedded script tags to inject more content as it completes. The resulting stream ends up looking something like this:
Principles
The basic principle of the design is that we'll try to prepare as much content ahead of time as possible. Meaning we'll use as much CPU as we can. This will build up the output in a priority queue.
Eventually we'll be blocked on network input. Once blocked we'll try to emit as much as possible onto the network output but only as much as the network can actually handle. Ideally we should have low level access to the network and not a bunch of proxies buffering in the way. Because if it's not actually going to be sent to the user, it's better to wait a bit because we can emit higher priority content or more efficient format if we wait a bit.
Any completed content can always be emitted on the network but we rely on Suspense boundaries to know when something is ready to be displayed as a unit.
SSG and streaming SSR has slightly different tradeoffs. For streaming SSR, the ideal order over the network is whatever is ready. E.g. it's better to emit lower pri content first if the higher pri content isn't ready. For SSG and cached SSR (i.e. on-demand SSG), the ideal order over the network is always higher pri content first even if it didn't load first.
Another principle is that SSG should yield deterministic output. Therefore the system is carefully designed to yield deterministic output regardless of when I/O.
Execution Model
The execution model is split into two section (same as Flight):
Every round in the Node event loop starts by performing work based on what I/O has been unblocked. After all I/O has been resolved and all work computed, we begin flushing what we've completed so far.
The host can control when to start flushing. E.g. if you want to generate only HTML with no script tags, you would wait until the whole tree is done before flushing. If you want optimal streaming perf you'd start flushing as soon as possible. If you want to ensure things like react-helmet work, you'd start streaming after you've reached some point in the tree and don't need to affect the header anymore. If you want maximize HTML for SEO, but don't want to compromise your ranking due to slow responses, you might flush after a timeout.
Normally if you wait until the end to flush, you only get a completed HTML tree. However, there's also a setting that configures the maximum byte size a Suspense boundary level is allowed to have. This lets you split a large page so that it loads incrementally. E.g. the shell would show first and then each Suspense boundary would fill in with content later in the stream. This is useful even in SSG mode and without streaming because the byte stream over the network still will reach the client incrementally. For SEO you might want to use a higher limit or infinity to avoid this heuristic.
Data Structures
The hierarchy of the data structures and concepts is something like this:
The request is made up of a tree of Suspense boundaries. Each boundary is made up of a tree of Segments. Each segment is made up of a number of chunks which is basically just strings or buffers - pieces of HTML.
For anything we can render synchronously we just fill up a single Segment with chunks of HTML. When something suspends, we split that into a child Segment. We can still emit the HTML we've completed so far to the client. However, in this case it needs to remain hidden. We then build up the Segments as they complete. Once all Segments in a SuspenseBoundary is complete, we can inject that into the main tree. When a SuspenseBoundary has other SuspenseBoundaries as children, the parent is considered complete if the fallback is component. The fallback segments are considered part of the parent SuspenseBoundary, not the child.
Conceptually, we just emit segments and boundaries independently at the end. However, as an optimization we typically will inline segments and suspense boundaries into their parent as they're written.
This works by building up a tree with a single root. If you flush before it's done it will flush as deeply as it can and inline everything and leave some holes. The next time you flush, it'll start flushing the remaining segments that weren't completed the first time.
As a result, if you wait until the end you get a completed HTML tree.
The root must always complete fully before we emit anything. I.e. up until the first SuspenseBoundary. It's best practice to have a Suspense boundary at the root.
Error Handling
The principle for error handling on the server is to report it to a logger. This can be used to set the status code if the error happens before the first flush, or logged for instrumentation.
If the error happens at the root or while rendering the root most Suspense boundary's fallback, that's a fatal error. We'll just kill the stream and give up. That's not a good experience. The expectation is that you have a Suspense boundary at the root and that its fallback never throws.
If the error happens inside a Suspense boundary, it triggers that boundary to go into "client rendered mode". The server will render its fallback state and then during hydration, it'll be rendered by the client and replaced. This lets an error fix itself if it was a temporary error that only happens on the server. This is better than showing an error state and then it fixes itself automatically on the client. If the error happens again on the client, it'll show up on the client.
Error boundaries are not considered at all in this architecture.
cc @nickbalestra