bippy 0.6.1-dev.b88fcb4 → 0.6.1-dev.f9a65c0
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 +211 -445
- package/dist/core.cjs +1 -1
- package/dist/core.d.cts +9 -59
- package/dist/core.d.ts +9 -59
- package/dist/core.js +1 -1
- package/dist/core2.cjs +1 -1
- package/dist/core2.d.cts +3 -3
- package/dist/core2.d.ts +3 -3
- package/dist/core2.js +1 -1
- package/dist/errors.d.cts +2 -57
- package/dist/errors.d.ts +2 -57
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- 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 +77 -78
- package/dist/source.d.ts +77 -78
- package/dist/source.js +12 -13
- package/package.json +6 -3
- package/src/core.ts +106 -545
- 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 +30 -43
- package/src/react-internals/generated/react-work-tags.ts +6 -2
- package/src/react-internals/index.ts +4 -4
- package/src/react-internals/semver.ts +2 -0
- package/src/react-internals/types.ts +0 -83
- package/src/react.ts +76 -0
- package/src/source/get-display-name-from-source.ts +44 -40
- package/src/source/get-source.ts +40 -24
- package/src/source/index.ts +2 -0
- package/src/source/inspect-hooks.ts +70 -91
- package/src/source/owner-stack.ts +60 -89
- 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 +312 -111
- package/dist/index.iife.js +0 -9
- package/dist/install-hook-only.iife.js +0 -9
package/README.md
CHANGED
|
@@ -1,639 +1,405 @@
|
|
|
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/).
|
|
9
|
+
bippy hacks into React internals.
|
|
39
10
|
|
|
40
|
-
|
|
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.
|
|
41
12
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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;
|
|
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.
|
|
55
17
|
|
|
56
|
-
|
|
57
|
-
alternate: Fiber | null;
|
|
18
|
+
## How Fiber works
|
|
58
19
|
|
|
59
|
-
|
|
60
|
-
memoizedProps: any;
|
|
20
|
+
React turns the elements returned by your components into a Fiber tree. Each Fiber is a mutable object representing one unit of work, such as a component, host element, text node, or internal boundary. It stores the node’s props, state, position in the tree, and pending work.
|
|
61
21
|
|
|
62
|
-
|
|
63
|
-
memoizedState: any;
|
|
22
|
+
Consider this component tree:
|
|
64
23
|
|
|
65
|
-
|
|
66
|
-
|
|
24
|
+
```tsx
|
|
25
|
+
const Button = () => <button>Save</button>;
|
|
67
26
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
27
|
+
const App = () => (
|
|
28
|
+
<main>
|
|
29
|
+
<Button />
|
|
30
|
+
</main>
|
|
31
|
+
);
|
|
71
32
|
```
|
|
72
33
|
|
|
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:
|
|
34
|
+
React represents it with Fibers similar to these:
|
|
78
35
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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>;
|
|
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;
|
|
36
|
+
```text
|
|
37
|
+
HostRoot
|
|
38
|
+
└── App FunctionComponent
|
|
39
|
+
└── main HostComponent
|
|
40
|
+
└── Button FunctionComponent
|
|
41
|
+
└── button HostComponent
|
|
42
|
+
```
|
|
101
43
|
|
|
102
|
-
|
|
103
|
-
|
|
44
|
+
Fibers are actual linked objects. The host Fiber for `<button>` contains the fields that connect React’s component tree to the rendered element:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
FiberNode {
|
|
48
|
+
tag: 5,
|
|
49
|
+
type: "button",
|
|
50
|
+
stateNode: HTMLButtonElement {},
|
|
51
|
+
return: FiberNode { … },
|
|
52
|
+
child: null,
|
|
53
|
+
sibling: null,
|
|
54
|
+
memoizedProps: { children: "Save" },
|
|
55
|
+
flags: 0,
|
|
56
|
+
alternate: null
|
|
104
57
|
}
|
|
105
58
|
```
|
|
106
59
|
|
|
107
|
-
|
|
60
|
+
`return`, `child`, and `sibling` form the tree. For a host Fiber, `stateNode` points to the renderer-owned instance, such as a DOM element or native view.
|
|
108
61
|
|
|
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)_
|
|
62
|
+
During an update, React builds a work-in-progress tree beside the current tree. The `alternate` field links the corresponding Fibers in both trees. React can pause or discard the work-in-progress tree; after a commit, the finished tree becomes current.
|
|
117
63
|
|
|
118
|
-
|
|
64
|
+
React does not expose Fiber as a public API. bippy “hacks into React” by accessing it anyway, giving you a consistent way to inspect Fiber trees across React versions and renderers.
|
|
119
65
|
|
|
120
|
-
|
|
66
|
+
## Install bippy
|
|
121
67
|
|
|
122
|
-
|
|
68
|
+
Install bippy:
|
|
123
69
|
|
|
124
70
|
```shell
|
|
125
71
|
npm install bippy
|
|
126
72
|
```
|
|
127
73
|
|
|
128
|
-
|
|
74
|
+
Import bippy before React or any React renderer.
|
|
129
75
|
|
|
130
|
-
###
|
|
76
|
+
### Next.js
|
|
131
77
|
|
|
132
|
-
|
|
78
|
+
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
79
|
|
|
134
80
|
```typescript
|
|
135
|
-
// instrumentation-client.ts
|
|
136
81
|
import "bippy";
|
|
137
82
|
```
|
|
138
83
|
|
|
139
|
-
|
|
84
|
+
### Vite
|
|
140
85
|
|
|
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:
|
|
86
|
+
Import bippy at the top of your Vite entry point, before any React imports:
|
|
144
87
|
|
|
145
88
|
```typescript
|
|
146
|
-
// src/main.tsx
|
|
147
89
|
import "bippy";
|
|
148
90
|
import { StrictMode } from "react";
|
|
149
91
|
import { createRoot } from "react-dom/client";
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## API Reference
|
|
95
|
+
|
|
96
|
+
### `getFiber`
|
|
97
|
+
|
|
98
|
+
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.
|
|
150
99
|
|
|
151
|
-
|
|
100
|
+
```typescript
|
|
101
|
+
import { getFiber } from "bippy";
|
|
102
|
+
|
|
103
|
+
const element = document.querySelector("button");
|
|
104
|
+
const fiber = getFiber(element);
|
|
152
105
|
```
|
|
153
106
|
|
|
154
|
-
|
|
107
|
+
`getFiberFromHostInstance` is an alias for `getFiber`.
|
|
155
108
|
|
|
156
|
-
|
|
109
|
+
### `useFiber`
|
|
157
110
|
|
|
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
|
-
> ```
|
|
111
|
+
Returns the calling component’s Fiber. During server rendering it returns `undefined` because there is no client Fiber for the component.
|
|
166
112
|
|
|
167
|
-
|
|
113
|
+
```tsx
|
|
114
|
+
import { useFiber } from "bippy";
|
|
168
115
|
|
|
169
|
-
|
|
116
|
+
const Component = () => {
|
|
117
|
+
const fiber = useFiber();
|
|
118
|
+
console.log(fiber?.type);
|
|
119
|
+
return null;
|
|
120
|
+
};
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `instrument`
|
|
170
124
|
|
|
171
|
-
|
|
125
|
+
Registers lifecycle handlers and returns an unsubscribe function.
|
|
172
126
|
|
|
173
|
-
|
|
127
|
+
Available handlers include:
|
|
128
|
+
|
|
129
|
+
- `onActive`: runs when instrumentation becomes active
|
|
130
|
+
- `onScheduleFiberRoot`: runs when React schedules a root
|
|
131
|
+
- `onCommitFiberRoot`: runs when React commits a root
|
|
132
|
+
- `onPostCommitFiberRoot`: runs after commit effects
|
|
133
|
+
- `onCommitFiberUnmount`: runs when React unmounts a Fiber
|
|
174
134
|
|
|
175
135
|
```typescript
|
|
176
|
-
import { instrument } from "bippy";
|
|
177
|
-
import * as React from "react";
|
|
136
|
+
import { instrument } from "bippy";
|
|
178
137
|
|
|
179
138
|
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
139
|
onCommitFiberUnmount(rendererID, fiber) {
|
|
187
|
-
console.log(
|
|
140
|
+
console.log(rendererID, fiber);
|
|
188
141
|
},
|
|
189
142
|
});
|
|
190
143
|
|
|
191
|
-
// later, stop listening (other instrument() subscribers keep working)
|
|
192
144
|
unsubscribe();
|
|
193
145
|
```
|
|
194
146
|
|
|
195
|
-
|
|
147
|
+
Call the returned function to unsubscribe those handlers.
|
|
196
148
|
|
|
197
|
-
### getRDTHook
|
|
149
|
+
### `getRDTHook`
|
|
198
150
|
|
|
199
|
-
|
|
151
|
+
Returns the React DevTools global hook at `globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__`. Use it to access registered renderers and Fiber roots directly.
|
|
200
152
|
|
|
201
153
|
```typescript
|
|
202
154
|
import { getRDTHook } from "bippy";
|
|
203
155
|
|
|
204
156
|
const hook = getRDTHook();
|
|
205
|
-
console.log(hook);
|
|
157
|
+
console.log(hook.renderers);
|
|
206
158
|
```
|
|
207
159
|
|
|
208
|
-
### traverseRenderedFibers
|
|
160
|
+
### `traverseRenderedFibers`
|
|
209
161
|
|
|
210
|
-
|
|
162
|
+
Visits Fibers that mounted, updated, or unmounted in a commit. The callback receives the Fiber and its `mount`, `update`, or `unmount` phase.
|
|
211
163
|
|
|
212
164
|
```typescript
|
|
213
|
-
import { instrument, traverseRenderedFibers } from "bippy";
|
|
214
|
-
import * as React from "react";
|
|
165
|
+
import { instrument, traverseRenderedFibers } from "bippy";
|
|
215
166
|
|
|
216
167
|
instrument({
|
|
217
168
|
onCommitFiberRoot(rendererID, root) {
|
|
218
|
-
traverseRenderedFibers(root, (fiber) => {
|
|
219
|
-
console.log(
|
|
169
|
+
traverseRenderedFibers(root, (fiber, phase) => {
|
|
170
|
+
console.log(rendererID, phase, fiber);
|
|
220
171
|
});
|
|
221
172
|
},
|
|
222
173
|
});
|
|
223
174
|
```
|
|
224
175
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
calls a callback on every fiber in the fiber tree.
|
|
228
|
-
|
|
229
|
-
```typescript
|
|
230
|
-
import { instrument, traverseFiber } from "bippy"; // must be imported BEFORE react
|
|
231
|
-
import * as React from "react";
|
|
176
|
+
Call it with the same root across commits so bippy can compare the current and previous trees.
|
|
232
177
|
|
|
233
|
-
|
|
234
|
-
onCommitFiberRoot(rendererID, root) {
|
|
235
|
-
traverseFiber(root.current, (fiber) => {
|
|
236
|
-
console.log(fiber);
|
|
237
|
-
});
|
|
238
|
-
},
|
|
239
|
-
});
|
|
240
|
-
```
|
|
178
|
+
### `traverseFiber`
|
|
241
179
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
traverses the props of a fiber.
|
|
180
|
+
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
181
|
|
|
246
182
|
```typescript
|
|
247
|
-
import {
|
|
248
|
-
|
|
249
|
-
// ...
|
|
183
|
+
import { isHostFiber, traverseFiber } from "bippy";
|
|
250
184
|
|
|
251
|
-
|
|
252
|
-
|
|
185
|
+
const buttonFiber = traverseFiber(fiber, (candidateFiber) => {
|
|
186
|
+
return isHostFiber(candidateFiber) && candidateFiber.type === "button";
|
|
253
187
|
});
|
|
254
188
|
```
|
|
255
189
|
|
|
256
|
-
|
|
190
|
+
The selector can also return a promise. In that case, `traverseFiber` returns a promise for the selected Fiber.
|
|
257
191
|
|
|
258
|
-
|
|
192
|
+
### `didFiberRender`
|
|
259
193
|
|
|
260
|
-
|
|
261
|
-
import { traverseState } from "bippy";
|
|
262
|
-
|
|
263
|
-
// ...
|
|
264
|
-
|
|
265
|
-
traverseState(fiber, (next, prev) => {
|
|
266
|
-
console.log(next, prev);
|
|
267
|
-
});
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
### traverseContexts
|
|
271
|
-
|
|
272
|
-
traverses the contexts (`useContext`) of a fiber.
|
|
194
|
+
Returns whether a Fiber has rendered. It does not identify whether the render happened during a specific commit.
|
|
273
195
|
|
|
274
196
|
```typescript
|
|
275
|
-
import {
|
|
276
|
-
|
|
277
|
-
// ...
|
|
197
|
+
import { didFiberRender } from "bippy";
|
|
278
198
|
|
|
279
|
-
|
|
280
|
-
console.log(next, prev);
|
|
281
|
-
});
|
|
199
|
+
console.log(didFiberRender(fiber));
|
|
282
200
|
```
|
|
283
201
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
set and get a persistent identity for a fiber. by default, fibers are anonymous and have no identity.
|
|
202
|
+
Use `traverseRenderedFibers` to inspect renders from a specific commit.
|
|
287
203
|
|
|
288
|
-
|
|
289
|
-
import { setFiberId, getFiberId } from "bippy";
|
|
204
|
+
### `didFiberCommit`
|
|
290
205
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
setFiberId(fiber);
|
|
294
|
-
console.log("unique id for fiber:", getFiberId(fiber));
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
### isHostFiber
|
|
298
|
-
|
|
299
|
-
returns `true` if the fiber is a host fiber (e.g., a DOM node in react-dom).
|
|
206
|
+
Returns whether a Fiber or its subtree has committed work. It does not identify a specific commit.
|
|
300
207
|
|
|
301
208
|
```typescript
|
|
302
|
-
import {
|
|
209
|
+
import { didFiberCommit } from "bippy";
|
|
303
210
|
|
|
304
|
-
|
|
305
|
-
console.log("fiber is a host fiber");
|
|
306
|
-
}
|
|
211
|
+
console.log(didFiberCommit(fiber));
|
|
307
212
|
```
|
|
308
213
|
|
|
309
|
-
###
|
|
214
|
+
### `setFiberId`
|
|
310
215
|
|
|
311
|
-
|
|
216
|
+
Assigns a numeric ID to a Fiber.
|
|
312
217
|
|
|
313
218
|
```typescript
|
|
314
|
-
import {
|
|
219
|
+
import { setFiberId } from "bippy";
|
|
315
220
|
|
|
316
|
-
|
|
317
|
-
console.log("fiber is a composite fiber");
|
|
318
|
-
}
|
|
221
|
+
setFiberId(fiber, 123);
|
|
319
222
|
```
|
|
320
223
|
|
|
321
|
-
###
|
|
224
|
+
### `getFiberId`
|
|
322
225
|
|
|
323
|
-
|
|
226
|
+
Returns a stable numeric ID across Fiber updates. It creates an ID when none has been assigned.
|
|
324
227
|
|
|
325
228
|
```typescript
|
|
326
|
-
import {
|
|
229
|
+
import { getFiberId } from "bippy";
|
|
327
230
|
|
|
328
|
-
|
|
231
|
+
const fiberId = getFiberId(fiber);
|
|
329
232
|
```
|
|
330
233
|
|
|
331
|
-
###
|
|
234
|
+
### `isFiber`
|
|
332
235
|
|
|
333
|
-
|
|
236
|
+
Returns whether a value contains the core fields required by a Fiber.
|
|
334
237
|
|
|
335
|
-
```
|
|
336
|
-
import {
|
|
337
|
-
import { memo } from "react";
|
|
338
|
-
|
|
339
|
-
const RealComponent = () => {
|
|
340
|
-
return <div>hello</div>;
|
|
341
|
-
};
|
|
342
|
-
const MemoizedComponent = memo(RealComponent);
|
|
238
|
+
```typescript
|
|
239
|
+
import { isFiber } from "bippy";
|
|
343
240
|
|
|
344
|
-
console.log(
|
|
241
|
+
console.log(isFiber(value));
|
|
345
242
|
```
|
|
346
243
|
|
|
347
|
-
###
|
|
244
|
+
### `isHostFiber`
|
|
348
245
|
|
|
349
|
-
|
|
246
|
+
Returns whether a Fiber represents a renderer host instance, such as a DOM element or React Native view.
|
|
350
247
|
|
|
351
|
-
```
|
|
352
|
-
import {
|
|
353
|
-
|
|
354
|
-
// ...
|
|
248
|
+
```typescript
|
|
249
|
+
import { isHostFiber } from "bippy";
|
|
355
250
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
<>
|
|
359
|
-
<div>hello</div>
|
|
360
|
-
<div>world</div>
|
|
361
|
-
</>
|
|
362
|
-
);
|
|
251
|
+
if (isHostFiber(fiber)) {
|
|
252
|
+
console.log(fiber.stateNode);
|
|
363
253
|
}
|
|
364
|
-
|
|
365
|
-
console.log(getNearestHostFiber(fiberForComponent)); // <div>hello</div>
|
|
366
|
-
console.log(getNearestHostFibers(fiberForComponent)); // [<div>hello</div>, <div>world</div>]
|
|
367
254
|
```
|
|
368
255
|
|
|
369
|
-
###
|
|
256
|
+
### `isCompositeFiber`
|
|
370
257
|
|
|
371
|
-
|
|
258
|
+
Returns whether a Fiber represents a function, class, memo, or forward-ref component.
|
|
372
259
|
|
|
373
260
|
```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.
|
|
261
|
+
import { isCompositeFiber } from "bippy";
|
|
384
262
|
|
|
385
|
-
|
|
386
|
-
[fiber, fiber.return, fiber.return.return, ...]
|
|
263
|
+
console.log(isCompositeFiber(fiber));
|
|
387
264
|
```
|
|
388
265
|
|
|
389
|
-
###
|
|
266
|
+
### `hasMemoCache`
|
|
390
267
|
|
|
391
|
-
|
|
268
|
+
Returns whether a Fiber uses a React Compiler memo cache.
|
|
392
269
|
|
|
393
270
|
```typescript
|
|
394
|
-
import {
|
|
271
|
+
import { hasMemoCache } from "bippy";
|
|
395
272
|
|
|
396
|
-
console.log(
|
|
273
|
+
console.log(hasMemoCache(fiber));
|
|
397
274
|
```
|
|
398
275
|
|
|
399
|
-
###
|
|
276
|
+
### `getDisplayName`
|
|
400
277
|
|
|
401
|
-
|
|
278
|
+
Returns the display name of a Fiber type.
|
|
402
279
|
|
|
403
280
|
```typescript
|
|
404
|
-
import {
|
|
281
|
+
import { getDisplayName } from "bippy";
|
|
405
282
|
|
|
406
|
-
console.log(
|
|
283
|
+
console.log(getDisplayName(fiber.type));
|
|
407
284
|
```
|
|
408
285
|
|
|
409
|
-
###
|
|
286
|
+
### `getType`
|
|
410
287
|
|
|
411
|
-
|
|
288
|
+
Unwraps memo and forward-ref wrappers and returns the underlying component definition.
|
|
412
289
|
|
|
413
290
|
```typescript
|
|
414
|
-
import {
|
|
291
|
+
import { getType } from "bippy";
|
|
415
292
|
|
|
416
|
-
|
|
417
|
-
console.log(fiber);
|
|
293
|
+
console.log(getType(fiber.type));
|
|
418
294
|
```
|
|
419
295
|
|
|
420
|
-
### getLatestFiber
|
|
296
|
+
### `getLatestFiber`
|
|
421
297
|
|
|
422
|
-
|
|
298
|
+
Returns the latest version of a Fiber. Use it when you retain a Fiber across renders.
|
|
423
299
|
|
|
424
300
|
```typescript
|
|
425
|
-
import { getLatestFiber } from "bippy";
|
|
426
|
-
|
|
427
|
-
const latestFiber = getLatestFiber(getFiberFromHostInstance(document.querySelector("div")));
|
|
428
|
-
console.log(latestFiber);
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
### overrideProps
|
|
432
|
-
|
|
433
|
-
overrides component props at runtime by modifying the fiber's props.
|
|
301
|
+
import { getFiber, getLatestFiber } from "bippy";
|
|
434
302
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
// override props on a fiber
|
|
439
|
-
overrideProps(fiber, {
|
|
440
|
-
title: "new title",
|
|
441
|
-
config: {
|
|
442
|
-
enabled: true,
|
|
443
|
-
count: 42,
|
|
444
|
-
},
|
|
445
|
-
});
|
|
303
|
+
const fiber = getFiber(document.body);
|
|
304
|
+
const latestFiber = fiber ? getLatestFiber(fiber) : null;
|
|
446
305
|
```
|
|
447
306
|
|
|
448
|
-
|
|
307
|
+
### `getRenderer`
|
|
449
308
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
overrides hook state (`useState`, `useReducer`, etc.) at runtime by hook id.
|
|
309
|
+
Returns the React renderer that owns a Fiber, or `null` when the renderer is unavailable.
|
|
453
310
|
|
|
454
311
|
```typescript
|
|
455
|
-
import {
|
|
456
|
-
|
|
457
|
-
// override the first hook (id: 0) with a new value
|
|
458
|
-
overrideHookState(fiber, 0, "new state value");
|
|
312
|
+
import { getRenderer } from "bippy";
|
|
459
313
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
name: "john",
|
|
464
|
-
age: 30,
|
|
465
|
-
},
|
|
466
|
-
});
|
|
314
|
+
const renderer = getRenderer(fiber);
|
|
315
|
+
renderer?.overrideProps?.(fiber, ["title"], "new title");
|
|
316
|
+
renderer?.scheduleUpdate?.(fiber);
|
|
467
317
|
```
|
|
468
318
|
|
|
469
|
-
|
|
319
|
+
Renderer capabilities are optional and vary by renderer version.
|
|
470
320
|
|
|
471
|
-
###
|
|
321
|
+
### React internals
|
|
472
322
|
|
|
473
|
-
|
|
323
|
+
The main `bippy` entry point exports the React internals used by its APIs.
|
|
474
324
|
|
|
475
325
|
```typescript
|
|
476
|
-
import {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
326
|
+
import {
|
|
327
|
+
MutationMask,
|
|
328
|
+
ReactBuildType,
|
|
329
|
+
ReactFiberFlags,
|
|
330
|
+
ReactSymbols,
|
|
331
|
+
getReactWorkTags,
|
|
332
|
+
getReactWorkTagsForFiber,
|
|
333
|
+
getReactWorkTagsForRenderer,
|
|
334
|
+
} from "bippy";
|
|
335
|
+
import type {
|
|
336
|
+
Fiber,
|
|
337
|
+
FiberRoot,
|
|
338
|
+
ReactDevToolsGlobalHook,
|
|
339
|
+
ReactRenderer,
|
|
340
|
+
RendererDispatcherRef,
|
|
341
|
+
} from "bippy";
|
|
489
342
|
```
|
|
490
343
|
|
|
491
|
-
|
|
344
|
+
These definitions follow React’s private implementation and may change between React versions.
|
|
345
|
+
|
|
346
|
+
### `getSource`
|
|
492
347
|
|
|
493
|
-
|
|
348
|
+
Returns the source location for a Fiber from these renderers:
|
|
494
349
|
|
|
495
|
-
|
|
350
|
+
- DOM
|
|
351
|
+
- Native
|
|
352
|
+
- Terminal
|
|
353
|
+
- Canvas
|
|
354
|
+
- PDF
|
|
355
|
+
- Custom
|
|
496
356
|
|
|
497
357
|
```typescript
|
|
498
358
|
import { getSource } from "bippy/source";
|
|
499
359
|
|
|
500
|
-
const fiber = getFiberFromHostInstance(hostInstance);
|
|
501
360
|
const source = await getSource(fiber);
|
|
502
|
-
|
|
503
|
-
// columnNumber: 12,
|
|
504
|
-
// fileName: 'path/to/file.tsx',
|
|
505
|
-
// lineNumber: 12,
|
|
506
|
-
// }
|
|
361
|
+
console.log(source);
|
|
507
362
|
```
|
|
508
363
|
|
|
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.
|
|
364
|
+
Production builds may omit source information. Runtimes without `fetch` receive unsymbolicated locations.
|
|
519
365
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
returns a symbolicated stack of components above a fiber.
|
|
523
|
-
|
|
524
|
-
`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).
|
|
525
|
-
|
|
526
|
-
`getParentStack` walks _all_ ancestors in the render tree (the fiber's `return` chain), including `{children}` wrappers. works on every react version.
|
|
366
|
+
Pass a custom `SourceFetch` for packaged bundles, virtual filesystems, or renderer-specific URLs:
|
|
527
367
|
|
|
528
368
|
```typescript
|
|
529
|
-
import {
|
|
530
|
-
|
|
531
|
-
const ownerFrames = await getOwnerStack(fiber);
|
|
532
|
-
// [{ functionName: "Button", fileName: "src/button.tsx", lineNumber: 12, ... }, ...]
|
|
369
|
+
import { getSource, type SourceFetch } from "bippy/source";
|
|
533
370
|
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
371
|
+
const sourceFetch: SourceFetch = async (url, init) => {
|
|
372
|
+
const artifact = sourceArtifacts.get(url);
|
|
373
|
+
if (!artifact) return fetch(url, init);
|
|
537
374
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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);
|
|
375
|
+
const sourceMapUrl = artifact.sourceMapUrl;
|
|
376
|
+
const headers = sourceMapUrl ? { SourceMap: sourceMapUrl } : undefined;
|
|
377
|
+
return new Response(artifact.content, { headers });
|
|
561
378
|
};
|
|
562
379
|
|
|
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
|
-
});
|
|
380
|
+
const source = await getSource(fiber, true, sourceFetch);
|
|
597
381
|
```
|
|
598
382
|
|
|
599
|
-
|
|
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.
|
|
383
|
+
### `getOwnerStack`
|
|
602
384
|
|
|
603
|
-
|
|
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 |
|
|
385
|
+
Returns the symbolicated stack of components that created a Fiber’s JSX. It falls back to the parent stack when owner information is unavailable.
|
|
611
386
|
|
|
612
|
-
|
|
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.
|
|
387
|
+
```typescript
|
|
388
|
+
import { getOwnerStack } from "bippy/source";
|
|
617
389
|
|
|
618
|
-
|
|
390
|
+
const ownerFrames = await getOwnerStack(fiber);
|
|
391
|
+
```
|
|
619
392
|
|
|
620
|
-
|
|
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
|
|
393
|
+
### `getParentStack`
|
|
630
394
|
|
|
631
|
-
|
|
395
|
+
Returns the symbolicated stack of every ancestor in a Fiber’s return chain.
|
|
632
396
|
|
|
633
|
-
|
|
397
|
+
```typescript
|
|
398
|
+
import { getParentStack } from "bippy/source";
|
|
634
399
|
|
|
635
|
-
|
|
400
|
+
const parentFrames = await getParentStack(fiber);
|
|
401
|
+
```
|
|
636
402
|
|
|
637
|
-
|
|
403
|
+
## Acknowledgements
|
|
638
404
|
|
|
639
|
-
|
|
405
|
+
[@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.
|