bippy 0.6.1-dev.93556ef → 0.6.1-dev.94e5797
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/README.md +173 -476
- package/dist/core.cjs +1 -1
- package/dist/core.d.cts +8 -57
- package/dist/core.d.ts +8 -57
- package/dist/core.js +1 -1
- package/dist/core2.cjs +1 -1
- package/dist/core2.d.cts +2 -2
- package/dist/core2.d.ts +2 -2
- package/dist/core2.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +5 -5
- package/dist/source.js +6 -6
- package/package.json +6 -3
- package/src/core.ts +43 -390
- package/src/index.ts +1 -0
- package/src/react.ts +76 -0
- package/dist/index.iife.js +0 -9
- package/dist/install-hook-only.iife.js +0 -9
package/README.md
CHANGED
|
@@ -1,658 +1,355 @@
|
|
|
1
|
-
>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
>
|
|
5
|
-
|
|
6
|
-
# <img src="https://github.com/aidenybai/bippy/blob/main/.github/public/bippy.png?raw=true" width="60" align="center" /> bippy
|
|
1
|
+
<h1>
|
|
2
|
+
<img src="./.github/public/bippy.png" width="48" alt="" valign="middle" />
|
|
3
|
+
bippy
|
|
4
|
+
</h1>
|
|
7
5
|
|
|
8
6
|
[](https://npmjs.com/package/bippy)
|
|
9
7
|
[](https://npmjs.com/package/bippy)
|
|
10
8
|
|
|
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;
|
|
49
|
-
|
|
50
|
-
// stateNode is the host fiber (e.g. DOM element)
|
|
51
|
-
stateNode: Node | null;
|
|
52
|
-
|
|
53
|
-
// parent fiber
|
|
54
|
-
return: Fiber | null;
|
|
55
|
-
|
|
56
|
-
// the previous or current version of the fiber
|
|
57
|
-
alternate: Fiber | null;
|
|
58
|
-
|
|
59
|
-
// saved props input
|
|
60
|
-
memoizedProps: any;
|
|
61
|
-
|
|
62
|
-
// state (useState, useReducer, useSES, etc.)
|
|
63
|
-
memoizedState: any;
|
|
64
|
-
|
|
65
|
-
// contexts (useContext)
|
|
66
|
-
dependencies: Dependencies | null;
|
|
67
|
-
|
|
68
|
-
// effects (useEffect, useLayoutEffect, etc.)
|
|
69
|
-
updateQueue: any;
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
here, the `child`, `sibling`, and `return` properties are pointers to other fibers in the tree.
|
|
74
|
-
|
|
75
|
-
additionally, `memoizedProps`, `memoizedState`, and `dependencies` are the fiber's props, state, and contexts.
|
|
76
|
-
|
|
77
|
-
while all of the information is there, it's awkward to work with, and changes frequently across different versions of react. bippy simplifies this by providing utility functions like:
|
|
78
|
-
|
|
79
|
-
- `traverseRenderedFibers` to detect renders and `traverseFiber` to traverse the overall fiber tree
|
|
80
|
-
- _(instead of `child`, `sibling`, and `return` pointers)_
|
|
81
|
-
- `traverseProps`, `traverseState`, and `traverseContexts` to traverse the fiber's props, state, and contexts
|
|
82
|
-
- _(instead of `memoizedProps`, `memoizedState`, and `dependencies`)_
|
|
83
|
-
|
|
84
|
-
however, react doesn't expose fibers to you directly. so, we have to hack our way around to access them.
|
|
85
|
-
|
|
86
|
-
luckily, react [reads from a property](https://github.com/facebook/react/blob/6a4b46cd70d2672bc4be59dcb5b8dede22ed0cef/packages/react-reconciler/src/ReactFiberDevToolsHook.js#L48) in the window object: `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` and runs handlers on it when certain events happen. this property must exist before react's bundle is executed. this is intended for react devtools, but we can use it to our advantage.
|
|
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>;
|
|
9
|
+
bippy hacks into React internals.
|
|
94
10
|
|
|
95
|
-
|
|
96
|
-
// apply changes to the host tree (e.g. DOM mutations)
|
|
97
|
-
onCommitFiberRoot: (rendererID: RendererID, root: FiberRoot, commitPriority?: number) => void;
|
|
11
|
+
React keeps its internals out of reach. bippy opens them up for metaprogramming, letting you inspect the [Fiber](https://youtu.be/ZCuYPiUIONs) tree, track renders, and access the renderer directly.
|
|
98
12
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
13
|
+
> [!WARNING]
|
|
14
|
+
> ⚠️⚠️⚠️ **This project may break production apps and cause unexpected behavior.** ⚠️⚠️⚠️
|
|
15
|
+
>
|
|
16
|
+
> This project uses React internals, which can change at any time. We don’t recommend depending on them unless you have to. By proceeding, you acknowledge the risk of breaking your own code or apps that use your code.
|
|
119
17
|
|
|
120
|
-
|
|
18
|
+
## Install bippy
|
|
121
19
|
|
|
122
|
-
|
|
20
|
+
Install bippy:
|
|
123
21
|
|
|
124
22
|
```shell
|
|
125
23
|
npm install bippy
|
|
126
24
|
```
|
|
127
25
|
|
|
128
|
-
|
|
26
|
+
Import bippy before React or any React renderer.
|
|
129
27
|
|
|
130
|
-
###
|
|
28
|
+
### Next.js
|
|
131
29
|
|
|
132
|
-
|
|
30
|
+
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
31
|
|
|
134
32
|
```typescript
|
|
135
|
-
// instrumentation-client.ts
|
|
136
33
|
import "bippy";
|
|
137
34
|
```
|
|
138
35
|
|
|
139
|
-
|
|
36
|
+
### Vite
|
|
140
37
|
|
|
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:
|
|
38
|
+
Import bippy at the top of your Vite entry point, before any React imports:
|
|
144
39
|
|
|
145
40
|
```typescript
|
|
146
|
-
// src/main.tsx
|
|
147
41
|
import "bippy";
|
|
148
42
|
import { StrictMode } from "react";
|
|
149
43
|
import { createRoot } from "react-dom/client";
|
|
44
|
+
```
|
|
150
45
|
|
|
151
|
-
|
|
46
|
+
## `getFiber`
|
|
47
|
+
|
|
48
|
+
Returns the Fiber associated with a renderer host instance, such as an element from the Document Object Model (DOM). The result is `null` when no registered renderer recognizes the instance.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { getFiber } from "bippy";
|
|
52
|
+
|
|
53
|
+
const element = document.querySelector("button");
|
|
54
|
+
const fiber = getFiber(element);
|
|
152
55
|
```
|
|
153
56
|
|
|
154
|
-
|
|
57
|
+
`getFiberFromHostInstance` is an alias for `getFiber`.
|
|
155
58
|
|
|
156
|
-
|
|
59
|
+
## `useFiber`
|
|
157
60
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
61
|
+
Returns the calling component’s Fiber. During server rendering it returns `undefined` because there is no client Fiber for the component.
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { useFiber } from "bippy";
|
|
65
|
+
|
|
66
|
+
const Component = () => {
|
|
67
|
+
const fiber = useFiber();
|
|
68
|
+
console.log(fiber?.type);
|
|
69
|
+
return null;
|
|
70
|
+
};
|
|
71
|
+
```
|
|
166
72
|
|
|
167
|
-
##
|
|
73
|
+
## `instrument`
|
|
168
74
|
|
|
169
|
-
|
|
75
|
+
Registers lifecycle handlers and returns an unsubscribe function.
|
|
170
76
|
|
|
171
|
-
|
|
77
|
+
Available handlers include:
|
|
172
78
|
|
|
173
|
-
|
|
79
|
+
- `onActive`: runs when instrumentation becomes active
|
|
80
|
+
- `onScheduleFiberRoot`: runs when React schedules a root
|
|
81
|
+
- `onCommitFiberRoot`: runs when React commits a root
|
|
82
|
+
- `onPostCommitFiberRoot`: runs after commit effects
|
|
83
|
+
- `onCommitFiberUnmount`: runs when React unmounts a Fiber
|
|
174
84
|
|
|
175
85
|
```typescript
|
|
176
|
-
import { instrument } from "bippy";
|
|
177
|
-
import * as React from "react";
|
|
86
|
+
import { instrument } from "bippy";
|
|
178
87
|
|
|
179
88
|
const unsubscribe = instrument({
|
|
180
|
-
onCommitFiberRoot(rendererID, root) {
|
|
181
|
-
console.log("root ready to commit", root);
|
|
182
|
-
},
|
|
183
|
-
onPostCommitFiberRoot(rendererID, root) {
|
|
184
|
-
console.log("root with effects committed", root);
|
|
185
|
-
},
|
|
186
89
|
onCommitFiberUnmount(rendererID, fiber) {
|
|
187
|
-
console.log(
|
|
90
|
+
console.log(rendererID, fiber);
|
|
188
91
|
},
|
|
189
92
|
});
|
|
190
93
|
|
|
191
|
-
// later, stop listening (other instrument() subscribers keep working)
|
|
192
94
|
unsubscribe();
|
|
193
95
|
```
|
|
194
96
|
|
|
195
|
-
|
|
97
|
+
Call the returned function to unsubscribe those handlers.
|
|
196
98
|
|
|
197
|
-
|
|
99
|
+
## `getRDTHook`
|
|
198
100
|
|
|
199
|
-
|
|
101
|
+
Returns the React DevTools global hook at `globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__`. Use it to access registered renderers and Fiber roots directly.
|
|
200
102
|
|
|
201
103
|
```typescript
|
|
202
104
|
import { getRDTHook } from "bippy";
|
|
203
105
|
|
|
204
106
|
const hook = getRDTHook();
|
|
205
|
-
console.log(hook);
|
|
107
|
+
console.log(hook.renderers);
|
|
206
108
|
```
|
|
207
109
|
|
|
208
|
-
|
|
110
|
+
## `traverseRenderedFibers`
|
|
209
111
|
|
|
210
|
-
|
|
112
|
+
Visits Fibers that mounted, updated, or unmounted in a commit. The callback receives the Fiber and its `mount`, `update`, or `unmount` phase.
|
|
211
113
|
|
|
212
114
|
```typescript
|
|
213
|
-
import { instrument, traverseRenderedFibers } from "bippy";
|
|
214
|
-
import * as React from "react";
|
|
115
|
+
import { instrument, traverseRenderedFibers } from "bippy";
|
|
215
116
|
|
|
216
117
|
instrument({
|
|
217
118
|
onCommitFiberRoot(rendererID, root) {
|
|
218
|
-
traverseRenderedFibers(root, (fiber) => {
|
|
219
|
-
console.log(
|
|
119
|
+
traverseRenderedFibers(root, (fiber, phase) => {
|
|
120
|
+
console.log(rendererID, phase, fiber);
|
|
220
121
|
});
|
|
221
122
|
},
|
|
222
123
|
});
|
|
223
124
|
```
|
|
224
125
|
|
|
225
|
-
|
|
126
|
+
Call it with the same root across commits so bippy can compare the current and previous trees.
|
|
226
127
|
|
|
227
|
-
|
|
128
|
+
## `traverseFiber`
|
|
228
129
|
|
|
229
|
-
|
|
230
|
-
import { instrument, traverseFiber } from "bippy"; // must be imported BEFORE react
|
|
231
|
-
import * as React from "react";
|
|
232
|
-
|
|
233
|
-
instrument({
|
|
234
|
-
onCommitFiberRoot(rendererID, root) {
|
|
235
|
-
traverseFiber(root.current, (fiber) => {
|
|
236
|
-
console.log(fiber);
|
|
237
|
-
});
|
|
238
|
-
},
|
|
239
|
-
});
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
### traverseProps
|
|
243
|
-
|
|
244
|
-
traverses the props of a fiber.
|
|
130
|
+
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.
|
|
245
131
|
|
|
246
132
|
```typescript
|
|
247
|
-
import {
|
|
133
|
+
import { isHostFiber, traverseFiber } from "bippy";
|
|
248
134
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
traverseProps(fiber, (propName, next, prev) => {
|
|
252
|
-
console.log(propName, next, prev);
|
|
135
|
+
const buttonFiber = traverseFiber(fiber, (candidateFiber) => {
|
|
136
|
+
return isHostFiber(candidateFiber) && candidateFiber.type === "button";
|
|
253
137
|
});
|
|
254
138
|
```
|
|
255
139
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
traverses the state (`useState`, `useReducer`, etc.) and effects that set state of a fiber.
|
|
259
|
-
|
|
260
|
-
```typescript
|
|
261
|
-
import { traverseState } from "bippy";
|
|
262
|
-
|
|
263
|
-
// ...
|
|
264
|
-
|
|
265
|
-
traverseState(fiber, (next, prev) => {
|
|
266
|
-
console.log(next, prev);
|
|
267
|
-
});
|
|
268
|
-
```
|
|
140
|
+
The selector can also return a promise. In that case, `traverseFiber` returns a promise for the selected Fiber.
|
|
269
141
|
|
|
270
|
-
|
|
142
|
+
## `didFiberRender`
|
|
271
143
|
|
|
272
|
-
|
|
144
|
+
Returns whether a Fiber has rendered. It does not identify whether the render happened during a specific commit.
|
|
273
145
|
|
|
274
146
|
```typescript
|
|
275
|
-
import {
|
|
276
|
-
|
|
277
|
-
// ...
|
|
147
|
+
import { didFiberRender } from "bippy";
|
|
278
148
|
|
|
279
|
-
|
|
280
|
-
console.log(next, prev);
|
|
281
|
-
});
|
|
149
|
+
console.log(didFiberRender(fiber));
|
|
282
150
|
```
|
|
283
151
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
set and get a persistent identity for a fiber. by default, fibers are anonymous and have no identity.
|
|
287
|
-
|
|
288
|
-
```typescript
|
|
289
|
-
import { setFiberId, getFiberId } from "bippy";
|
|
290
|
-
|
|
291
|
-
// ...
|
|
292
|
-
|
|
293
|
-
setFiberId(fiber);
|
|
294
|
-
console.log("unique id for fiber:", getFiberId(fiber));
|
|
295
|
-
```
|
|
152
|
+
Use `traverseRenderedFibers` to inspect renders from a specific commit.
|
|
296
153
|
|
|
297
|
-
|
|
154
|
+
## `didFiberCommit`
|
|
298
155
|
|
|
299
|
-
|
|
156
|
+
Returns whether a Fiber or its subtree has committed work. It does not identify a specific commit.
|
|
300
157
|
|
|
301
158
|
```typescript
|
|
302
|
-
import {
|
|
159
|
+
import { didFiberCommit } from "bippy";
|
|
303
160
|
|
|
304
|
-
|
|
305
|
-
console.log("fiber is a host fiber");
|
|
306
|
-
}
|
|
161
|
+
console.log(didFiberCommit(fiber));
|
|
307
162
|
```
|
|
308
163
|
|
|
309
|
-
|
|
164
|
+
## `setFiberId`
|
|
310
165
|
|
|
311
|
-
|
|
166
|
+
Assigns a numeric ID to a Fiber.
|
|
312
167
|
|
|
313
168
|
```typescript
|
|
314
|
-
import {
|
|
169
|
+
import { setFiberId } from "bippy";
|
|
315
170
|
|
|
316
|
-
|
|
317
|
-
console.log("fiber is a composite fiber");
|
|
318
|
-
}
|
|
171
|
+
setFiberId(fiber, 123);
|
|
319
172
|
```
|
|
320
173
|
|
|
321
|
-
|
|
174
|
+
## `getFiberId`
|
|
322
175
|
|
|
323
|
-
|
|
176
|
+
Returns a stable numeric ID across Fiber updates. It creates an ID when none has been assigned.
|
|
324
177
|
|
|
325
178
|
```typescript
|
|
326
|
-
import {
|
|
179
|
+
import { getFiberId } from "bippy";
|
|
327
180
|
|
|
328
|
-
|
|
181
|
+
const fiberId = getFiberId(fiber);
|
|
329
182
|
```
|
|
330
183
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
returns the underlying type (the component definition) for a given fiber. for example, this could be a function component or class component.
|
|
184
|
+
## `isFiber`
|
|
334
185
|
|
|
335
|
-
|
|
336
|
-
import { getType } from "bippy";
|
|
337
|
-
import { memo } from "react";
|
|
186
|
+
Returns whether a value contains the core fields required by a Fiber.
|
|
338
187
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
};
|
|
342
|
-
const MemoizedComponent = memo(RealComponent);
|
|
188
|
+
```typescript
|
|
189
|
+
import { isFiber } from "bippy";
|
|
343
190
|
|
|
344
|
-
console.log(
|
|
191
|
+
console.log(isFiber(value));
|
|
345
192
|
```
|
|
346
193
|
|
|
347
|
-
|
|
194
|
+
## `isHostFiber`
|
|
348
195
|
|
|
349
|
-
|
|
196
|
+
Returns whether a Fiber represents a renderer host instance, such as a DOM element or React Native view.
|
|
350
197
|
|
|
351
|
-
```
|
|
352
|
-
import {
|
|
353
|
-
|
|
354
|
-
// ...
|
|
198
|
+
```typescript
|
|
199
|
+
import { isHostFiber } from "bippy";
|
|
355
200
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
<>
|
|
359
|
-
<div>hello</div>
|
|
360
|
-
<div>world</div>
|
|
361
|
-
</>
|
|
362
|
-
);
|
|
201
|
+
if (isHostFiber(fiber)) {
|
|
202
|
+
console.log(fiber.stateNode);
|
|
363
203
|
}
|
|
364
|
-
|
|
365
|
-
console.log(getNearestHostFiber(fiberForComponent)); // <div>hello</div>
|
|
366
|
-
console.log(getNearestHostFibers(fiberForComponent)); // [<div>hello</div>, <div>world</div>]
|
|
367
204
|
```
|
|
368
205
|
|
|
369
|
-
|
|
206
|
+
## `isCompositeFiber`
|
|
370
207
|
|
|
371
|
-
|
|
208
|
+
Returns whether a Fiber represents a function, class, memo, or forward-ref component.
|
|
372
209
|
|
|
373
210
|
```typescript
|
|
374
|
-
|
|
375
|
-
if (fiber.actualDuration !== undefined) {
|
|
376
|
-
const { selfTime, totalTime } = getTimings(fiber);
|
|
377
|
-
console.log(selfTime, totalTime);
|
|
378
|
-
}
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
### getFiberStack
|
|
382
|
-
|
|
383
|
-
returns an array representing the stack of fibers from the current fiber up to the root.
|
|
211
|
+
import { isCompositeFiber } from "bippy";
|
|
384
212
|
|
|
385
|
-
|
|
386
|
-
[fiber, fiber.return, fiber.return.return, ...]
|
|
213
|
+
console.log(isCompositeFiber(fiber));
|
|
387
214
|
```
|
|
388
215
|
|
|
389
|
-
|
|
216
|
+
## `hasMemoCache`
|
|
390
217
|
|
|
391
|
-
|
|
218
|
+
Returns whether a Fiber uses a React Compiler memo cache.
|
|
392
219
|
|
|
393
220
|
```typescript
|
|
394
|
-
import {
|
|
221
|
+
import { hasMemoCache } from "bippy";
|
|
395
222
|
|
|
396
|
-
console.log(
|
|
223
|
+
console.log(hasMemoCache(fiber));
|
|
397
224
|
```
|
|
398
225
|
|
|
399
|
-
|
|
226
|
+
## `getDisplayName`
|
|
400
227
|
|
|
401
|
-
|
|
228
|
+
Returns the display name of a Fiber type.
|
|
402
229
|
|
|
403
230
|
```typescript
|
|
404
|
-
import {
|
|
231
|
+
import { getDisplayName } from "bippy";
|
|
405
232
|
|
|
406
|
-
console.log(
|
|
233
|
+
console.log(getDisplayName(fiber.type));
|
|
407
234
|
```
|
|
408
235
|
|
|
409
|
-
|
|
236
|
+
## `getType`
|
|
410
237
|
|
|
411
|
-
|
|
238
|
+
Unwraps memo and forward-ref wrappers and returns the underlying component definition.
|
|
412
239
|
|
|
413
240
|
```typescript
|
|
414
|
-
import {
|
|
241
|
+
import { getType } from "bippy";
|
|
415
242
|
|
|
416
|
-
|
|
417
|
-
console.log(fiber);
|
|
243
|
+
console.log(getType(fiber.type));
|
|
418
244
|
```
|
|
419
245
|
|
|
420
|
-
|
|
246
|
+
## `getLatestFiber`
|
|
421
247
|
|
|
422
|
-
|
|
248
|
+
Returns the latest version of a Fiber. Use it when you retain a Fiber across renders.
|
|
423
249
|
|
|
424
250
|
```typescript
|
|
425
|
-
import { getLatestFiber } from "bippy";
|
|
251
|
+
import { getFiber, getLatestFiber } from "bippy";
|
|
426
252
|
|
|
427
|
-
const
|
|
428
|
-
|
|
253
|
+
const fiber = getFiber(document.body);
|
|
254
|
+
const latestFiber = fiber ? getLatestFiber(fiber) : null;
|
|
429
255
|
```
|
|
430
256
|
|
|
431
|
-
|
|
257
|
+
## `getRenderer`
|
|
432
258
|
|
|
433
|
-
|
|
259
|
+
Returns the React renderer that owns a Fiber, or `null` when the renderer is unavailable.
|
|
434
260
|
|
|
435
261
|
```typescript
|
|
436
|
-
import {
|
|
437
|
-
|
|
438
|
-
// override props on a fiber
|
|
439
|
-
overrideProps(fiber, {
|
|
440
|
-
title: "new title",
|
|
441
|
-
config: {
|
|
442
|
-
enabled: true,
|
|
443
|
-
count: 42,
|
|
444
|
-
},
|
|
445
|
-
});
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
the function accepts a fiber and a partial object containing the props to override. bippy automatically flattens nested objects into property paths.
|
|
449
|
-
|
|
450
|
-
### overrideHookState
|
|
451
|
-
|
|
452
|
-
overrides hook state (`useState`, `useReducer`, etc.) at runtime by hook id.
|
|
262
|
+
import { getRenderer } from "bippy";
|
|
453
263
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
// override the first hook (id: 0) with a new value
|
|
458
|
-
overrideHookState(fiber, 0, "new state value");
|
|
459
|
-
|
|
460
|
-
// override nested state object
|
|
461
|
-
overrideHookState(fiber, 1, {
|
|
462
|
-
user: {
|
|
463
|
-
name: "john",
|
|
464
|
-
age: 30,
|
|
465
|
-
},
|
|
466
|
-
});
|
|
264
|
+
const renderer = getRenderer(fiber);
|
|
265
|
+
renderer?.overrideProps?.(fiber, ["title"], "new title");
|
|
266
|
+
renderer?.scheduleUpdate?.(fiber);
|
|
467
267
|
```
|
|
468
268
|
|
|
469
|
-
|
|
269
|
+
Renderer capabilities are optional and vary by renderer version.
|
|
470
270
|
|
|
471
|
-
|
|
271
|
+
## React internals
|
|
472
272
|
|
|
473
|
-
|
|
273
|
+
The main `bippy` entry point exports the React internals used by its APIs.
|
|
474
274
|
|
|
475
275
|
```typescript
|
|
476
|
-
import {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
276
|
+
import {
|
|
277
|
+
MutationMask,
|
|
278
|
+
ReactBuildType,
|
|
279
|
+
ReactFiberFlags,
|
|
280
|
+
ReactSymbols,
|
|
281
|
+
getReactWorkTags,
|
|
282
|
+
getReactWorkTagsForFiber,
|
|
283
|
+
getReactWorkTagsForRenderer,
|
|
284
|
+
} from "bippy";
|
|
285
|
+
import type {
|
|
286
|
+
Fiber,
|
|
287
|
+
FiberRoot,
|
|
288
|
+
ReactDevToolsGlobalHook,
|
|
289
|
+
ReactRenderer,
|
|
290
|
+
RendererDispatcherRef,
|
|
291
|
+
} from "bippy";
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
These definitions follow React’s private implementation and may change between React versions.
|
|
295
|
+
|
|
296
|
+
## `getSource`
|
|
297
|
+
|
|
298
|
+
Returns the source location for a Fiber from these renderers:
|
|
299
|
+
|
|
300
|
+
- DOM
|
|
301
|
+
- Native
|
|
302
|
+
- Terminal
|
|
303
|
+
- Canvas
|
|
304
|
+
- PDF
|
|
305
|
+
- Custom
|
|
496
306
|
|
|
497
307
|
```typescript
|
|
498
308
|
import { getSource } from "bippy/source";
|
|
499
309
|
|
|
500
|
-
const fiber = getFiberFromHostInstance(hostInstance);
|
|
501
310
|
const source = await getSource(fiber);
|
|
502
|
-
|
|
503
|
-
// columnNumber: 12,
|
|
504
|
-
// fileName: 'path/to/file.tsx',
|
|
505
|
-
// lineNumber: 12,
|
|
506
|
-
// }
|
|
311
|
+
console.log(source);
|
|
507
312
|
```
|
|
508
313
|
|
|
509
|
-
|
|
510
|
-
>
|
|
511
|
-
> - only available in dev mode
|
|
512
|
-
> - source availability is controlled by react and the renderer; production builds normally remove debug metadata
|
|
513
|
-
> - captures the location where the element is _used_; definition locations are recovered when react exposes an owned child debug stack
|
|
514
|
-
> - react 18 requires `_debugSource` from the JSX source transform (see [react#31981](https://github.com/facebook/react/issues/31981))
|
|
515
|
-
> - react 19 uses `_debugStack` and works for both composite and host fibers
|
|
516
|
-
> - source-map fetching is optional; runtimes without `fetch` still receive the unsymbolicated source location
|
|
314
|
+
Production builds may omit source information. Runtimes without `fetch` receive unsymbolicated locations.
|
|
517
315
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
`getSource`, `getOwnerStack`, `getParentStack`, `getDisplayNameFromSource`, `parseHookNames`, and `getSourceMap` accept an optional `SourceFetch`. use it for packaged React Native bundles, desktop runtimes, virtual filesystems, or renderer-specific URLs that the runtime's global `fetch` cannot load. a virtual bundle response can provide a `SourceMap` header when the map is stored separately:
|
|
316
|
+
Pass a custom `SourceFetch` for packaged bundles, virtual filesystems, or renderer-specific URLs:
|
|
521
317
|
|
|
522
318
|
```typescript
|
|
523
319
|
import { getSource, type SourceFetch } from "bippy/source";
|
|
524
320
|
|
|
525
321
|
const sourceFetch: SourceFetch = async (url, init) => {
|
|
526
|
-
const
|
|
527
|
-
if (!
|
|
322
|
+
const artifact = sourceArtifacts.get(url);
|
|
323
|
+
if (!artifact) return fetch(url, init);
|
|
528
324
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
});
|
|
325
|
+
const sourceMapUrl = artifact.sourceMapUrl;
|
|
326
|
+
const headers = sourceMapUrl ? { SourceMap: sourceMapUrl } : undefined;
|
|
327
|
+
return new Response(artifact.content, { headers });
|
|
532
328
|
};
|
|
533
329
|
|
|
534
330
|
const source = await getSource(fiber, true, sourceFetch);
|
|
535
331
|
```
|
|
536
332
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
### getOwnerStack / getParentStack
|
|
333
|
+
## `getOwnerStack`
|
|
540
334
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
`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).
|
|
544
|
-
|
|
545
|
-
`getParentStack` walks _all_ ancestors in the render tree (the fiber's `return` chain), including `{children}` wrappers. works on every react version.
|
|
335
|
+
Returns the symbolicated stack of components that created a Fiber’s JSX. It falls back to the parent stack when owner information is unavailable.
|
|
546
336
|
|
|
547
337
|
```typescript
|
|
548
|
-
import { getOwnerStack
|
|
338
|
+
import { getOwnerStack } from "bippy/source";
|
|
549
339
|
|
|
550
340
|
const ownerFrames = await getOwnerStack(fiber);
|
|
551
|
-
// [{ functionName: "Button", fileName: "src/button.tsx", lineNumber: 12, ... }, ...]
|
|
552
|
-
|
|
553
|
-
const parentFrames = await getParentStack(fiber);
|
|
554
|
-
// includes every wrapper between the fiber and the root
|
|
555
341
|
```
|
|
556
342
|
|
|
557
|
-
##
|
|
558
|
-
|
|
559
|
-
here's a mini toy version of [`react-scan`](https://github.com/aidenybai/react-scan) that highlights renders in your app.
|
|
560
|
-
|
|
561
|
-
```javascript
|
|
562
|
-
import { instrument, getNearestHostFiber, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
|
|
563
|
-
|
|
564
|
-
const highlightFiber = (fiber) => {
|
|
565
|
-
if (!(fiber.stateNode instanceof HTMLElement)) return;
|
|
566
|
-
// fiber.stateNode is a DOM element
|
|
567
|
-
const rect = fiber.stateNode.getBoundingClientRect();
|
|
568
|
-
const highlight = document.createElement("div");
|
|
569
|
-
highlight.style.border = "1px solid red";
|
|
570
|
-
highlight.style.position = "fixed";
|
|
571
|
-
highlight.style.top = `${rect.top}px`;
|
|
572
|
-
highlight.style.left = `${rect.left}px`;
|
|
573
|
-
highlight.style.width = `${rect.width}px`;
|
|
574
|
-
highlight.style.height = `${rect.height}px`;
|
|
575
|
-
highlight.style.zIndex = "999999999";
|
|
576
|
-
document.documentElement.appendChild(highlight);
|
|
577
|
-
setTimeout(() => {
|
|
578
|
-
document.documentElement.removeChild(highlight);
|
|
579
|
-
}, 100);
|
|
580
|
-
};
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
* `instrument` is a function that installs the react DevTools global
|
|
584
|
-
* hook and allows you to set up custom handlers for react fiber events.
|
|
585
|
-
*/
|
|
586
|
-
instrument({
|
|
587
|
-
/**
|
|
588
|
-
* `onCommitFiberRoot` is a handler that is called when react is
|
|
589
|
-
* ready to commit a fiber root. this means that react is has
|
|
590
|
-
* rendered your entire app and is ready to apply changes to
|
|
591
|
-
* the host tree (e.g. via DOM mutations).
|
|
592
|
-
*/
|
|
593
|
-
onCommitFiberRoot(rendererID, root) {
|
|
594
|
-
/**
|
|
595
|
-
* `traverseRenderedFibers` traverses the fiber tree and determines which
|
|
596
|
-
* fibers have actually rendered.
|
|
597
|
-
*
|
|
598
|
-
* A fiber tree contains many fibers that may have not rendered. this
|
|
599
|
-
* can be because it bailed out (e.g. `useMemo`) or because it wasn't
|
|
600
|
-
* actually rendered (if <Child> re-rendered, then <Parent> didn't
|
|
601
|
-
* actually render, but exists in the fiber tree).
|
|
602
|
-
*/
|
|
603
|
-
traverseRenderedFibers(root, (fiber) => {
|
|
604
|
-
/**
|
|
605
|
-
* `getNearestHostFiber` is a utility function that finds the
|
|
606
|
-
* nearest host fiber to a given fiber.
|
|
607
|
-
*
|
|
608
|
-
* a host fiber for `react-dom` is a fiber that has a DOM element
|
|
609
|
-
* as its `stateNode`.
|
|
610
|
-
*/
|
|
611
|
-
const hostFiber = getNearestHostFiber(fiber);
|
|
612
|
-
highlightFiber(hostFiber);
|
|
613
|
-
});
|
|
614
|
-
},
|
|
615
|
-
});
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
## renderer support
|
|
619
|
-
|
|
620
|
-
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.
|
|
343
|
+
## `getParentStack`
|
|
621
344
|
|
|
622
|
-
|
|
623
|
-
| ------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
|
624
|
-
| automatic | React DOM, Remotion | unit matrix and browser e2e |
|
|
625
|
-
| automatic | React Native Fabric, React Native Skia | iOS and Android Detox e2e |
|
|
626
|
-
| automatic | React Three Fiber, Ink, react-nil | unit matrix |
|
|
627
|
-
| automatic | `@opentui/react`, `@pixi/react`, React BabylonJS | unit matrix with non-DOM host instances |
|
|
628
|
-
| compatibility | `@react-pdf/renderer` | its real reconciler root is forwarded through a synthetic DevTools hook bridge because react-pdf does not inject itself |
|
|
629
|
-
| compatibility | React Konva | its exported reconciler is injected by a test bridge because upstream automatic injection is disabled |
|
|
345
|
+
Returns the symbolicated stack of every ancestor in a Fiber’s return chain.
|
|
630
346
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
terminal renderers must load bippy before their reconciler initializes. importing `bippy` first works in Node and Bun; `bippy/install-hook-only` is also available as a minimal prelude when application import order is controlled elsewhere. the test suite verifies OpenTUI in clean Node and Bun processes and verifies that Ink can replace the hook with full React DevTools without losing either renderer.
|
|
634
|
-
|
|
635
|
-
`@testing-library/react` is a React DOM testing utility, not a renderer. bippy uses it throughout the test suite, including hydration, event-driven updates, portals, unmounts, and error-boundary recovery.
|
|
636
|
-
|
|
637
|
-
## glossary
|
|
638
|
-
|
|
639
|
-
- fiber: a “unit of execution” in react, representing a component or dom element
|
|
640
|
-
- commit: the process of applying changes to the host tree (e.g. DOM mutations)
|
|
641
|
-
- render: the process of building the fiber tree by executing component function/classes
|
|
642
|
-
- host tree: the tree of UI elements that react mutates (e.g. DOM elements)
|
|
643
|
-
- reconciler (or “renderer”): custom bindings for react, e.g. react-dom, react-native, react-three-fiber, etc to mutate the host tree
|
|
644
|
-
- `rendererID`: the id of the reconciler, starting at 1 (can be from multiple reconciler instances)
|
|
645
|
-
- `root`: a special `FiberRoot` type that contains the container fiber (the one you pass to `ReactDOM.createRoot`) in the `current` property
|
|
646
|
-
- `onCommitFiberRoot`: called when react is ready to commit a fiber root
|
|
647
|
-
- `onPostCommitFiberRoot`: called when react has committed a fiber root and effects have run
|
|
648
|
-
- `onCommitFiberUnmount`: called when a fiber unmounts
|
|
649
|
-
|
|
650
|
-
## misc
|
|
651
|
-
|
|
652
|
-
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.
|
|
347
|
+
```typescript
|
|
348
|
+
import { getParentStack } from "bippy/source";
|
|
653
349
|
|
|
654
|
-
|
|
350
|
+
const parentFrames = await getParentStack(fiber);
|
|
351
|
+
```
|
|
655
352
|
|
|
656
|
-
|
|
353
|
+
## Acknowledgements
|
|
657
354
|
|
|
658
|
-
|
|
355
|
+
[@dairyfreerice](https://www.instagram.com/dairyfreerice) created and owns the original bippy character. this project has nothing to do with the bippy brand, i think the character is cute.
|