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.
I think the direction above is promising, assuming it actually handles the update cases like I think it can, but there's a secondary level of things I haven't yet thought about how to handle: the
children
andparent
pointers for each element. The Store's tree state can use the edge overrides, but individual elements will still point tochildren
andparents
without the overrides. Seems like this will cause problems.Spitballing here, but maybe I could make these pointers "private" attributes, and redirect all reading of them through public methods on the Store (where the edges map could be checked) but this feels very heavy handed.