bippy 0.6.1-dev.d7876ea → 0.6.1-dev.f9e6c65
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 +159 -487
- package/dist/core.cjs +1 -1
- package/dist/core.d.cts +24 -70
- package/dist/core.d.ts +24 -70
- 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/errors.d.cts +33 -59
- package/dist/errors.d.ts +33 -59
- 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 +12 -13
- package/dist/source.d.cts +85 -77
- package/dist/source.d.ts +85 -77
- package/dist/source.js +12 -13
- package/package.json +6 -3
- package/src/core.ts +141 -564
- package/src/errors.ts +0 -65
- package/src/index.ts +1 -0
- package/src/install-hook-only.ts +2 -2
- package/src/rdt-hook.ts +39 -45
- package/src/{generated/react-work-tags.js → react-internals/generated/react-work-tags.ts} +85 -7
- package/src/{react-internals.ts → react-internals/index.ts} +6 -4
- package/src/{semver.ts → react-internals/semver.ts} +2 -0
- package/src/{types.ts → react-internals/types.ts} +1 -84
- package/src/react.ts +76 -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 +2 -0
- package/src/source/inspect-hooks.ts +74 -96
- package/src/source/owner-stack.ts +66 -91
- 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 +421 -108
- package/dist/index.iife.js +0 -9
- package/dist/install-hook-only.iife.js +0 -9
- package/src/generated/react-work-tags.d.ts +0 -261
- package/src/unsubscribe.ts +0 -17
package/README.md
CHANGED
|
@@ -1,639 +1,311 @@
|
|
|
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;
|
|
9
|
+
bippy hacks into React internals.
|
|
49
10
|
|
|
50
|
-
|
|
51
|
-
stateNode: Node | null;
|
|
11
|
+
React normally keeps its [Fiber](https://youtu.be/ZCuYPiUIONs) tree out of reach. bippy lets you inspect components, track renders, and access the renderer directly.
|
|
52
12
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
alternate: Fiber | null;
|
|
58
|
-
|
|
59
|
-
// saved props input
|
|
60
|
-
memoizedProps: any;
|
|
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.
|
|
61
17
|
|
|
62
|
-
|
|
63
|
-
memoizedState: any;
|
|
18
|
+
## Install bippy
|
|
64
19
|
|
|
65
|
-
|
|
66
|
-
dependencies: Dependencies | null;
|
|
20
|
+
Install bippy:
|
|
67
21
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
22
|
+
```shell
|
|
23
|
+
npm install bippy
|
|
71
24
|
```
|
|
72
25
|
|
|
73
|
-
|
|
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.
|
|
26
|
+
Import bippy before React or any React renderer.
|
|
85
27
|
|
|
86
|
-
|
|
28
|
+
### Next.js
|
|
87
29
|
|
|
88
|
-
|
|
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`:
|
|
89
31
|
|
|
90
32
|
```typescript
|
|
91
|
-
|
|
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
|
-
}
|
|
33
|
+
import "bippy";
|
|
105
34
|
```
|
|
106
35
|
|
|
107
|
-
|
|
36
|
+
### Vite
|
|
108
37
|
|
|
109
|
-
|
|
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)_
|
|
38
|
+
Import bippy at the top of your Vite entry point, before any React imports:
|
|
117
39
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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.
|
|
123
|
-
|
|
124
|
-
```shell
|
|
125
|
-
npm install bippy
|
|
40
|
+
```typescript
|
|
41
|
+
import "bippy";
|
|
42
|
+
import { StrictMode } from "react";
|
|
43
|
+
import { createRoot } from "react-dom/client";
|
|
126
44
|
```
|
|
127
45
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
### next.js
|
|
46
|
+
## `getFiber`
|
|
131
47
|
|
|
132
|
-
|
|
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.
|
|
133
49
|
|
|
134
50
|
```typescript
|
|
135
|
-
|
|
136
|
-
|
|
51
|
+
import { getFiber } from "bippy";
|
|
52
|
+
|
|
53
|
+
const element = document.querySelector("button");
|
|
54
|
+
const fiber = getFiber(element);
|
|
137
55
|
```
|
|
138
56
|
|
|
139
|
-
|
|
57
|
+
`getFiberFromHostInstance` is an alias for `getFiber`.
|
|
140
58
|
|
|
141
|
-
|
|
59
|
+
## `useFiber`
|
|
142
60
|
|
|
143
|
-
|
|
61
|
+
Returns the calling component’s Fiber. During server rendering it returns `undefined` because there is no client Fiber for the component.
|
|
144
62
|
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
import "bippy";
|
|
148
|
-
import { StrictMode } from "react";
|
|
149
|
-
import { createRoot } from "react-dom/client";
|
|
63
|
+
```tsx
|
|
64
|
+
import { useFiber } from "bippy";
|
|
150
65
|
|
|
151
|
-
|
|
66
|
+
const Component = () => {
|
|
67
|
+
const fiber = useFiber();
|
|
68
|
+
console.log(fiber?.type);
|
|
69
|
+
return null;
|
|
70
|
+
};
|
|
152
71
|
```
|
|
153
72
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
> **note for library maintainers**: if you're building a library and want to define your own utility functions while minimizing bundle size, you can use `bippy/install-hook-only` (~90 bytes) instead of the main `bippy` export. this only installs the react devtools hook without importing any utility functions, allowing you to import only what you need from `bippy/core` or define your own fiber utilities. that said, the full `bippy` package is only ~4 KB gzipped, so bundle size is rarely a concern.
|
|
73
|
+
## Instrumentation
|
|
157
74
|
|
|
158
|
-
|
|
159
|
-
> import "bippy/install-hook-only"; // only installs the hook
|
|
160
|
-
> import { getRDTHook, traverseFiber } from "bippy/core"; // import only what you need
|
|
161
|
-
> import * as React from "react"; // import react AFTER the hook is installed
|
|
162
|
-
>
|
|
163
|
-
> const hook = getRDTHook();
|
|
164
|
-
> // define your own utilities or use only specific ones
|
|
165
|
-
> ```
|
|
75
|
+
Listen for React lifecycle events with `instrument`.
|
|
166
76
|
|
|
167
|
-
|
|
77
|
+
### `instrument`
|
|
168
78
|
|
|
169
|
-
|
|
79
|
+
Registers lifecycle handlers and returns an unsubscribe function.
|
|
170
80
|
|
|
171
|
-
|
|
81
|
+
Available handlers include:
|
|
172
82
|
|
|
173
|
-
|
|
83
|
+
- `onActive`: runs when instrumentation becomes active
|
|
84
|
+
- `onScheduleFiberRoot`: runs when React schedules a root
|
|
85
|
+
- `onCommitFiberRoot`: runs when React commits a root
|
|
86
|
+
- `onPostCommitFiberRoot`: runs after commit effects
|
|
87
|
+
- `onCommitFiberUnmount`: runs when React unmounts a Fiber
|
|
174
88
|
|
|
175
89
|
```typescript
|
|
176
|
-
import { instrument } from "bippy";
|
|
177
|
-
import * as React from "react";
|
|
90
|
+
import { instrument } from "bippy";
|
|
178
91
|
|
|
179
92
|
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
93
|
onCommitFiberUnmount(rendererID, fiber) {
|
|
187
|
-
console.log(
|
|
94
|
+
console.log(rendererID, fiber);
|
|
188
95
|
},
|
|
189
96
|
});
|
|
190
97
|
|
|
191
|
-
// later, stop listening (other instrument() subscribers keep working)
|
|
192
98
|
unsubscribe();
|
|
193
99
|
```
|
|
194
100
|
|
|
195
|
-
|
|
101
|
+
Call the returned function to unsubscribe those handlers.
|
|
196
102
|
|
|
197
|
-
### getRDTHook
|
|
103
|
+
### `getRDTHook`
|
|
198
104
|
|
|
199
|
-
|
|
105
|
+
Returns the React DevTools global hook at `globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__`. Use it to access registered renderers and Fiber roots directly.
|
|
200
106
|
|
|
201
107
|
```typescript
|
|
202
108
|
import { getRDTHook } from "bippy";
|
|
203
109
|
|
|
204
110
|
const hook = getRDTHook();
|
|
205
|
-
console.log(hook);
|
|
111
|
+
console.log(hook.renderers);
|
|
206
112
|
```
|
|
207
113
|
|
|
208
|
-
|
|
114
|
+
## Fiber traversal
|
|
209
115
|
|
|
210
|
-
|
|
116
|
+
Traversal helpers walk a complete Fiber tree or select the Fibers involved in a commit.
|
|
211
117
|
|
|
212
|
-
|
|
213
|
-
import { instrument, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
|
|
214
|
-
import * as React from "react";
|
|
118
|
+
### `traverseRenderedFibers`
|
|
215
119
|
|
|
216
|
-
|
|
217
|
-
onCommitFiberRoot(rendererID, root) {
|
|
218
|
-
traverseRenderedFibers(root, (fiber) => {
|
|
219
|
-
console.log("fiber rendered", fiber);
|
|
220
|
-
});
|
|
221
|
-
},
|
|
222
|
-
});
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### traverseFiber
|
|
226
|
-
|
|
227
|
-
calls a callback on every fiber in the fiber tree.
|
|
120
|
+
Visits Fibers that mounted, updated, or unmounted in a commit. The callback receives the Fiber and its `mount`, `update`, or `unmount` phase.
|
|
228
121
|
|
|
229
122
|
```typescript
|
|
230
|
-
import { instrument,
|
|
231
|
-
import * as React from "react";
|
|
123
|
+
import { instrument, traverseRenderedFibers } from "bippy";
|
|
232
124
|
|
|
233
125
|
instrument({
|
|
234
126
|
onCommitFiberRoot(rendererID, root) {
|
|
235
|
-
|
|
236
|
-
console.log(fiber);
|
|
127
|
+
traverseRenderedFibers(root, (fiber, phase) => {
|
|
128
|
+
console.log(rendererID, phase, fiber);
|
|
237
129
|
});
|
|
238
130
|
},
|
|
239
131
|
});
|
|
240
132
|
```
|
|
241
133
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
traverses the props of a fiber.
|
|
245
|
-
|
|
246
|
-
```typescript
|
|
247
|
-
import { traverseProps } from "bippy";
|
|
248
|
-
|
|
249
|
-
// ...
|
|
250
|
-
|
|
251
|
-
traverseProps(fiber, (propName, next, prev) => {
|
|
252
|
-
console.log(propName, next, prev);
|
|
253
|
-
});
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### traverseState
|
|
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
|
-
```
|
|
134
|
+
Call it with the same root across commits so bippy can compare the current and previous trees.
|
|
269
135
|
|
|
270
|
-
###
|
|
136
|
+
### `traverseFiber`
|
|
271
137
|
|
|
272
|
-
|
|
138
|
+
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.
|
|
273
139
|
|
|
274
140
|
```typescript
|
|
275
|
-
import {
|
|
141
|
+
import { isHostFiber, traverseFiber } from "bippy";
|
|
276
142
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
traverseContexts(fiber, (next, prev) => {
|
|
280
|
-
console.log(next, prev);
|
|
143
|
+
const buttonFiber = traverseFiber(fiber, (candidateFiber) => {
|
|
144
|
+
return isHostFiber(candidateFiber) && candidateFiber.type === "button";
|
|
281
145
|
});
|
|
282
146
|
```
|
|
283
147
|
|
|
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
|
-
// ...
|
|
148
|
+
The selector can also return a promise. In that case, `traverseFiber` returns a promise for the selected Fiber.
|
|
292
149
|
|
|
293
|
-
|
|
294
|
-
console.log("unique id for fiber:", getFiberId(fiber));
|
|
295
|
-
```
|
|
150
|
+
### `didFiberRender` and `didFiberCommit`
|
|
296
151
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
returns `true` if the fiber is a host fiber (e.g., a DOM node in react-dom).
|
|
152
|
+
Return whether a Fiber has rendered or committed. Use `traverseRenderedFibers` to inspect changes from a specific commit.
|
|
300
153
|
|
|
301
154
|
```typescript
|
|
302
|
-
import {
|
|
155
|
+
import { didFiberCommit, didFiberRender } from "bippy";
|
|
303
156
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
157
|
+
console.log(didFiberRender(fiber));
|
|
158
|
+
console.log(didFiberCommit(fiber));
|
|
307
159
|
```
|
|
308
160
|
|
|
309
|
-
|
|
161
|
+
## Fiber inspection
|
|
310
162
|
|
|
311
|
-
|
|
163
|
+
Inspection helpers identify Fibers and read their component, host instance, and renderer metadata.
|
|
312
164
|
|
|
313
|
-
|
|
314
|
-
import { isCompositeFiber } from "bippy";
|
|
165
|
+
### `setFiberId` and `getFiberId`
|
|
315
166
|
|
|
316
|
-
|
|
317
|
-
console.log("fiber is a composite fiber");
|
|
318
|
-
}
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
### getDisplayName
|
|
322
|
-
|
|
323
|
-
returns the display name of the fiber's component, falling back to the component's function or class name if available.
|
|
167
|
+
Assign and read a stable numeric identity across Fiber updates. `getFiberId` creates an identity when one does not exist.
|
|
324
168
|
|
|
325
169
|
```typescript
|
|
326
|
-
import {
|
|
327
|
-
|
|
328
|
-
console.log(getDisplayName(fiber));
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### getType
|
|
332
|
-
|
|
333
|
-
returns the underlying type (the component definition) for a given fiber. for example, this could be a function component or class component.
|
|
334
|
-
|
|
335
|
-
```jsx
|
|
336
|
-
import { getType } from "bippy";
|
|
337
|
-
import { memo } from "react";
|
|
338
|
-
|
|
339
|
-
const RealComponent = () => {
|
|
340
|
-
return <div>hello</div>;
|
|
341
|
-
};
|
|
342
|
-
const MemoizedComponent = memo(RealComponent);
|
|
170
|
+
import { getFiberId, setFiberId } from "bippy";
|
|
343
171
|
|
|
344
|
-
|
|
172
|
+
setFiberId(fiber, 123);
|
|
173
|
+
console.log(getFiberId(fiber));
|
|
345
174
|
```
|
|
346
175
|
|
|
347
|
-
###
|
|
176
|
+
### Classification helpers
|
|
348
177
|
|
|
349
|
-
|
|
178
|
+
Use these predicates to narrow an unknown value or Fiber before reading renderer-specific fields:
|
|
350
179
|
|
|
351
|
-
|
|
352
|
-
|
|
180
|
+
| Helper | Result |
|
|
181
|
+
| ------------------ | ------------------------------------------------------- |
|
|
182
|
+
| `isFiber` | Performs a fast check for a Fiber-like object |
|
|
183
|
+
| `isValidFiber` | Checks the core fields required by a Fiber |
|
|
184
|
+
| `isHostFiber` | Narrows a Fiber to a host Fiber |
|
|
185
|
+
| `isCompositeFiber` | Finds function, class, memo, and other composite Fibers |
|
|
186
|
+
| `hasMemoCache` | Detects React Compiler memo cache data |
|
|
353
187
|
|
|
354
|
-
|
|
188
|
+
```typescript
|
|
189
|
+
import { isHostFiber } from "bippy";
|
|
355
190
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
<>
|
|
359
|
-
<div>hello</div>
|
|
360
|
-
<div>world</div>
|
|
361
|
-
</>
|
|
362
|
-
);
|
|
191
|
+
if (isHostFiber(fiber)) {
|
|
192
|
+
console.log(fiber.stateNode);
|
|
363
193
|
}
|
|
364
|
-
|
|
365
|
-
console.log(getNearestHostFiber(fiberForComponent)); // <div>hello</div>
|
|
366
|
-
console.log(getNearestHostFibers(fiberForComponent)); // [<div>hello</div>, <div>world</div>]
|
|
367
194
|
```
|
|
368
195
|
|
|
369
|
-
###
|
|
196
|
+
### `getDisplayName` and `getType`
|
|
370
197
|
|
|
371
|
-
|
|
198
|
+
`getDisplayName` reads a component name from a Fiber type. `getType` unwraps memo and forward-ref wrappers to return the underlying component definition.
|
|
372
199
|
|
|
373
200
|
```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.
|
|
201
|
+
import { getDisplayName, getType } from "bippy";
|
|
384
202
|
|
|
385
|
-
|
|
386
|
-
|
|
203
|
+
console.log(getDisplayName(fiber.type));
|
|
204
|
+
console.log(getType(fiber.type));
|
|
387
205
|
```
|
|
388
206
|
|
|
389
|
-
###
|
|
207
|
+
### `getLatestFiber`
|
|
390
208
|
|
|
391
|
-
|
|
209
|
+
Returns the latest version of a Fiber. Use it when you retain a Fiber across renders.
|
|
392
210
|
|
|
393
211
|
```typescript
|
|
394
|
-
import {
|
|
212
|
+
import { getFiber, getLatestFiber } from "bippy";
|
|
395
213
|
|
|
396
|
-
|
|
214
|
+
const fiber = getFiber(document.body);
|
|
215
|
+
const latestFiber = fiber ? getLatestFiber(fiber) : null;
|
|
397
216
|
```
|
|
398
217
|
|
|
399
|
-
###
|
|
218
|
+
### `getRenderer`
|
|
400
219
|
|
|
401
|
-
|
|
220
|
+
Returns the React renderer that owns a Fiber, or `null` when the renderer is unavailable.
|
|
402
221
|
|
|
403
222
|
```typescript
|
|
404
|
-
import {
|
|
223
|
+
import { getRenderer } from "bippy";
|
|
405
224
|
|
|
406
|
-
|
|
225
|
+
const renderer = getRenderer(fiber);
|
|
226
|
+
renderer?.overrideProps?.(fiber, ["title"], "new title");
|
|
227
|
+
renderer?.scheduleUpdate?.(fiber);
|
|
407
228
|
```
|
|
408
229
|
|
|
409
|
-
|
|
230
|
+
Renderer capabilities are optional and vary by renderer version.
|
|
410
231
|
|
|
411
|
-
|
|
232
|
+
### React internals
|
|
412
233
|
|
|
413
|
-
|
|
414
|
-
import { getFiberFromHostInstance } from "bippy";
|
|
415
|
-
|
|
416
|
-
const fiber = getFiberFromHostInstance(document.querySelector("div"));
|
|
417
|
-
console.log(fiber);
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
### getLatestFiber
|
|
421
|
-
|
|
422
|
-
returns the latest fiber (since it may be double-buffered). usually use this in combination with `getFiberFromHostInstance`.
|
|
234
|
+
The main `bippy` entry point exports the React internals used by its APIs.
|
|
423
235
|
|
|
424
236
|
```typescript
|
|
425
|
-
import {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
237
|
+
import {
|
|
238
|
+
MutationMask,
|
|
239
|
+
ReactBuildType,
|
|
240
|
+
ReactFiberFlags,
|
|
241
|
+
ReactSymbols,
|
|
242
|
+
getReactWorkTags,
|
|
243
|
+
getReactWorkTagsForFiber,
|
|
244
|
+
getReactWorkTagsForRenderer,
|
|
245
|
+
} from "bippy";
|
|
246
|
+
import type {
|
|
247
|
+
Fiber,
|
|
248
|
+
FiberRoot,
|
|
249
|
+
ReactDevToolsGlobalHook,
|
|
250
|
+
ReactRenderer,
|
|
251
|
+
RendererDispatcherRef,
|
|
252
|
+
} from "bippy";
|
|
429
253
|
```
|
|
430
254
|
|
|
431
|
-
|
|
255
|
+
These definitions follow React’s private implementation and may change between React versions.
|
|
432
256
|
|
|
433
|
-
|
|
257
|
+
## Source inspection
|
|
434
258
|
|
|
435
|
-
|
|
436
|
-
import { overrideProps } from "bippy";
|
|
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
|
-
```
|
|
259
|
+
Source utilities resolve component locations, source maps, and component stacks. Import them from `bippy/source`.
|
|
447
260
|
|
|
448
|
-
|
|
261
|
+
### `getSource`
|
|
449
262
|
|
|
450
|
-
|
|
263
|
+
Returns the source location for a Fiber from these renderers:
|
|
451
264
|
|
|
452
|
-
|
|
265
|
+
- DOM
|
|
266
|
+
- Native
|
|
267
|
+
- Terminal
|
|
268
|
+
- Canvas
|
|
269
|
+
- PDF
|
|
270
|
+
- Custom
|
|
453
271
|
|
|
454
272
|
```typescript
|
|
455
|
-
import {
|
|
456
|
-
|
|
457
|
-
// override the first hook (id: 0) with a new value
|
|
458
|
-
overrideHookState(fiber, 0, "new state value");
|
|
273
|
+
import { getSource } from "bippy/source";
|
|
459
274
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
user: {
|
|
463
|
-
name: "john",
|
|
464
|
-
age: 30,
|
|
465
|
-
},
|
|
466
|
-
});
|
|
275
|
+
const source = await getSource(fiber);
|
|
276
|
+
console.log(source);
|
|
467
277
|
```
|
|
468
278
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
### overrideContext
|
|
279
|
+
Production builds may omit source information. Runtimes without `fetch` receive unsymbolicated locations.
|
|
472
280
|
|
|
473
|
-
|
|
281
|
+
Pass a custom `SourceFetch` for packaged bundles, virtual filesystems, or renderer-specific URLs:
|
|
474
282
|
|
|
475
283
|
```typescript
|
|
476
|
-
import {
|
|
477
|
-
|
|
478
|
-
// override context value
|
|
479
|
-
overrideContext(fiber, MyContext, {
|
|
480
|
-
theme: "dark",
|
|
481
|
-
user: {
|
|
482
|
-
id: 123,
|
|
483
|
-
name: "jane",
|
|
484
|
-
},
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
// override with primitive value
|
|
488
|
-
overrideContext(fiber, ThemeContext, "dark");
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
the function traverses up the fiber tree to find the context provider matching the provided context type and overrides its value.
|
|
492
|
-
|
|
493
|
-
### getSource
|
|
284
|
+
import { getSource, type SourceFetch } from "bippy/source";
|
|
494
285
|
|
|
495
|
-
|
|
286
|
+
const sourceFetch: SourceFetch = async (url, init) => {
|
|
287
|
+
const artifact = sourceArtifacts.get(url);
|
|
288
|
+
if (!artifact) return fetch(url, init);
|
|
496
289
|
|
|
497
|
-
|
|
498
|
-
|
|
290
|
+
const sourceMapUrl = artifact.sourceMapUrl;
|
|
291
|
+
const headers = sourceMapUrl ? { SourceMap: sourceMapUrl } : undefined;
|
|
292
|
+
return new Response(artifact.content, { headers });
|
|
293
|
+
};
|
|
499
294
|
|
|
500
|
-
const
|
|
501
|
-
const source = await getSource(fiber);
|
|
502
|
-
// {
|
|
503
|
-
// columnNumber: 12,
|
|
504
|
-
// fileName: 'path/to/file.tsx',
|
|
505
|
-
// lineNumber: 12,
|
|
506
|
-
// }
|
|
295
|
+
const source = await getSource(fiber, true, sourceFetch);
|
|
507
296
|
```
|
|
508
297
|
|
|
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
|
|
517
|
-
|
|
518
|
-
`getSourceMap` accepts an optional fetch implementation plus request limits, an abort signal, and a timeout. its cache is scoped to the fetch implementation so credentials or virtual file systems cannot leak results into each other.
|
|
519
|
-
|
|
520
|
-
### getOwnerStack / getParentStack
|
|
521
|
-
|
|
522
|
-
returns a symbolicated stack of components above a fiber.
|
|
298
|
+
### `getOwnerStack` and `getParentStack`
|
|
523
299
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
`getParentStack` walks _all_ ancestors in the render tree (the fiber's `return` chain), including `{children}` wrappers. works on every react version.
|
|
300
|
+
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.
|
|
527
301
|
|
|
528
302
|
```typescript
|
|
529
303
|
import { getOwnerStack, getParentStack } from "bippy/source";
|
|
530
304
|
|
|
531
305
|
const ownerFrames = await getOwnerStack(fiber);
|
|
532
|
-
// [{ functionName: "Button", fileName: "src/button.tsx", lineNumber: 12, ... }, ...]
|
|
533
|
-
|
|
534
306
|
const parentFrames = await getParentStack(fiber);
|
|
535
|
-
// includes every wrapper between the fiber and the root
|
|
536
307
|
```
|
|
537
308
|
|
|
538
|
-
##
|
|
539
|
-
|
|
540
|
-
here's a mini toy version of [`react-scan`](https://github.com/aidenybai/react-scan) that highlights renders in your app.
|
|
541
|
-
|
|
542
|
-
```javascript
|
|
543
|
-
import { instrument, getNearestHostFiber, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
|
|
544
|
-
|
|
545
|
-
const highlightFiber = (fiber) => {
|
|
546
|
-
if (!(fiber.stateNode instanceof HTMLElement)) return;
|
|
547
|
-
// fiber.stateNode is a DOM element
|
|
548
|
-
const rect = fiber.stateNode.getBoundingClientRect();
|
|
549
|
-
const highlight = document.createElement("div");
|
|
550
|
-
highlight.style.border = "1px solid red";
|
|
551
|
-
highlight.style.position = "fixed";
|
|
552
|
-
highlight.style.top = `${rect.top}px`;
|
|
553
|
-
highlight.style.left = `${rect.left}px`;
|
|
554
|
-
highlight.style.width = `${rect.width}px`;
|
|
555
|
-
highlight.style.height = `${rect.height}px`;
|
|
556
|
-
highlight.style.zIndex = "999999999";
|
|
557
|
-
document.documentElement.appendChild(highlight);
|
|
558
|
-
setTimeout(() => {
|
|
559
|
-
document.documentElement.removeChild(highlight);
|
|
560
|
-
}, 100);
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
/**
|
|
564
|
-
* `instrument` is a function that installs the react DevTools global
|
|
565
|
-
* hook and allows you to set up custom handlers for react fiber events.
|
|
566
|
-
*/
|
|
567
|
-
instrument({
|
|
568
|
-
/**
|
|
569
|
-
* `onCommitFiberRoot` is a handler that is called when react is
|
|
570
|
-
* ready to commit a fiber root. this means that react is has
|
|
571
|
-
* rendered your entire app and is ready to apply changes to
|
|
572
|
-
* the host tree (e.g. via DOM mutations).
|
|
573
|
-
*/
|
|
574
|
-
onCommitFiberRoot(rendererID, root) {
|
|
575
|
-
/**
|
|
576
|
-
* `traverseRenderedFibers` traverses the fiber tree and determines which
|
|
577
|
-
* fibers have actually rendered.
|
|
578
|
-
*
|
|
579
|
-
* A fiber tree contains many fibers that may have not rendered. this
|
|
580
|
-
* can be because it bailed out (e.g. `useMemo`) or because it wasn't
|
|
581
|
-
* actually rendered (if <Child> re-rendered, then <Parent> didn't
|
|
582
|
-
* actually render, but exists in the fiber tree).
|
|
583
|
-
*/
|
|
584
|
-
traverseRenderedFibers(root, (fiber) => {
|
|
585
|
-
/**
|
|
586
|
-
* `getNearestHostFiber` is a utility function that finds the
|
|
587
|
-
* nearest host fiber to a given fiber.
|
|
588
|
-
*
|
|
589
|
-
* a host fiber for `react-dom` is a fiber that has a DOM element
|
|
590
|
-
* as its `stateNode`.
|
|
591
|
-
*/
|
|
592
|
-
const hostFiber = getNearestHostFiber(fiber);
|
|
593
|
-
highlightFiber(hostFiber);
|
|
594
|
-
});
|
|
595
|
-
},
|
|
596
|
-
});
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
## renderer support
|
|
600
|
-
|
|
601
|
-
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.
|
|
602
|
-
|
|
603
|
-
| level | renderers | coverage |
|
|
604
|
-
| ------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
|
605
|
-
| automatic | React DOM, Remotion | unit matrix and browser e2e |
|
|
606
|
-
| automatic | React Native Fabric, React Native Skia | iOS and Android Detox e2e |
|
|
607
|
-
| automatic | React Three Fiber, Ink, react-nil | unit matrix |
|
|
608
|
-
| automatic | `@opentui/react`, `@pixi/react`, React BabylonJS | unit matrix with non-DOM host instances |
|
|
609
|
-
| compatibility | `@react-pdf/renderer` | its real reconciler root is forwarded through a synthetic DevTools hook bridge because react-pdf does not inject itself |
|
|
610
|
-
| compatibility | React Konva | its exported reconciler is injected by a test bridge because upstream automatic injection is disabled |
|
|
611
|
-
|
|
612
|
-
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.
|
|
613
|
-
|
|
614
|
-
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.
|
|
615
|
-
|
|
616
|
-
`@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.
|
|
617
|
-
|
|
618
|
-
## glossary
|
|
619
|
-
|
|
620
|
-
- fiber: a “unit of execution” in react, representing a component or dom element
|
|
621
|
-
- commit: the process of applying changes to the host tree (e.g. DOM mutations)
|
|
622
|
-
- render: the process of building the fiber tree by executing component function/classes
|
|
623
|
-
- host tree: the tree of UI elements that react mutates (e.g. DOM elements)
|
|
624
|
-
- reconciler (or “renderer”): custom bindings for react, e.g. react-dom, react-native, react-three-fiber, etc to mutate the host tree
|
|
625
|
-
- `rendererID`: the id of the reconciler, starting at 1 (can be from multiple reconciler instances)
|
|
626
|
-
- `root`: a special `FiberRoot` type that contains the container fiber (the one you pass to `ReactDOM.createRoot`) in the `current` property
|
|
627
|
-
- `onCommitFiberRoot`: called when react is ready to commit a fiber root
|
|
628
|
-
- `onPostCommitFiberRoot`: called when react has committed a fiber root and effects have run
|
|
629
|
-
- `onCommitFiberUnmount`: called when a fiber unmounts
|
|
630
|
-
|
|
631
|
-
## misc
|
|
632
|
-
|
|
633
|
-
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.
|
|
634
|
-
|
|
635
|
-
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.
|
|
636
|
-
|
|
637
|
-
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.
|
|
309
|
+
## Acknowledgements
|
|
638
310
|
|
|
639
|
-
|
|
311
|
+
[@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.
|