Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bvaughn/ba900134bf581fafb3e8d89c7c2bd6d2 to your computer and use it in GitHub Desktop.
Save bvaughn/ba900134bf581fafb3e8d89c7c2bd6d2 to your computer and use it in GitHub Desktop.
Merging client and server trees

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.

@bvaughn
Copy link
Author

bvaughn commented Jun 5, 2021

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 or StrictMode? It could only do this when DevTools was present (isDevToolsPresent) to avoid adding memory unnecessarily. This would make DevTools integration much easier.

@bvaughn
Copy link
Author

bvaughn commented Jun 7, 2021

Another dimension of this that we haven't even considered yet is component filtering.

If Server Components are just a new/special Fiber type then component filtering "just works".

If they're a separate thing that DevTools needs to merge in, then it has to also filter them (which could make the merge difficult), and preserve the metadata somewhere so that changing component filters doesn't break the tree.

As a side note, should we add a new filter-type for Server vs Client components?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment