Adding Server Component support to DevTools is going to be a big lift, but generally speaking there are a few initial things to work out:
- Updating the Flight server renderer to stream server component names/types to the client.
- Adding a new hook to DevTools for the Flight client to call.
- Merging the server component subtrees into the DevTools client tree.
Yesterday and today I've been thinking about this last item, and I'm feeling pretty stumped at the moment. Merging the trees isn't that difficult, but preserving the tree across client updates gets nasty when it comes to things like conditionally rendered elements.
Setting performance concerns aside, even if I were to undo the merged trees, apply the client updates, and then redo the merge– I'm still not sure we would definitely end up with the correct final state.
For example, consider the following client component that accepts "children" rendered by a server component:
function StatefulClientComponent({ children = null }) {
// State...
return (
<React.Fragment>
{condition && <ClientComponent key="A" />}
{children}
{condition && <ClientComponent key="B" />}
</React.Fragment>
);
}
First, DevTools would see this tree as only:
<StatefulClientComponent>
Then server component info could be added in, e.g.
<StatefulClientComponent>
<ServerComponent>
If condition
were to be true, DevTools should update its (merged) tree to:
<StatefulClientComponent>
<ClientComponent key="A">
<ServerComponent>
<ClientComponent key="B">
But I'm not sure how it should know that "A" goes before ServerComponent
and "C" goes after.
The way DevTools appends children further complicates things: First it sends "create" commands for new children, then it sends a "reorder" command (consisting of only the child ids). In this case, that means that the reorder command would only specify "A" and "B" (and DevTools would need to know to leave the ServerComponent
in place).
Maybe that's not so difficult to do in this simple example, but what if the server component rendered content (passed as children
) contained nested client components? Then the initial DevTools tree might look something like this:
<StatefulClientComponent>
<NestedClientComponent>
And the merged tree would look like this:
<StatefulClientComponent>
<ServerComponent>
<NestedClientComponent>
Now when there's an update, DevTools would tell the frontend that StatefulClientComponent
has 3 children: "A", NestedClientComponent
, and "B". So DevTools would need to both ignore children that had previously been reparented and ignore server components when re-ordering.
Thinking outside of the box, what if React were to create Fibers for server components– a new type of Fiber that worked as a pass-through like
Fragment
orStrictMode
? It could only do this when DevTools was present (isDevToolsPresent
) to avoid adding memory unnecessarily. This would make DevTools integration much easier.