Skip to content

[pull] main from react:main#509

Merged
pull[bot] merged 5 commits into
LoadsAForks:mainfrom
react:main
Jun 18, 2026
Merged

[pull] main from react:main#509
pull[bot] merged 5 commits into
LoadsAForks:mainfrom
react:main

Conversation

@pull

@pull pull Bot commented Jun 18, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

gaojude and others added 5 commits June 18, 2026 17:07
`getPostponedState` snapshots `request.nextSegmentId` at `onAllReady`,
before the Fizz stream is flowing. At this point we have visited all
Suspense boundaries and know which ones suspended by user code or not.
However, only when the stream is flowing are we counting the size of
each boundary. When we detect large boundaries, we suspend them i.e. we
outline them instead of keeping them inline. This results in more
segments being written while the postponed state holds a stale count.

We now keep a reference to the returned postponed state and mutate the
segment IDs when we outline. That way serializing `postponed` after the
`prelude` has flushed writes the latest postponed state.

The current API design means that you can potentially serialize stale
postponed state. We're considering a redesign to make these issues
impossible (e.g. #36815).

For now, the postponed state should only be serialized or passed onto a
`resume` once the `prelude` has flushed e.g.:

```js
const { createWriteStream, writeFileSync } = require('node:fs');
const { createWriteStream } = require('node:stream');

const { prelude, postponed } = prenderToNodeStream(...)
// serializing `postponed` now would write a stale state.
// serialize prelude
const destination = createWriteStream('prelude.html')

prelude.pipe(destination)

await finished(prelude)
// now we can serialize postponed
writeFileSync('postponed.json', JSON.stringify(postponed))
```

---------

Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
Adds the component-tree building blocks and the `createTools(facade)`
assembler — the first tools layered on top of the `installFacade` hook
from commit 1.

### `createTools(facade): Tools`

Reads the facade's tracked state (fiber roots + per-renderer internals)
and returns a plain `Tools` object — no globals; the integrator decides
what to do with it. Tools return **typed, plain JavaScript values** (or
`{error}`); serialization (to an integration package's wire format) is
left to the caller.

### Tools

- **`getComponentTree(depth?, rootUid?)`** — the component tree as a
flat array of `{uid, type, name, key, firstChild, nextSibling}` nodes
(an adjacency list referencing other nodes by label).
- **`getComponentByUid(uid)`** — one component's `{type, name, key?,
props?, hooks?}`. For function components, `hooks` is the inspected
hooks tree (nested `subHooks`), obtained via `react-debug-tools'`
`inspectHooksOfFiberWithoutDefaultDispatcher` with the renderer's
injected dispatcher (normalized by `getDispatcherRef`) — so hooks
introspection never falls back to, or bundles, React's shared internals.
- **`findComponents(name, rootUid?, page?, pageSize?)`** — paginated,
case-insensitive name search.
- **`getComponentSource(uid)`** — the component's definition location
`{name, fileName, line, column}` (or `null`).
- **`getOwnersStack(uid)`** — the raw JSX owner-stack string (DEV only).
- **`getOwnersBranch(uid)`** — the structured owner chain `[{uid, name,
type}]`, ordered immediate owner → root (DEV only).

### UIDs

Components are addressed by stable `rN` uids, assigned lazily and
memoized per fiber (and its alternate), so a component keeps the same
uid across re-renders and across every tool. These uids don't survive
page reloads.
Adds the profiler building blocks to `createTools` — per-commit render
timing on top of the component-tree tools from commit 2.

### Tools

- **`startProfiling(traceName?)`** → `{status: 'started', trace}`.
Begins a session that records timing on every commit. Errors if a
session is already active.
- **`stopProfiling()`** → `{status: 'stopped', traceName, commits}`
(commit count). Errors if no session is active.
- **`getTraceOverview(traceName)`** → one row per commit: `{commit,
committedAt, renderDuration, layoutDuration, passiveDuration,
componentsChanged}`.
- **`getCommitReport(traceName, commitIndex)`** → one commit's detail:
`{committedAt, priority, renderDuration, layoutDuration,
passiveDuration,
components}`, where `components` is `{label, name, type, actualDuration,
  selfDuration}` sorted by `actualDuration` descending.

Durations are in milliseconds, or `null` when the build does not collect
profiler
timing. `passiveDuration` is attributed per root via the hook's
post-commit pass.
This PR just adds more tests that validate Facade's implementation
against setups with multiple React roots or Renderers.
…#36682)

Makes `installFacade` usable on pages that already have a DevTools
backend — most importantly the **React DevTools browser extension** — by
attaching to the existing `__REACT_DEVTOOLS_GLOBAL_HOOK__` instead of
refusing to install.

This is important, because otherwise if the page had Facade installed,
the user won't be able to use React DevTools browser extension.
@pull pull Bot locked and limited conversation to collaborators Jun 18, 2026
@pull pull Bot added the ⤵️ pull label Jun 18, 2026
@pull pull Bot merged commit e92ecef into LoadsAForks:main Jun 18, 2026
15 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants