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 CHANGED
@@ -1,658 +1,355 @@
1
- > [!WARNING]
2
- > ⚠️⚠️⚠️ **this project may break production apps and cause unexpected behavior** ⚠️⚠️⚠️
3
- >
4
- > this project uses react internals, which can change at any time. we don't recommend depending on internals unless you really, _really_ have to. by proceeding, you acknowledge the risk of breaking your own code or apps that use your code.
5
-
6
- # <img src="https://github.com/aidenybai/bippy/blob/main/.github/public/bippy.png?raw=true" width="60" align="center" /> bippy
1
+ <h1>
2
+ <img src="./.github/public/bippy.png" width="48" alt="" valign="middle" />
3
+ bippy
4
+ </h1>
7
5
 
8
6
  [![version](https://img.shields.io/npm/v/bippy?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/bippy)
9
7
  [![downloads](https://img.shields.io/npm/dt/bippy.svg?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/bippy)
10
8
 
11
- bippy is a toolkit to **hack into react internals**
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
- // 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;
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
- // called when effects run
100
- onPostCommitFiberRoot: (rendererID: RendererID, root: FiberRoot) => void;
101
-
102
- // called when a specific fiber unmounts
103
- onCommitFiberUnmount: (rendererID: RendererID, fiber: Fiber) => void;
104
- }
105
- ```
106
-
107
- bippy works by monkey-patching `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with our own custom handlers. bippy simplifies this by providing utility functions like:
108
-
109
- - `instrument` to safely patch `window.__REACT_DEVTOOLS_GLOBAL_HOOK__`
110
- - _(instead of directly mutating `onCommitFiberRoot`, …)_
111
- - `traverseRenderedFibers` to traverse the fiber tree and determine which fibers have actually rendered
112
- - _(instead of `child`, `sibling`, and `return` pointers)_
113
- - `traverseFiber` to traverse the fiber tree, regardless of whether it has rendered
114
- - _(instead of `child`, `sibling`, and `return` pointers)_
115
- - `setFiberId` / `getFiberId` to set and get a fiber's id
116
- - _(instead of anonymous fibers with no identity)_
117
-
118
- ## how to use
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
- we recommend installing via npm.
18
+ ## Install bippy
121
19
 
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.
20
+ Install bippy:
123
21
 
124
22
  ```shell
125
23
  npm install bippy
126
24
  ```
127
25
 
128
- since bippy must load before react, some bundlers need specific configuration to get the import order right.
26
+ Import bippy before React or any React renderer.
129
27
 
130
- ### next.js
28
+ ### Next.js
131
29
 
132
- in next.js 15.3+, use the [`instrumentation-client.js`](https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation-client) file to ensure bippy loads before react. create this file at the root of your application (or inside the `src` folder if you're using the src directory structure):
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
- this file executes before react hydration, making it the ideal place to initialize bippy.
36
+ ### Vite
140
37
 
141
- ### vite
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
- // ... rest of your code
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
- the import order is critical: import bippy before any react packages.
57
+ `getFiberFromHostInstance` is an alias for `getFiber`.
155
58
 
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.
59
+ ## `useFiber`
157
60
 
158
- > ```typescript
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
- > ```
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
- ## API reference
73
+ ## `instrument`
168
74
 
169
- ### instrument
75
+ Registers lifecycle handlers and returns an unsubscribe function.
170
76
 
171
- patches `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with your handlers. import bippy before react, and call `instrument` before any other methods.
77
+ Available handlers include:
172
78
 
173
- bippy patches each hook event once and dispatches it to a set of listeners, so multiple `instrument` calls compose instead of replacing each other. `instrument` returns an unsubscribe function that removes exactly the handlers you registered (also a `Disposable`, so it works with `using`).
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"; // must be imported BEFORE react
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("fiber unmounted", fiber);
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
- instrumentation, React DevTools, and hook-listener failures propagate unchanged. callback dispatch stops at the failure so the caller controls error handling. failures created by bippy, such as unsupported hooks and source-map timeouts, use the exported `BippyError` subclasses.
97
+ Call the returned function to unsubscribe those handlers.
196
98
 
197
- ### getRDTHook
99
+ ## `getRDTHook`
198
100
 
199
- returns the `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` object. great for advanced use cases, such as accessing or modifying the `renderers` property.
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
- ### traverseRenderedFibers
110
+ ## `traverseRenderedFibers`
209
111
 
210
- not every fiber in the fiber tree renders. `traverseRenderedFibers` allows you to traverse the fiber tree and determine which fibers have actually rendered.
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"; // must be imported BEFORE react
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("fiber rendered", fiber);
119
+ traverseRenderedFibers(root, (fiber, phase) => {
120
+ console.log(rendererID, phase, fiber);
220
121
  });
221
122
  },
222
123
  });
223
124
  ```
224
125
 
225
- ### traverseFiber
126
+ Call it with the same root across commits so bippy can compare the current and previous trees.
226
127
 
227
- calls a callback on every fiber in the fiber tree.
128
+ ## `traverseFiber`
228
129
 
229
- ```typescript
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 { traverseProps } from "bippy";
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
- ### 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
- ```
140
+ The selector can also return a promise. In that case, `traverseFiber` returns a promise for the selected Fiber.
269
141
 
270
- ### traverseContexts
142
+ ## `didFiberRender`
271
143
 
272
- traverses the contexts (`useContext`) of a fiber.
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 { traverseContexts } from "bippy";
276
-
277
- // ...
147
+ import { didFiberRender } from "bippy";
278
148
 
279
- traverseContexts(fiber, (next, prev) => {
280
- console.log(next, prev);
281
- });
149
+ console.log(didFiberRender(fiber));
282
150
  ```
283
151
 
284
- ### setFiberId / getFiberId
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
- ### isHostFiber
154
+ ## `didFiberCommit`
298
155
 
299
- returns `true` if the fiber is a host fiber (e.g., a DOM node in react-dom).
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 { isHostFiber } from "bippy";
159
+ import { didFiberCommit } from "bippy";
303
160
 
304
- if (isHostFiber(fiber)) {
305
- console.log("fiber is a host fiber");
306
- }
161
+ console.log(didFiberCommit(fiber));
307
162
  ```
308
163
 
309
- ### isCompositeFiber
164
+ ## `setFiberId`
310
165
 
311
- returns `true` if the fiber is a composite fiber. composite fibers represent class components, function components, memoized components, and so on (anything that can actually render output).
166
+ Assigns a numeric ID to a Fiber.
312
167
 
313
168
  ```typescript
314
- import { isCompositeFiber } from "bippy";
169
+ import { setFiberId } from "bippy";
315
170
 
316
- if (isCompositeFiber(fiber)) {
317
- console.log("fiber is a composite fiber");
318
- }
171
+ setFiberId(fiber, 123);
319
172
  ```
320
173
 
321
- ### getDisplayName
174
+ ## `getFiberId`
322
175
 
323
- returns the display name of the fiber's component, falling back to the component's function or class name if available.
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 { getDisplayName } from "bippy";
179
+ import { getFiberId } from "bippy";
327
180
 
328
- console.log(getDisplayName(fiber));
181
+ const fiberId = getFiberId(fiber);
329
182
  ```
330
183
 
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.
184
+ ## `isFiber`
334
185
 
335
- ```jsx
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
- const RealComponent = () => {
340
- return <div>hello</div>;
341
- };
342
- const MemoizedComponent = memo(RealComponent);
188
+ ```typescript
189
+ import { isFiber } from "bippy";
343
190
 
344
- console.log(getType(fiberForMemoizedComponent) === RealComponent);
191
+ console.log(isFiber(value));
345
192
  ```
346
193
 
347
- ### getNearestHostFiber / getNearestHostFibers
194
+ ## `isHostFiber`
348
195
 
349
- `getNearestHostFiber` returns the closest host fiber above or below a given fiber. `getNearestHostFibers` returns all host fibers associated with the provided fiber and its subtree.
196
+ Returns whether a Fiber represents a renderer host instance, such as a DOM element or React Native view.
350
197
 
351
- ```jsx
352
- import { getNearestHostFiber, getNearestHostFibers } from "bippy";
353
-
354
- // ...
198
+ ```typescript
199
+ import { isHostFiber } from "bippy";
355
200
 
356
- function Component() {
357
- return (
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
- ### getTimings
206
+ ## `isCompositeFiber`
370
207
 
371
- returns the self and total render times for the fiber.
208
+ Returns whether a Fiber represents a function, class, memo, or forward-ref component.
372
209
 
373
210
  ```typescript
374
- // timings don't exist in react production builds
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
- ```typescript
386
- [fiber, fiber.return, fiber.return.return, ...]
213
+ console.log(isCompositeFiber(fiber));
387
214
  ```
388
215
 
389
- ### getMutatedHostFibers
216
+ ## `hasMemoCache`
390
217
 
391
- returns an array of all host fibers that have committed and rendered in the provided fiber's subtree.
218
+ Returns whether a Fiber uses a React Compiler memo cache.
392
219
 
393
220
  ```typescript
394
- import { getMutatedHostFibers } from "bippy";
221
+ import { hasMemoCache } from "bippy";
395
222
 
396
- console.log(getMutatedHostFibers(fiber));
223
+ console.log(hasMemoCache(fiber));
397
224
  ```
398
225
 
399
- ### isValidFiber
226
+ ## `getDisplayName`
400
227
 
401
- returns `true` if the given object is a valid React Fiber (i.e., has a tag, stateNode, return, child, sibling, etc.).
228
+ Returns the display name of a Fiber type.
402
229
 
403
230
  ```typescript
404
- import { isValidFiber } from "bippy";
231
+ import { getDisplayName } from "bippy";
405
232
 
406
- console.log(isValidFiber(fiber));
233
+ console.log(getDisplayName(fiber.type));
407
234
  ```
408
235
 
409
- ### getFiberFromHostInstance
236
+ ## `getType`
410
237
 
411
- returns the fiber associated with a given host instance (e.g., a DOM element).
238
+ Unwraps memo and forward-ref wrappers and returns the underlying component definition.
412
239
 
413
240
  ```typescript
414
- import { getFiberFromHostInstance } from "bippy";
241
+ import { getType } from "bippy";
415
242
 
416
- const fiber = getFiberFromHostInstance(document.querySelector("div"));
417
- console.log(fiber);
243
+ console.log(getType(fiber.type));
418
244
  ```
419
245
 
420
- ### getLatestFiber
246
+ ## `getLatestFiber`
421
247
 
422
- returns the latest fiber (since it may be double-buffered). usually use this in combination with `getFiberFromHostInstance`.
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 latestFiber = getLatestFiber(getFiberFromHostInstance(document.querySelector("div")));
428
- console.log(latestFiber);
253
+ const fiber = getFiber(document.body);
254
+ const latestFiber = fiber ? getLatestFiber(fiber) : null;
429
255
  ```
430
256
 
431
- ### overrideProps
257
+ ## `getRenderer`
432
258
 
433
- overrides component props at runtime by modifying the fiber's props.
259
+ Returns the React renderer that owns a Fiber, or `null` when the renderer is unavailable.
434
260
 
435
261
  ```typescript
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
- ```
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
- ```typescript
455
- import { overrideHookState } from "bippy";
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
- the hook id parameter corresponds to the order of hooks in the component (0-indexed). pass either a primitive value or an object for nested state updates.
269
+ Renderer capabilities are optional and vary by renderer version.
470
270
 
471
- ### overrideContext
271
+ ## React internals
472
272
 
473
- overrides react context values at runtime by finding the appropriate context provider.
273
+ The main `bippy` entry point exports the React internals used by its APIs.
474
274
 
475
275
  ```typescript
476
- import { overrideContext } from "bippy";
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
494
-
495
- gets the source code location of any fiber with development source metadata. resolution is based on fiber debug data and source maps, so it is independent of the renderer's host instances and works with DOM, native, terminal, canvas, PDF, and custom reconcilers.
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
- > **caveats:**
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
- source maps are discovered from `SourceMap` headers, source map annotations, or Metro's `.bundle` to `.map` convention. HTTP, Chrome, Firefox, and Safari WebExtension URLs work without adapters. Metro bundle options such as `platform=ios` or `platform=android` are preserved when requesting the map.
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 resource = nativeSourceArtifacts.get(url);
527
- if (!resource) return fetch(url, init);
322
+ const artifact = sourceArtifacts.get(url);
323
+ if (!artifact) return fetch(url, init);
528
324
 
529
- return new Response(resource.content, {
530
- headers: resource.sourceMapUrl ? { SourceMap: resource.sourceMapUrl } : undefined,
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
- custom loaders are isolated in their own source-map cache and may resolve schemes such as `file:`. bippy never reads those schemes through the global fetch implementation. `getSourceMap` also accepts request limits, an abort signal, and a timeout.
538
-
539
- ### getOwnerStack / getParentStack
333
+ ## `getOwnerStack`
540
334
 
541
- returns a symbolicated stack of components above a fiber.
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, getParentStack } from "bippy/source";
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
- ## example
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
- | level | renderers | coverage |
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
- 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.
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
- 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.
350
+ const parentFrames = await getParentStack(fiber);
351
+ ```
655
352
 
656
- 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.
353
+ ## Acknowledgements
657
354
 
658
- the original bippy character is owned and created by [@dairyfreerice](https://www.instagram.com/dairyfreerice). this project is not related to the bippy brand, i just think the character is cute.
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.