bippy 0.6.1-dev.3a24abf → 0.6.1-dev.4ed5147
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +177 -478
- package/dist/core.cjs +1 -1
- package/dist/core.d.cts +39 -87
- package/dist/core.d.ts +39 -87
- package/dist/core.js +1 -1
- package/dist/core2.cjs +1 -1
- package/dist/core2.d.cts +11 -3
- package/dist/core2.d.ts +11 -3
- package/dist/core2.js +1 -1
- package/dist/{types.d.cts → errors.d.cts} +187 -68
- package/dist/{types.d.ts → errors.d.ts} +187 -68
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.d.cts +8 -0
- package/dist/install-hook-only.d.ts +8 -0
- package/dist/install-hook-only.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +13 -14
- package/dist/source.d.cts +86 -69
- package/dist/source.d.ts +86 -69
- package/dist/source.js +13 -14
- package/package.json +14 -7
- package/src/core.ts +245 -629
- package/src/errors.ts +34 -0
- package/src/index.ts +1 -0
- package/src/install-hook-only.ts +2 -2
- package/src/rdt-hook.ts +152 -110
- package/src/react-internals/generated/react-work-tags.ts +318 -0
- package/src/react-internals/index.ts +67 -0
- package/src/react-internals/semver.ts +80 -0
- package/src/{types.ts → react-internals/types.ts} +13 -86
- package/src/react.ts +76 -0
- package/src/source/error-stack.ts +11 -0
- package/src/source/get-display-name-from-source.ts +44 -40
- package/src/source/get-source.ts +42 -26
- package/src/source/index.ts +11 -1
- package/src/source/inspect-hooks.ts +220 -207
- package/src/source/owner-stack.ts +86 -128
- package/src/source/parse-debug-stack.ts +4 -4
- package/src/source/parse-hook-names.ts +14 -53
- package/src/source/parse-stack.ts +14 -31
- package/src/source/renderer-dispatchers.ts +30 -0
- package/src/source/symbolication.ts +703 -135
- package/dist/index.iife.js +0 -9
- package/dist/install-hook-only.iife.js +0 -9
- package/src/generated/react-work-tags.ts +0 -169
- package/src/react-internals.ts +0 -67
- package/src/unsubscribe.ts +0 -17
package/README.md
CHANGED
|
@@ -1,633 +1,332 @@
|
|
|
1
|
-
>
|
|
2
|
-
> ⚠️⚠️⚠️ **this project may break production apps and cause unexpected behavior** ⚠️⚠️⚠️
|
|
3
|
-
>
|
|
4
|
-
> this project uses react internals, which can change at any time. we don't recommend depending on internals unless you really, _really_ have to. by proceeding, you acknowledge the risk of breaking your own code or apps that use your code.
|
|
5
|
-
|
|
6
|
-
# <img src="https://github.com/aidenybai/bippy/blob/main/.github/public/bippy.png?raw=true" width="60" align="center" /> bippy
|
|
1
|
+
<h1><img src="./.github/public/bippy.png" width="48" alt="" align="middle" /> bippy</h1>
|
|
7
2
|
|
|
8
3
|
[](https://npmjs.com/package/bippy)
|
|
9
4
|
[](https://npmjs.com/package/bippy)
|
|
10
5
|
|
|
11
|
-
bippy
|
|
12
|
-
|
|
13
|
-
by default, you cannot access react internals. bippy bypasses this by “pretending” to be react devtools, giving you access to the fiber tree and other internals.
|
|
14
|
-
|
|
15
|
-
- works outside of react: no react code modification needed
|
|
16
|
-
- utility functions that work across modern react (v17-19)
|
|
17
|
-
- no prior react source code knowledge required
|
|
18
|
-
|
|
19
|
-
```jsx
|
|
20
|
-
import { instrument, traverseFiber } from "bippy"; // must be imported BEFORE react
|
|
21
|
-
|
|
22
|
-
instrument({
|
|
23
|
-
onCommitFiberRoot(rendererID, root) {
|
|
24
|
-
traverseFiber(root.current, (fiber) => {
|
|
25
|
-
// prints every fiber in the current React tree
|
|
26
|
-
console.log("fiber:", fiber);
|
|
27
|
-
});
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## how it works & motivation
|
|
33
|
-
|
|
34
|
-
bippy allows you to **access** and **use** react fibers **outside** of react components.
|
|
35
|
-
|
|
36
|
-
a react fiber is a “unit of execution.” this means react will do something based on the data in a fiber. each fiber either represents a composite (function/class component) or a host (dom element).
|
|
37
|
-
|
|
38
|
-
> here is a [live visualization](https://jser.pro/ddir/rie?reactVersion=18.3.1&snippetKey=hq8jm2ylzb9u8eh468) of what the fiber tree looks like, and here is a [deep dive article](https://jser.dev/2023-07-18-how-react-rerenders/).
|
|
39
|
-
|
|
40
|
-
fibers are useful because they contain information about the react app (component props, state, contexts, etc.). a simplified version of a fiber looks roughly like this:
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
interface Fiber {
|
|
44
|
-
// component type (function/class)
|
|
45
|
-
type: any;
|
|
46
|
-
|
|
47
|
-
child: Fiber | null;
|
|
48
|
-
sibling: Fiber | null;
|
|
6
|
+
bippy hacks into React internals.
|
|
49
7
|
|
|
50
|
-
|
|
51
|
-
stateNode: Node | null;
|
|
8
|
+
React normally keeps its Fiber tree out of reach. bippy gets you in, so you can inspect components, track renders, and access the renderer directly.
|
|
52
9
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
- `
|
|
82
|
-
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
here's what it roughly looks like:
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
interface __REACT_DEVTOOLS_GLOBAL_HOOK__ {
|
|
92
|
-
// list of renderers (react-dom, react-native, etc.)
|
|
93
|
-
renderers: Map<RendererID, reactRenderer>;
|
|
94
|
-
|
|
95
|
-
// called when react has rendered everything for an update and the fiber tree is fully built and ready to
|
|
96
|
-
// apply changes to the host tree (e.g. DOM mutations)
|
|
97
|
-
onCommitFiberRoot: (rendererID: RendererID, root: FiberRoot, commitPriority?: number) => void;
|
|
98
|
-
|
|
99
|
-
// called when effects run
|
|
100
|
-
onPostCommitFiberRoot: (rendererID: RendererID, root: FiberRoot) => void;
|
|
101
|
-
|
|
102
|
-
// called when a specific fiber unmounts
|
|
103
|
-
onCommitFiberUnmount: (rendererID: RendererID, fiber: Fiber) => void;
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
bippy works by monkey-patching `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with our own custom handlers. bippy simplifies this by providing utility functions like:
|
|
108
|
-
|
|
109
|
-
- `instrument` to safely patch `window.__REACT_DEVTOOLS_GLOBAL_HOOK__`
|
|
110
|
-
- _(instead of directly mutating `onCommitFiberRoot`, …)_
|
|
111
|
-
- `traverseRenderedFibers` to traverse the fiber tree and determine which fibers have actually rendered
|
|
112
|
-
- _(instead of `child`, `sibling`, and `return` pointers)_
|
|
113
|
-
- `traverseFiber` to traverse the fiber tree, regardless of whether it has rendered
|
|
114
|
-
- _(instead of `child`, `sibling`, and `return` pointers)_
|
|
115
|
-
- `setFiberId` / `getFiberId` to set and get a fiber's id
|
|
116
|
-
- _(instead of anonymous fibers with no identity)_
|
|
117
|
-
|
|
118
|
-
## how to use
|
|
119
|
-
|
|
120
|
-
we recommend installing via npm.
|
|
121
|
-
|
|
122
|
-
import this package before your react app runs. it adds a special object to the global scope that react reports its internals to (react devtools uses the same mechanism). as soon as react loads and attaches, bippy starts collecting data about what is going on in react's internals.
|
|
10
|
+
> [!WARNING]
|
|
11
|
+
> ⚠️⚠️⚠️ **This project may break production apps and cause unexpected behavior.** ⚠️⚠️⚠️
|
|
12
|
+
>
|
|
13
|
+
> This project uses React internals, which can change at any time. We don't recommend depending on internals unless you really, _really_ have to. By proceeding, you acknowledge the risk of breaking your own code or apps that use your code.
|
|
14
|
+
|
|
15
|
+
## Table of contents
|
|
16
|
+
|
|
17
|
+
- [Install bippy](#install-bippy)
|
|
18
|
+
- [Next.js](#nextjs)
|
|
19
|
+
- [Vite](#vite)
|
|
20
|
+
- [React integration](#react-integration)
|
|
21
|
+
- [`useFiber`](#usefiber)
|
|
22
|
+
- [Instrumentation](#instrumentation)
|
|
23
|
+
- [`instrument`](#instrument)
|
|
24
|
+
- [`getRDTHook`](#getrdthook)
|
|
25
|
+
- [Fiber traversal](#fiber-traversal)
|
|
26
|
+
- [`traverseRenderedFibers`](#traverserenderedfibers)
|
|
27
|
+
- [`traverseFiber`](#traversefiber)
|
|
28
|
+
- [`didFiberRender` and `didFiberCommit`](#didfiberrender-and-didfibercommit)
|
|
29
|
+
- [Fiber inspection](#fiber-inspection)
|
|
30
|
+
- [`setFiberId` and `getFiberId`](#setfiberid-and-getfiberid)
|
|
31
|
+
- [Classification helpers](#classification-helpers)
|
|
32
|
+
- [`getDisplayName` and `getType`](#getdisplayname-and-gettype)
|
|
33
|
+
- [`getFiber`](#getfiber)
|
|
34
|
+
- [`getLatestFiber`](#getlatestfiber)
|
|
35
|
+
- [`getRenderer`](#getrenderer)
|
|
36
|
+
- [React internals](#react-internals)
|
|
37
|
+
- [Source inspection](#source-inspection)
|
|
38
|
+
- [`getSource`](#getsource)
|
|
39
|
+
- [`getOwnerStack` and `getParentStack`](#getownerstack-and-getparentstack)
|
|
40
|
+
- [Acknowledgements](#acknowledgements)
|
|
41
|
+
|
|
42
|
+
## Install bippy
|
|
43
|
+
|
|
44
|
+
Install bippy:
|
|
123
45
|
|
|
124
46
|
```shell
|
|
125
47
|
npm install bippy
|
|
126
48
|
```
|
|
127
49
|
|
|
128
|
-
|
|
50
|
+
Import bippy before React or any React renderer.
|
|
129
51
|
|
|
130
|
-
###
|
|
52
|
+
### Next.js
|
|
131
53
|
|
|
132
|
-
|
|
54
|
+
Next.js 15.3 and later can load bippy through [`instrumentation-client.ts`](https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation-client). Create the file at the project root or in `src`:
|
|
133
55
|
|
|
134
56
|
```typescript
|
|
135
|
-
// instrumentation-client.ts
|
|
136
57
|
import "bippy";
|
|
137
58
|
```
|
|
138
59
|
|
|
139
|
-
|
|
60
|
+
### Vite
|
|
140
61
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
in vite, import bippy at the very top of your main entry point (typically `src/main.tsx` or `src/main.ts`) before any react imports:
|
|
62
|
+
Import bippy at the top of your Vite entry point, before any React imports:
|
|
144
63
|
|
|
145
64
|
```typescript
|
|
146
|
-
// src/main.tsx
|
|
147
65
|
import "bippy";
|
|
148
66
|
import { StrictMode } from "react";
|
|
149
67
|
import { createRoot } from "react-dom/client";
|
|
150
|
-
|
|
151
|
-
// ... rest of your code
|
|
152
68
|
```
|
|
153
69
|
|
|
154
|
-
|
|
70
|
+
## React integration
|
|
155
71
|
|
|
156
|
-
|
|
72
|
+
### `useFiber`
|
|
157
73
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
74
|
+
Returns the calling component’s Fiber. During server rendering it returns `undefined` because there is no client Fiber for the component.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { useFiber } from "bippy";
|
|
78
|
+
|
|
79
|
+
const Component = () => {
|
|
80
|
+
const fiber = useFiber();
|
|
81
|
+
console.log(fiber?.type);
|
|
82
|
+
return null;
|
|
83
|
+
};
|
|
84
|
+
```
|
|
166
85
|
|
|
167
|
-
##
|
|
86
|
+
## Instrumentation
|
|
168
87
|
|
|
169
|
-
|
|
88
|
+
Listen for React lifecycle events with `instrument`.
|
|
170
89
|
|
|
171
|
-
|
|
90
|
+
### `instrument`
|
|
172
91
|
|
|
173
|
-
|
|
92
|
+
Registers lifecycle handlers and returns an unsubscribe function.
|
|
93
|
+
|
|
94
|
+
Available handlers include:
|
|
95
|
+
|
|
96
|
+
- `onActive`: runs when instrumentation becomes active
|
|
97
|
+
- `onScheduleFiberRoot`: runs when React schedules a root
|
|
98
|
+
- `onCommitFiberRoot`: runs when React commits a root
|
|
99
|
+
- `onPostCommitFiberRoot`: runs after commit effects
|
|
100
|
+
- `onCommitFiberUnmount`: runs when React unmounts a Fiber
|
|
174
101
|
|
|
175
102
|
```typescript
|
|
176
|
-
import { instrument } from "bippy";
|
|
103
|
+
import { instrument } from "bippy";
|
|
177
104
|
import * as React from "react";
|
|
178
105
|
|
|
179
106
|
const unsubscribe = instrument({
|
|
180
107
|
onCommitFiberRoot(rendererID, root) {
|
|
181
|
-
console.log(
|
|
182
|
-
},
|
|
183
|
-
onPostCommitFiberRoot(rendererID, root) {
|
|
184
|
-
console.log("root with effects committed", root);
|
|
185
|
-
},
|
|
186
|
-
onCommitFiberUnmount(rendererID, fiber) {
|
|
187
|
-
console.log("fiber unmounted", fiber);
|
|
108
|
+
console.log(rendererID, root.current);
|
|
188
109
|
},
|
|
189
110
|
});
|
|
190
111
|
|
|
191
|
-
// later, stop listening (other instrument() subscribers keep working)
|
|
192
112
|
unsubscribe();
|
|
193
113
|
```
|
|
194
114
|
|
|
195
|
-
|
|
115
|
+
Call the returned function to unsubscribe those handlers.
|
|
116
|
+
|
|
117
|
+
### `getRDTHook`
|
|
196
118
|
|
|
197
|
-
|
|
119
|
+
Returns `globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__`. Use it when an integration needs the complete renderer registry or another low-level hook capability.
|
|
198
120
|
|
|
199
121
|
```typescript
|
|
200
122
|
import { getRDTHook } from "bippy";
|
|
201
123
|
|
|
202
124
|
const hook = getRDTHook();
|
|
203
|
-
console.log(hook);
|
|
125
|
+
console.log(hook.renderers);
|
|
204
126
|
```
|
|
205
127
|
|
|
206
|
-
|
|
128
|
+
## Fiber traversal
|
|
207
129
|
|
|
208
|
-
|
|
130
|
+
Traversal helpers walk a complete Fiber tree or select the Fibers involved in a commit.
|
|
209
131
|
|
|
210
|
-
|
|
211
|
-
import { instrument, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
|
|
212
|
-
import * as React from "react";
|
|
213
|
-
|
|
214
|
-
instrument({
|
|
215
|
-
onCommitFiberRoot(rendererID, root) {
|
|
216
|
-
traverseRenderedFibers(root, (fiber) => {
|
|
217
|
-
console.log("fiber rendered", fiber);
|
|
218
|
-
});
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### traverseFiber
|
|
132
|
+
### `traverseRenderedFibers`
|
|
224
133
|
|
|
225
|
-
|
|
134
|
+
Visits Fibers that mounted, updated, or unmounted in a commit. The callback receives the Fiber and its `mount`, `update`, or `unmount` phase.
|
|
226
135
|
|
|
227
136
|
```typescript
|
|
228
|
-
import { instrument,
|
|
137
|
+
import { instrument, traverseRenderedFibers } from "bippy";
|
|
229
138
|
import * as React from "react";
|
|
230
139
|
|
|
231
140
|
instrument({
|
|
232
141
|
onCommitFiberRoot(rendererID, root) {
|
|
233
|
-
|
|
234
|
-
console.log(fiber);
|
|
142
|
+
traverseRenderedFibers(root, (fiber, phase) => {
|
|
143
|
+
console.log(rendererID, phase, fiber);
|
|
235
144
|
});
|
|
236
145
|
},
|
|
237
146
|
});
|
|
238
147
|
```
|
|
239
148
|
|
|
240
|
-
|
|
149
|
+
Call it with the same root across commits so bippy can compare the current and previous trees.
|
|
241
150
|
|
|
242
|
-
|
|
151
|
+
### `traverseFiber`
|
|
243
152
|
|
|
244
|
-
|
|
245
|
-
import { traverseProps } from "bippy";
|
|
153
|
+
Walks down from a Fiber and calls a selector for each node. Return `true` to stop and return the selected Fiber. Pass `true` as the third argument to walk toward the root instead.
|
|
246
154
|
|
|
247
|
-
|
|
155
|
+
```typescript
|
|
156
|
+
import { isHostFiber, traverseFiber } from "bippy";
|
|
248
157
|
|
|
249
|
-
|
|
250
|
-
|
|
158
|
+
const buttonFiber = traverseFiber(root.current, (fiber) => {
|
|
159
|
+
return isHostFiber(fiber) && fiber.type === "button";
|
|
251
160
|
});
|
|
252
161
|
```
|
|
253
162
|
|
|
254
|
-
|
|
163
|
+
The selector can also return a promise. In that case, `traverseFiber` returns a promise for the selected Fiber.
|
|
255
164
|
|
|
256
|
-
|
|
165
|
+
### `didFiberRender` and `didFiberCommit`
|
|
257
166
|
|
|
258
|
-
|
|
259
|
-
import { traverseState } from "bippy";
|
|
167
|
+
Return whether a Fiber has rendered or committed. Use `traverseRenderedFibers` to inspect changes from a specific commit.
|
|
260
168
|
|
|
261
|
-
|
|
169
|
+
```typescript
|
|
170
|
+
import { didFiberCommit, didFiberRender } from "bippy";
|
|
262
171
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
});
|
|
172
|
+
console.log(didFiberRender(fiber));
|
|
173
|
+
console.log(didFiberCommit(fiber));
|
|
266
174
|
```
|
|
267
175
|
|
|
268
|
-
|
|
176
|
+
## Fiber inspection
|
|
269
177
|
|
|
270
|
-
|
|
178
|
+
Inspection helpers identify Fibers and read their component, host instance, and renderer metadata.
|
|
271
179
|
|
|
272
|
-
|
|
273
|
-
import { traverseContexts } from "bippy";
|
|
274
|
-
|
|
275
|
-
// ...
|
|
276
|
-
|
|
277
|
-
traverseContexts(fiber, (next, prev) => {
|
|
278
|
-
console.log(next, prev);
|
|
279
|
-
});
|
|
280
|
-
```
|
|
180
|
+
### `setFiberId` and `getFiberId`
|
|
281
181
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
set and get a persistent identity for a fiber. by default, fibers are anonymous and have no identity.
|
|
182
|
+
Assign and read a stable numeric identity across Fiber updates. `getFiberId` creates an identity when one does not exist.
|
|
285
183
|
|
|
286
184
|
```typescript
|
|
287
|
-
import {
|
|
288
|
-
|
|
289
|
-
// ...
|
|
185
|
+
import { getFiberId, setFiberId } from "bippy";
|
|
290
186
|
|
|
291
|
-
setFiberId(fiber);
|
|
292
|
-
console.log(
|
|
187
|
+
setFiberId(fiber, 123);
|
|
188
|
+
console.log(getFiberId(fiber));
|
|
293
189
|
```
|
|
294
190
|
|
|
295
|
-
###
|
|
191
|
+
### Classification helpers
|
|
192
|
+
|
|
193
|
+
Use these predicates to narrow an unknown value or Fiber before reading renderer-specific fields:
|
|
296
194
|
|
|
297
|
-
|
|
195
|
+
| Helper | Result |
|
|
196
|
+
| ------------------ | ------------------------------------------------------- |
|
|
197
|
+
| `isFiber` | Performs a fast check for a Fiber-like object |
|
|
198
|
+
| `isValidFiber` | Checks the core fields required by a Fiber |
|
|
199
|
+
| `isHostFiber` | Narrows a Fiber to a host Fiber |
|
|
200
|
+
| `isCompositeFiber` | Finds function, class, memo, and other composite Fibers |
|
|
201
|
+
| `hasMemoCache` | Detects React Compiler memo cache data |
|
|
298
202
|
|
|
299
203
|
```typescript
|
|
300
204
|
import { isHostFiber } from "bippy";
|
|
301
205
|
|
|
302
206
|
if (isHostFiber(fiber)) {
|
|
303
|
-
console.log(
|
|
207
|
+
console.log(fiber.stateNode);
|
|
304
208
|
}
|
|
305
209
|
```
|
|
306
210
|
|
|
307
|
-
###
|
|
211
|
+
### `getDisplayName` and `getType`
|
|
308
212
|
|
|
309
|
-
|
|
213
|
+
`getDisplayName` reads a component name from a Fiber type. `getType` unwraps memo and forward-ref wrappers to return the underlying component definition.
|
|
310
214
|
|
|
311
215
|
```typescript
|
|
312
|
-
import {
|
|
216
|
+
import { getDisplayName, getType } from "bippy";
|
|
313
217
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
218
|
+
console.log(getDisplayName(fiber.type));
|
|
219
|
+
console.log(getType(fiber.type));
|
|
317
220
|
```
|
|
318
221
|
|
|
319
|
-
###
|
|
222
|
+
### `getFiber`
|
|
320
223
|
|
|
321
|
-
|
|
224
|
+
Returns the Fiber associated with a renderer host instance, such as a DOM element. The result is `null` when no registered renderer recognizes the instance.
|
|
322
225
|
|
|
323
226
|
```typescript
|
|
324
|
-
import {
|
|
325
|
-
|
|
326
|
-
console.log(getDisplayName(fiber));
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### getType
|
|
330
|
-
|
|
331
|
-
returns the underlying type (the component definition) for a given fiber. for example, this could be a function component or class component.
|
|
332
|
-
|
|
333
|
-
```jsx
|
|
334
|
-
import { getType } from "bippy";
|
|
335
|
-
import { memo } from "react";
|
|
336
|
-
|
|
337
|
-
const RealComponent = () => {
|
|
338
|
-
return <div>hello</div>;
|
|
339
|
-
};
|
|
340
|
-
const MemoizedComponent = memo(RealComponent);
|
|
341
|
-
|
|
342
|
-
console.log(getType(fiberForMemoizedComponent) === RealComponent);
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
### getNearestHostFiber / getNearestHostFibers
|
|
346
|
-
|
|
347
|
-
`getNearestHostFiber` returns the closest host fiber above or below a given fiber. `getNearestHostFibers` returns all host fibers associated with the provided fiber and its subtree.
|
|
348
|
-
|
|
349
|
-
```jsx
|
|
350
|
-
import { getNearestHostFiber, getNearestHostFibers } from "bippy";
|
|
351
|
-
|
|
352
|
-
// ...
|
|
353
|
-
|
|
354
|
-
function Component() {
|
|
355
|
-
return (
|
|
356
|
-
<>
|
|
357
|
-
<div>hello</div>
|
|
358
|
-
<div>world</div>
|
|
359
|
-
</>
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
console.log(getNearestHostFiber(fiberForComponent)); // <div>hello</div>
|
|
364
|
-
console.log(getNearestHostFibers(fiberForComponent)); // [<div>hello</div>, <div>world</div>]
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
### getTimings
|
|
368
|
-
|
|
369
|
-
returns the self and total render times for the fiber.
|
|
227
|
+
import { getFiber } from "bippy";
|
|
370
228
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (fiber.actualDuration !== undefined) {
|
|
374
|
-
const { selfTime, totalTime } = getTimings(fiber);
|
|
375
|
-
console.log(selfTime, totalTime);
|
|
376
|
-
}
|
|
229
|
+
const element = document.querySelector("button");
|
|
230
|
+
const fiber = getFiber(element);
|
|
377
231
|
```
|
|
378
232
|
|
|
379
|
-
|
|
233
|
+
`getFiberFromHostInstance` remains available as an alias.
|
|
380
234
|
|
|
381
|
-
|
|
235
|
+
### `getLatestFiber`
|
|
382
236
|
|
|
383
|
-
|
|
384
|
-
[fiber, fiber.return, fiber.return.return, ...]
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
### getMutatedHostFibers
|
|
388
|
-
|
|
389
|
-
returns an array of all host fibers that have committed and rendered in the provided fiber's subtree.
|
|
237
|
+
Returns the latest version of a Fiber. Use it when you retain a Fiber across renders.
|
|
390
238
|
|
|
391
239
|
```typescript
|
|
392
|
-
import {
|
|
240
|
+
import { getFiber, getLatestFiber } from "bippy";
|
|
393
241
|
|
|
394
|
-
|
|
242
|
+
const fiber = getFiber(document.body);
|
|
243
|
+
const latestFiber = fiber ? getLatestFiber(fiber) : null;
|
|
395
244
|
```
|
|
396
245
|
|
|
397
|
-
###
|
|
246
|
+
### `getRenderer`
|
|
398
247
|
|
|
399
|
-
|
|
248
|
+
Returns the React renderer that owns a Fiber, or `null` when the renderer is unavailable.
|
|
400
249
|
|
|
401
250
|
```typescript
|
|
402
|
-
import {
|
|
251
|
+
import { getRenderer } from "bippy";
|
|
403
252
|
|
|
404
|
-
|
|
253
|
+
const renderer = getRenderer(fiber);
|
|
254
|
+
renderer?.overrideProps?.(fiber, ["title"], "new title");
|
|
255
|
+
renderer?.scheduleUpdate?.(fiber);
|
|
405
256
|
```
|
|
406
257
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
returns the fiber associated with a given host instance (e.g., a DOM element).
|
|
258
|
+
Renderer capabilities are optional and vary by renderer version.
|
|
410
259
|
|
|
411
|
-
|
|
412
|
-
import { getFiberFromHostInstance } from "bippy";
|
|
413
|
-
|
|
414
|
-
const fiber = getFiberFromHostInstance(document.querySelector("div"));
|
|
415
|
-
console.log(fiber);
|
|
416
|
-
```
|
|
260
|
+
### React internals
|
|
417
261
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
returns the latest fiber (since it may be double-buffered). usually use this in combination with `getFiberFromHostInstance`.
|
|
262
|
+
The main `bippy` entry point exports the complete internals model used by its APIs. This includes Fiber, root, renderer, dispatcher, work-tag, flag, symbol, and build-type definitions.
|
|
421
263
|
|
|
422
264
|
```typescript
|
|
423
|
-
import {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
265
|
+
import {
|
|
266
|
+
MutationMask,
|
|
267
|
+
ReactBuildType,
|
|
268
|
+
ReactFiberFlags,
|
|
269
|
+
ReactSymbols,
|
|
270
|
+
getReactWorkTags,
|
|
271
|
+
getReactWorkTagsForFiber,
|
|
272
|
+
getReactWorkTagsForRenderer,
|
|
273
|
+
} from "bippy";
|
|
274
|
+
import type {
|
|
275
|
+
Fiber,
|
|
276
|
+
FiberRoot,
|
|
277
|
+
ReactDevToolsGlobalHook,
|
|
278
|
+
ReactRenderer,
|
|
279
|
+
RendererDispatcherRef,
|
|
280
|
+
} from "bippy";
|
|
427
281
|
```
|
|
428
282
|
|
|
429
|
-
|
|
283
|
+
These definitions follow React’s private implementation and may change between React versions.
|
|
430
284
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
```typescript
|
|
434
|
-
import { overrideProps } from "bippy";
|
|
435
|
-
|
|
436
|
-
// override props on a fiber
|
|
437
|
-
overrideProps(fiber, {
|
|
438
|
-
title: "new title",
|
|
439
|
-
config: {
|
|
440
|
-
enabled: true,
|
|
441
|
-
count: 42,
|
|
442
|
-
},
|
|
443
|
-
});
|
|
444
|
-
```
|
|
285
|
+
## Source inspection
|
|
445
286
|
|
|
446
|
-
|
|
287
|
+
Source utilities resolve component locations, source maps, and component stacks. Import them from `bippy/source`.
|
|
447
288
|
|
|
448
|
-
###
|
|
289
|
+
### `getSource`
|
|
449
290
|
|
|
450
|
-
|
|
291
|
+
Returns the source location for a Fiber across DOM, native, terminal, canvas, PDF, and custom renderers.
|
|
451
292
|
|
|
452
293
|
```typescript
|
|
453
|
-
import {
|
|
454
|
-
|
|
455
|
-
// override the first hook (id: 0) with a new value
|
|
456
|
-
overrideHookState(fiber, 0, "new state value");
|
|
294
|
+
import { getSource } from "bippy/source";
|
|
457
295
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
user: {
|
|
461
|
-
name: "john",
|
|
462
|
-
age: 30,
|
|
463
|
-
},
|
|
464
|
-
});
|
|
296
|
+
const source = await getSource(fiber);
|
|
297
|
+
console.log(source);
|
|
465
298
|
```
|
|
466
299
|
|
|
467
|
-
|
|
300
|
+
Production builds may omit source information. Runtimes without `fetch` receive unsymbolicated locations.
|
|
468
301
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
overrides react context values at runtime by finding the appropriate context provider.
|
|
302
|
+
Pass a custom `SourceFetch` for packaged bundles, virtual filesystems, or renderer-specific URLs:
|
|
472
303
|
|
|
473
304
|
```typescript
|
|
474
|
-
import {
|
|
475
|
-
|
|
476
|
-
// override context value
|
|
477
|
-
overrideContext(fiber, MyContext, {
|
|
478
|
-
theme: "dark",
|
|
479
|
-
user: {
|
|
480
|
-
id: 123,
|
|
481
|
-
name: "jane",
|
|
482
|
-
},
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
// override with primitive value
|
|
486
|
-
overrideContext(fiber, ThemeContext, "dark");
|
|
487
|
-
```
|
|
305
|
+
import { getSource, type SourceFetch } from "bippy/source";
|
|
488
306
|
|
|
489
|
-
|
|
307
|
+
const sourceFetch: SourceFetch = async (url, init) => {
|
|
308
|
+
const artifact = sourceArtifacts.get(url);
|
|
309
|
+
if (!artifact) return fetch(url, init);
|
|
490
310
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
```typescript
|
|
496
|
-
import { getSource } from "bippy/source";
|
|
311
|
+
return new Response(artifact.content, {
|
|
312
|
+
headers: artifact.sourceMapUrl ? { SourceMap: artifact.sourceMapUrl } : undefined,
|
|
313
|
+
});
|
|
314
|
+
};
|
|
497
315
|
|
|
498
|
-
const
|
|
499
|
-
const source = await getSource(fiber);
|
|
500
|
-
// {
|
|
501
|
-
// columnNumber: 12,
|
|
502
|
-
// fileName: 'path/to/file.tsx',
|
|
503
|
-
// lineNumber: 12,
|
|
504
|
-
// }
|
|
316
|
+
const source = await getSource(fiber, true, sourceFetch);
|
|
505
317
|
```
|
|
506
318
|
|
|
507
|
-
|
|
508
|
-
>
|
|
509
|
-
> - only available in dev mode
|
|
510
|
-
> - source availability is controlled by react and the renderer; production builds normally remove debug metadata
|
|
511
|
-
> - captures the location where the element is _used_; definition locations are recovered when react exposes an owned child debug stack
|
|
512
|
-
> - react 18 requires `_debugSource` from the JSX source transform (see [react#31981](https://github.com/facebook/react/issues/31981))
|
|
513
|
-
> - react 19 uses `_debugStack` and works for both composite and host fibers
|
|
514
|
-
> - source-map fetching is optional; runtimes without `fetch` still receive the unsymbolicated source location
|
|
515
|
-
|
|
516
|
-
### getOwnerStack / getParentStack
|
|
517
|
-
|
|
518
|
-
returns a symbolicated stack of components above a fiber.
|
|
519
|
-
|
|
520
|
-
`getOwnerStack` walks the chain of components that _created_ this fiber's JSX (react's `_debugOwner` chain), with exact creation-site locations on react 19, including server component owners. wrappers that merely render `{children}` don't appear. it automatically falls back to `getParentStack` when no usable owner frames exist (e.g. react <19).
|
|
319
|
+
### `getOwnerStack` and `getParentStack`
|
|
521
320
|
|
|
522
|
-
|
|
321
|
+
Both functions return symbolicated component stacks above a Fiber. `getOwnerStack` follows the components that created the Fiber’s JSX. `getParentStack` follows every ancestor in the Fiber return chain.
|
|
523
322
|
|
|
524
323
|
```typescript
|
|
525
324
|
import { getOwnerStack, getParentStack } from "bippy/source";
|
|
526
325
|
|
|
527
326
|
const ownerFrames = await getOwnerStack(fiber);
|
|
528
|
-
// [{ functionName: "Button", fileName: "src/button.tsx", lineNumber: 12, ... }, ...]
|
|
529
|
-
|
|
530
327
|
const parentFrames = await getParentStack(fiber);
|
|
531
|
-
// includes every wrapper between the fiber and the root
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
## example
|
|
535
|
-
|
|
536
|
-
here's a mini toy version of [`react-scan`](https://github.com/aidenybai/react-scan) that highlights renders in your app.
|
|
537
|
-
|
|
538
|
-
```javascript
|
|
539
|
-
import { instrument, getNearestHostFiber, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
|
|
540
|
-
|
|
541
|
-
const highlightFiber = (fiber) => {
|
|
542
|
-
if (!(fiber.stateNode instanceof HTMLElement)) return;
|
|
543
|
-
// fiber.stateNode is a DOM element
|
|
544
|
-
const rect = fiber.stateNode.getBoundingClientRect();
|
|
545
|
-
const highlight = document.createElement("div");
|
|
546
|
-
highlight.style.border = "1px solid red";
|
|
547
|
-
highlight.style.position = "fixed";
|
|
548
|
-
highlight.style.top = `${rect.top}px`;
|
|
549
|
-
highlight.style.left = `${rect.left}px`;
|
|
550
|
-
highlight.style.width = `${rect.width}px`;
|
|
551
|
-
highlight.style.height = `${rect.height}px`;
|
|
552
|
-
highlight.style.zIndex = "999999999";
|
|
553
|
-
document.documentElement.appendChild(highlight);
|
|
554
|
-
setTimeout(() => {
|
|
555
|
-
document.documentElement.removeChild(highlight);
|
|
556
|
-
}, 100);
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* `instrument` is a function that installs the react DevTools global
|
|
561
|
-
* hook and allows you to set up custom handlers for react fiber events.
|
|
562
|
-
*/
|
|
563
|
-
instrument({
|
|
564
|
-
/**
|
|
565
|
-
* `onCommitFiberRoot` is a handler that is called when react is
|
|
566
|
-
* ready to commit a fiber root. this means that react is has
|
|
567
|
-
* rendered your entire app and is ready to apply changes to
|
|
568
|
-
* the host tree (e.g. via DOM mutations).
|
|
569
|
-
*/
|
|
570
|
-
onCommitFiberRoot(rendererID, root) {
|
|
571
|
-
/**
|
|
572
|
-
* `traverseRenderedFibers` traverses the fiber tree and determines which
|
|
573
|
-
* fibers have actually rendered.
|
|
574
|
-
*
|
|
575
|
-
* A fiber tree contains many fibers that may have not rendered. this
|
|
576
|
-
* can be because it bailed out (e.g. `useMemo`) or because it wasn't
|
|
577
|
-
* actually rendered (if <Child> re-rendered, then <Parent> didn't
|
|
578
|
-
* actually render, but exists in the fiber tree).
|
|
579
|
-
*/
|
|
580
|
-
traverseRenderedFibers(root, (fiber) => {
|
|
581
|
-
/**
|
|
582
|
-
* `getNearestHostFiber` is a utility function that finds the
|
|
583
|
-
* nearest host fiber to a given fiber.
|
|
584
|
-
*
|
|
585
|
-
* a host fiber for `react-dom` is a fiber that has a DOM element
|
|
586
|
-
* as its `stateNode`.
|
|
587
|
-
*/
|
|
588
|
-
const hostFiber = getNearestHostFiber(fiber);
|
|
589
|
-
highlightFiber(hostFiber);
|
|
590
|
-
});
|
|
591
|
-
},
|
|
592
|
-
});
|
|
593
328
|
```
|
|
594
329
|
|
|
595
|
-
##
|
|
596
|
-
|
|
597
|
-
bippy observes renderers through the React DevTools global hook. a renderer is automatically supported when it injects its reconciler and forwards commits to that hook.
|
|
598
|
-
|
|
599
|
-
| level | renderers | coverage |
|
|
600
|
-
| ------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
|
601
|
-
| automatic | React DOM, Remotion | unit matrix and browser e2e |
|
|
602
|
-
| automatic | React Native Fabric, React Native Skia | iOS and Android Detox e2e |
|
|
603
|
-
| automatic | React Three Fiber, Ink, react-nil | unit matrix |
|
|
604
|
-
| automatic | `@opentui/react`, `@pixi/react`, React BabylonJS | unit matrix with non-DOM host instances |
|
|
605
|
-
| compatibility | `@react-pdf/renderer` | its real reconciler root is forwarded through a synthetic DevTools hook bridge because react-pdf does not inject itself |
|
|
606
|
-
| compatibility | React Konva | its exported reconciler is injected by a test bridge because upstream automatic injection is disabled |
|
|
607
|
-
|
|
608
|
-
compatibility entries are not zero-config support claims. the bridge implementations in [`packages/bippy/tests/renderer-adapters.tsx`](packages/bippy/tests/renderer-adapters.tsx) verify bippy's fiber APIs against the real host trees while keeping the missing upstream DevTools integration explicit. React Native Windows, macOS, and NativeScript are not currently asserted.
|
|
609
|
-
|
|
610
|
-
`@testing-library/react` is a React DOM testing utility, not a renderer. bippy uses it throughout the test suite, so those tests exercise `react-dom` support.
|
|
611
|
-
|
|
612
|
-
## glossary
|
|
613
|
-
|
|
614
|
-
- fiber: a “unit of execution” in react, representing a component or dom element
|
|
615
|
-
- commit: the process of applying changes to the host tree (e.g. DOM mutations)
|
|
616
|
-
- render: the process of building the fiber tree by executing component function/classes
|
|
617
|
-
- host tree: the tree of UI elements that react mutates (e.g. DOM elements)
|
|
618
|
-
- reconciler (or “renderer”): custom bindings for react, e.g. react-dom, react-native, react-three-fiber, etc to mutate the host tree
|
|
619
|
-
- `rendererID`: the id of the reconciler, starting at 1 (can be from multiple reconciler instances)
|
|
620
|
-
- `root`: a special `FiberRoot` type that contains the container fiber (the one you pass to `ReactDOM.createRoot`) in the `current` property
|
|
621
|
-
- `onCommitFiberRoot`: called when react is ready to commit a fiber root
|
|
622
|
-
- `onPostCommitFiberRoot`: called when react has committed a fiber root and effects have run
|
|
623
|
-
- `onCommitFiberUnmount`: called when a fiber unmounts
|
|
624
|
-
|
|
625
|
-
## misc
|
|
626
|
-
|
|
627
|
-
we initially created bippy for [react-scan](https://github.com/aidenybai/react-scan), which ships with safeguards so it only runs in development or error-guarded in production.
|
|
628
|
-
|
|
629
|
-
if you're seeking more robust solutions, you might consider [its-fine](https://github.com/pmndrs/its-fine) for accessing fibers within react using hooks, or [react-devtools-inline](https://www.npmjs.com/package/react-devtools-inline) for a headful interface.
|
|
630
|
-
|
|
631
|
-
if you plan to use this project beyond experimentation, please review [react-scan's source code](https://github.com/aidenybai/react-scan) to understand our safeguarding practices.
|
|
330
|
+
## Acknowledgements
|
|
632
331
|
|
|
633
332
|
the original bippy character is owned and created by [@dairyfreerice](https://www.instagram.com/dairyfreerice). this project is not related to the bippy brand, i just think the character is cute.
|