Repo deep dive

facebook/react

React installs as two packages but is developed as one monorepo, and the split inside it is the architecture: a reconciler that knows no platform, a binding layer per host, and a scheduler that decides when any of it is allowed to run.

entry

react

packages/react

The package you import from, and it renders nothing at all. It creates element objects, declares the hook and context APIs, and forwards every hook call to whichever renderer is currently mounting — the implementation lives there, not here.

import { useState } from 'react'
core

react-reconciler

packages/react-reconciler

The algorithm itself, with no idea what a DOM is. It builds and diffs the fiber tree, calls your components, stores their hook state, and hands the resulting mutations to a host config you supply. It publishes as a package so you can write your own renderer.

const Renderer = Reconciler(hostConfig)
6 sub-nodes →
core

scheduler

packages/scheduler

A priority queue and a yield loop, with nothing React-specific in it. It runs callbacks in slices and hands control back to the browser every few milliseconds, so a long render cannot sit on the main thread and block paint or input.

scheduleCallback(NormalPriority, work)
renderer

react-dom

packages/react-dom

The browser entry point — createRoot, hydrateRoot, createPortal, flushSync. It is a thin public surface over the reconciler wired to the DOM host config, which is why it is a separate install from react in the first place.

createRoot(document.getElementById('root'))
  .render(<App />)
renderer

react-dom-bindings

packages/react-dom-bindings

Everything that actually knows about the DOM: creating and patching nodes, deciding attribute versus property, and the synthetic event system — one delegated listener per root that replays the bubble along the fiber tree rather than the DOM one.

// one listener on the root container,
// dispatched down the fiber path
renderer

react-native-renderer

packages/react-native-renderer

The same reconciler pointed at native views instead of elements. It sits in this repo next to react-dom because that is the proof the split holds: swap the host config and the whole component model comes with you unchanged.

// same fibers, different host nodes
server

react-server

packages/react-server · react-server-dom-*

Server Components: a renderer whose output is a stream, not a tree of nodes. It serialises the rendered output into the Flight format and leaves holes where client components go, which the bundler binding fills with chunk references the browser can fetch.

renderToPipeableStream(<Page />, manifest)
server

react-dom/server

packages/react-dom/src/server

Classic server rendering: walk the tree once, push HTML strings into a stream, never diff anything. It also plants the markers hydrateRoot reads on the client so the first render can adopt the existing DOM instead of replacing it.

const stream = renderToPipeableStream(<App />)
internal

shared

packages/shared

The pieces every other package needs and none of them should own: the element and portal symbols, the feature flags each build is compiled against, and ReactSharedInternals — the single mutable slot through which react and a renderer find each other.

import { REACT_ELEMENT_TYPE } from 'shared/ReactSymbols'
internal

react-is

packages/react-is

Element type checks, published so that libraries inspecting children can ask the same question React asks — isValidElement, isFragment, typeOf. The alternative is every library guessing at symbols it does not own.

isValidElement(node)
tooling

react-refresh

packages/react-refresh

Fast Refresh. A Babel transform registers each component under a stable identity, and this runtime swaps the implementation in place and re-renders, keeping the hook state of every component whose hook signature did not change.

// edit a component, keep its state
tooling

eslint-plugin-react-hooks

packages/eslint-plugin-react-hooks

The rules of hooks, enforced before the code runs. Hook state is matched by call order, so a hook behind an if silently reads the wrong slot — a class of bug the runtime cannot always catch, shipped as a lint rule from the same repo instead.

react-hooks/rules-of-hooks
react-hooks/exhaustive-deps