bippy 0.6.1-dev.3702582 → 0.6.1-dev.4ed5147

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,639 +1,332 @@
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><img src="./.github/public/bippy.png" width="48" alt="" align="middle" /> bippy</h1>
7
2
 
8
3
  [![version](https://img.shields.io/npm/v/bippy?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/bippy)
9
4
  [![downloads](https://img.shields.io/npm/dt/bippy.svg?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/bippy)
10
5
 
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;
6
+ bippy hacks into React internals.
49
7
 
50
- // stateNode is the host fiber (e.g. DOM element)
51
- stateNode: Node | null;
8
+ React normally keeps its Fiber tree out of reach. bippy gets you in, so you can inspect components, track renders, and access the renderer directly.
52
9
 
53
- // 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>;
94
-
95
- // called when react has rendered everything for an update and the fiber tree is fully built and ready to
96
- // apply changes to the host tree (e.g. DOM mutations)
97
- onCommitFiberRoot: (rendererID: RendererID, root: FiberRoot, commitPriority?: number) => void;
98
-
99
- // called when effects run
100
- onPostCommitFiberRoot: (rendererID: RendererID, root: FiberRoot) => void;
101
-
102
- // called when a specific fiber unmounts
103
- onCommitFiberUnmount: (rendererID: RendererID, fiber: Fiber) => void;
104
- }
105
- ```
106
-
107
- bippy works by monkey-patching `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with our own custom handlers. bippy simplifies this by providing utility functions like:
108
-
109
- - `instrument` to safely patch `window.__REACT_DEVTOOLS_GLOBAL_HOOK__`
110
- - _(instead of directly mutating `onCommitFiberRoot`, …)_
111
- - `traverseRenderedFibers` to traverse the fiber tree and determine which fibers have actually rendered
112
- - _(instead of `child`, `sibling`, and `return` pointers)_
113
- - `traverseFiber` to traverse the fiber tree, regardless of whether it has rendered
114
- - _(instead of `child`, `sibling`, and `return` pointers)_
115
- - `setFiberId` / `getFiberId` to set and get a fiber's id
116
- - _(instead of anonymous fibers with no identity)_
117
-
118
- ## how to use
119
-
120
- we recommend installing via npm.
121
-
122
- import this package before your react app runs. it adds a special object to the global scope that react reports its internals to (react devtools uses the same mechanism). as soon as react loads and attaches, bippy starts collecting data about what is going on in react's internals.
10
+ > [!WARNING]
11
+ > ⚠️⚠️⚠️ **This project may break production apps and cause unexpected behavior.** ⚠️⚠️⚠️
12
+ >
13
+ > This project uses React internals, which can change at any time. We don't recommend depending on internals unless you really, _really_ have to. By proceeding, you acknowledge the risk of breaking your own code or apps that use your code.
14
+
15
+ ## Table of contents
16
+
17
+ - [Install bippy](#install-bippy)
18
+ - [Next.js](#nextjs)
19
+ - [Vite](#vite)
20
+ - [React integration](#react-integration)
21
+ - [`useFiber`](#usefiber)
22
+ - [Instrumentation](#instrumentation)
23
+ - [`instrument`](#instrument)
24
+ - [`getRDTHook`](#getrdthook)
25
+ - [Fiber traversal](#fiber-traversal)
26
+ - [`traverseRenderedFibers`](#traverserenderedfibers)
27
+ - [`traverseFiber`](#traversefiber)
28
+ - [`didFiberRender` and `didFiberCommit`](#didfiberrender-and-didfibercommit)
29
+ - [Fiber inspection](#fiber-inspection)
30
+ - [`setFiberId` and `getFiberId`](#setfiberid-and-getfiberid)
31
+ - [Classification helpers](#classification-helpers)
32
+ - [`getDisplayName` and `getType`](#getdisplayname-and-gettype)
33
+ - [`getFiber`](#getfiber)
34
+ - [`getLatestFiber`](#getlatestfiber)
35
+ - [`getRenderer`](#getrenderer)
36
+ - [React internals](#react-internals)
37
+ - [Source inspection](#source-inspection)
38
+ - [`getSource`](#getsource)
39
+ - [`getOwnerStack` and `getParentStack`](#getownerstack-and-getparentstack)
40
+ - [Acknowledgements](#acknowledgements)
41
+
42
+ ## Install bippy
43
+
44
+ Install bippy:
123
45
 
124
46
  ```shell
125
47
  npm install bippy
126
48
  ```
127
49
 
128
- since bippy must load before react, some bundlers need specific configuration to get the import order right.
50
+ Import bippy before React or any React renderer.
129
51
 
130
- ### next.js
52
+ ### Next.js
131
53
 
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):
54
+ Next.js 15.3 and later can load bippy through [`instrumentation-client.ts`](https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation-client). Create the file at the project root or in `src`:
133
55
 
134
56
  ```typescript
135
- // instrumentation-client.ts
136
57
  import "bippy";
137
58
  ```
138
59
 
139
- this file executes before react hydration, making it the ideal place to initialize bippy.
60
+ ### Vite
140
61
 
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:
62
+ Import bippy at the top of your Vite entry point, before any React imports:
144
63
 
145
64
  ```typescript
146
- // src/main.tsx
147
65
  import "bippy";
148
66
  import { StrictMode } from "react";
149
67
  import { createRoot } from "react-dom/client";
150
-
151
- // ... rest of your code
152
68
  ```
153
69
 
154
- the import order is critical: import bippy before any react packages.
70
+ ## React integration
155
71
 
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.
72
+ ### `useFiber`
157
73
 
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
- > ```
74
+ Returns the calling component’s Fiber. During server rendering it returns `undefined` because there is no client Fiber for the component.
75
+
76
+ ```tsx
77
+ import { useFiber } from "bippy";
78
+
79
+ const Component = () => {
80
+ const fiber = useFiber();
81
+ console.log(fiber?.type);
82
+ return null;
83
+ };
84
+ ```
166
85
 
167
- ## API reference
86
+ ## Instrumentation
168
87
 
169
- ### instrument
88
+ Listen for React lifecycle events with `instrument`.
170
89
 
171
- patches `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with your handlers. import bippy before react, and call `instrument` before any other methods.
90
+ ### `instrument`
172
91
 
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`).
92
+ Registers lifecycle handlers and returns an unsubscribe function.
93
+
94
+ Available handlers include:
95
+
96
+ - `onActive`: runs when instrumentation becomes active
97
+ - `onScheduleFiberRoot`: runs when React schedules a root
98
+ - `onCommitFiberRoot`: runs when React commits a root
99
+ - `onPostCommitFiberRoot`: runs after commit effects
100
+ - `onCommitFiberUnmount`: runs when React unmounts a Fiber
174
101
 
175
102
  ```typescript
176
- import { instrument } from "bippy"; // must be imported BEFORE react
103
+ import { instrument } from "bippy";
177
104
  import * as React from "react";
178
105
 
179
106
  const unsubscribe = instrument({
180
107
  onCommitFiberRoot(rendererID, root) {
181
- console.log("root ready to commit", root);
182
- },
183
- onPostCommitFiberRoot(rendererID, root) {
184
- console.log("root with effects committed", root);
185
- },
186
- onCommitFiberUnmount(rendererID, fiber) {
187
- console.log("fiber unmounted", fiber);
108
+ console.log(rendererID, root.current);
188
109
  },
189
110
  });
190
111
 
191
- // later, stop listening (other instrument() subscribers keep working)
192
112
  unsubscribe();
193
113
  ```
194
114
 
195
- 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.
115
+ Call the returned function to unsubscribe those handlers.
196
116
 
197
- ### getRDTHook
117
+ ### `getRDTHook`
198
118
 
199
- returns the `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` object. great for advanced use cases, such as accessing or modifying the `renderers` property.
119
+ Returns `globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__`. Use it when an integration needs the complete renderer registry or another low-level hook capability.
200
120
 
201
121
  ```typescript
202
122
  import { getRDTHook } from "bippy";
203
123
 
204
124
  const hook = getRDTHook();
205
- console.log(hook);
125
+ console.log(hook.renderers);
206
126
  ```
207
127
 
208
- ### traverseRenderedFibers
209
-
210
- not every fiber in the fiber tree renders. `traverseRenderedFibers` allows you to traverse the fiber tree and determine which fibers have actually rendered.
128
+ ## Fiber traversal
211
129
 
212
- ```typescript
213
- import { instrument, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
214
- import * as React from "react";
215
-
216
- instrument({
217
- onCommitFiberRoot(rendererID, root) {
218
- traverseRenderedFibers(root, (fiber) => {
219
- console.log("fiber rendered", fiber);
220
- });
221
- },
222
- });
223
- ```
130
+ Traversal helpers walk a complete Fiber tree or select the Fibers involved in a commit.
224
131
 
225
- ### traverseFiber
132
+ ### `traverseRenderedFibers`
226
133
 
227
- calls a callback on every fiber in the fiber tree.
134
+ Visits Fibers that mounted, updated, or unmounted in a commit. The callback receives the Fiber and its `mount`, `update`, or `unmount` phase.
228
135
 
229
136
  ```typescript
230
- import { instrument, traverseFiber } from "bippy"; // must be imported BEFORE react
137
+ import { instrument, traverseRenderedFibers } from "bippy";
231
138
  import * as React from "react";
232
139
 
233
140
  instrument({
234
141
  onCommitFiberRoot(rendererID, root) {
235
- traverseFiber(root.current, (fiber) => {
236
- console.log(fiber);
142
+ traverseRenderedFibers(root, (fiber, phase) => {
143
+ console.log(rendererID, phase, fiber);
237
144
  });
238
145
  },
239
146
  });
240
147
  ```
241
148
 
242
- ### traverseProps
149
+ Call it with the same root across commits so bippy can compare the current and previous trees.
243
150
 
244
- traverses the props of a fiber.
151
+ ### `traverseFiber`
245
152
 
246
- ```typescript
247
- import { traverseProps } from "bippy";
153
+ Walks down from a Fiber and calls a selector for each node. Return `true` to stop and return the selected Fiber. Pass `true` as the third argument to walk toward the root instead.
248
154
 
249
- // ...
155
+ ```typescript
156
+ import { isHostFiber, traverseFiber } from "bippy";
250
157
 
251
- traverseProps(fiber, (propName, next, prev) => {
252
- console.log(propName, next, prev);
158
+ const buttonFiber = traverseFiber(root.current, (fiber) => {
159
+ return isHostFiber(fiber) && fiber.type === "button";
253
160
  });
254
161
  ```
255
162
 
256
- ### traverseState
163
+ The selector can also return a promise. In that case, `traverseFiber` returns a promise for the selected Fiber.
257
164
 
258
- traverses the state (`useState`, `useReducer`, etc.) and effects that set state of a fiber.
165
+ ### `didFiberRender` and `didFiberCommit`
259
166
 
260
- ```typescript
261
- import { traverseState } from "bippy";
167
+ Return whether a Fiber has rendered or committed. Use `traverseRenderedFibers` to inspect changes from a specific commit.
262
168
 
263
- // ...
169
+ ```typescript
170
+ import { didFiberCommit, didFiberRender } from "bippy";
264
171
 
265
- traverseState(fiber, (next, prev) => {
266
- console.log(next, prev);
267
- });
172
+ console.log(didFiberRender(fiber));
173
+ console.log(didFiberCommit(fiber));
268
174
  ```
269
175
 
270
- ### traverseContexts
271
-
272
- traverses the contexts (`useContext`) of a fiber.
176
+ ## Fiber inspection
273
177
 
274
- ```typescript
275
- import { traverseContexts } from "bippy";
276
-
277
- // ...
278
-
279
- traverseContexts(fiber, (next, prev) => {
280
- console.log(next, prev);
281
- });
282
- ```
178
+ Inspection helpers identify Fibers and read their component, host instance, and renderer metadata.
283
179
 
284
- ### setFiberId / getFiberId
180
+ ### `setFiberId` and `getFiberId`
285
181
 
286
- set and get a persistent identity for a fiber. by default, fibers are anonymous and have no identity.
182
+ Assign and read a stable numeric identity across Fiber updates. `getFiberId` creates an identity when one does not exist.
287
183
 
288
184
  ```typescript
289
- import { setFiberId, getFiberId } from "bippy";
185
+ import { getFiberId, setFiberId } from "bippy";
290
186
 
291
- // ...
292
-
293
- setFiberId(fiber);
294
- console.log("unique id for fiber:", getFiberId(fiber));
187
+ setFiberId(fiber, 123);
188
+ console.log(getFiberId(fiber));
295
189
  ```
296
190
 
297
- ### isHostFiber
191
+ ### Classification helpers
192
+
193
+ Use these predicates to narrow an unknown value or Fiber before reading renderer-specific fields:
298
194
 
299
- returns `true` if the fiber is a host fiber (e.g., a DOM node in react-dom).
195
+ | Helper | Result |
196
+ | ------------------ | ------------------------------------------------------- |
197
+ | `isFiber` | Performs a fast check for a Fiber-like object |
198
+ | `isValidFiber` | Checks the core fields required by a Fiber |
199
+ | `isHostFiber` | Narrows a Fiber to a host Fiber |
200
+ | `isCompositeFiber` | Finds function, class, memo, and other composite Fibers |
201
+ | `hasMemoCache` | Detects React Compiler memo cache data |
300
202
 
301
203
  ```typescript
302
204
  import { isHostFiber } from "bippy";
303
205
 
304
206
  if (isHostFiber(fiber)) {
305
- console.log("fiber is a host fiber");
207
+ console.log(fiber.stateNode);
306
208
  }
307
209
  ```
308
210
 
309
- ### isCompositeFiber
211
+ ### `getDisplayName` and `getType`
310
212
 
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).
213
+ `getDisplayName` reads a component name from a Fiber type. `getType` unwraps memo and forward-ref wrappers to return the underlying component definition.
312
214
 
313
215
  ```typescript
314
- import { isCompositeFiber } from "bippy";
216
+ import { getDisplayName, getType } from "bippy";
315
217
 
316
- if (isCompositeFiber(fiber)) {
317
- console.log("fiber is a composite fiber");
318
- }
218
+ console.log(getDisplayName(fiber.type));
219
+ console.log(getType(fiber.type));
319
220
  ```
320
221
 
321
- ### getDisplayName
222
+ ### `getFiber`
322
223
 
323
- returns the display name of the fiber's component, falling back to the component's function or class name if available.
224
+ Returns the Fiber associated with a renderer host instance, such as a DOM element. The result is `null` when no registered renderer recognizes the instance.
324
225
 
325
226
  ```typescript
326
- import { getDisplayName } from "bippy";
227
+ import { getFiber } from "bippy";
327
228
 
328
- console.log(getDisplayName(fiber));
229
+ const element = document.querySelector("button");
230
+ const fiber = getFiber(element);
329
231
  ```
330
232
 
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);
343
-
344
- console.log(getType(fiberForMemoizedComponent) === RealComponent);
345
- ```
346
-
347
- ### getNearestHostFiber / getNearestHostFibers
348
-
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.
233
+ `getFiberFromHostInstance` remains available as an alias.
350
234
 
351
- ```jsx
352
- import { getNearestHostFiber, getNearestHostFibers } from "bippy";
235
+ ### `getLatestFiber`
353
236
 
354
- // ...
355
-
356
- function Component() {
357
- return (
358
- <>
359
- <div>hello</div>
360
- <div>world</div>
361
- </>
362
- );
363
- }
364
-
365
- console.log(getNearestHostFiber(fiberForComponent)); // <div>hello</div>
366
- console.log(getNearestHostFibers(fiberForComponent)); // [<div>hello</div>, <div>world</div>]
367
- ```
368
-
369
- ### getTimings
370
-
371
- returns the self and total render times for the fiber.
237
+ Returns the latest version of a Fiber. Use it when you retain a Fiber across renders.
372
238
 
373
239
  ```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.
240
+ import { getFiber, getLatestFiber } from "bippy";
384
241
 
385
- ```typescript
386
- [fiber, fiber.return, fiber.return.return, ...]
387
- ```
388
-
389
- ### getMutatedHostFibers
390
-
391
- returns an array of all host fibers that have committed and rendered in the provided fiber's subtree.
392
-
393
- ```typescript
394
- import { getMutatedHostFibers } from "bippy";
395
-
396
- console.log(getMutatedHostFibers(fiber));
242
+ const fiber = getFiber(document.body);
243
+ const latestFiber = fiber ? getLatestFiber(fiber) : null;
397
244
  ```
398
245
 
399
- ### isValidFiber
246
+ ### `getRenderer`
400
247
 
401
- returns `true` if the given object is a valid React Fiber (i.e., has a tag, stateNode, return, child, sibling, etc.).
248
+ Returns the React renderer that owns a Fiber, or `null` when the renderer is unavailable.
402
249
 
403
250
  ```typescript
404
- import { isValidFiber } from "bippy";
251
+ import { getRenderer } from "bippy";
405
252
 
406
- console.log(isValidFiber(fiber));
253
+ const renderer = getRenderer(fiber);
254
+ renderer?.overrideProps?.(fiber, ["title"], "new title");
255
+ renderer?.scheduleUpdate?.(fiber);
407
256
  ```
408
257
 
409
- ### getFiberFromHostInstance
410
-
411
- returns the fiber associated with a given host instance (e.g., a DOM element).
412
-
413
- ```typescript
414
- import { getFiberFromHostInstance } from "bippy";
415
-
416
- const fiber = getFiberFromHostInstance(document.querySelector("div"));
417
- console.log(fiber);
418
- ```
258
+ Renderer capabilities are optional and vary by renderer version.
419
259
 
420
- ### getLatestFiber
260
+ ### React internals
421
261
 
422
- returns the latest fiber (since it may be double-buffered). usually use this in combination with `getFiberFromHostInstance`.
262
+ The main `bippy` entry point exports the complete internals model used by its APIs. This includes Fiber, root, renderer, dispatcher, work-tag, flag, symbol, and build-type definitions.
423
263
 
424
264
  ```typescript
425
- import { getLatestFiber } from "bippy";
426
-
427
- const latestFiber = getLatestFiber(getFiberFromHostInstance(document.querySelector("div")));
428
- console.log(latestFiber);
265
+ import {
266
+ MutationMask,
267
+ ReactBuildType,
268
+ ReactFiberFlags,
269
+ ReactSymbols,
270
+ getReactWorkTags,
271
+ getReactWorkTagsForFiber,
272
+ getReactWorkTagsForRenderer,
273
+ } from "bippy";
274
+ import type {
275
+ Fiber,
276
+ FiberRoot,
277
+ ReactDevToolsGlobalHook,
278
+ ReactRenderer,
279
+ RendererDispatcherRef,
280
+ } from "bippy";
429
281
  ```
430
282
 
431
- ### overrideProps
283
+ These definitions follow React’s private implementation and may change between React versions.
432
284
 
433
- overrides component props at runtime by modifying the fiber's props.
434
-
435
- ```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
- ```
285
+ ## Source inspection
447
286
 
448
- the function accepts a fiber and a partial object containing the props to override. bippy automatically flattens nested objects into property paths.
287
+ Source utilities resolve component locations, source maps, and component stacks. Import them from `bippy/source`.
449
288
 
450
- ### overrideHookState
289
+ ### `getSource`
451
290
 
452
- overrides hook state (`useState`, `useReducer`, etc.) at runtime by hook id.
291
+ Returns the source location for a Fiber across DOM, native, terminal, canvas, PDF, and custom renderers.
453
292
 
454
293
  ```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");
294
+ import { getSource } from "bippy/source";
459
295
 
460
- // override nested state object
461
- overrideHookState(fiber, 1, {
462
- user: {
463
- name: "john",
464
- age: 30,
465
- },
466
- });
296
+ const source = await getSource(fiber);
297
+ console.log(source);
467
298
  ```
468
299
 
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.
300
+ Production builds may omit source information. Runtimes without `fetch` receive unsymbolicated locations.
470
301
 
471
- ### overrideContext
472
-
473
- overrides react context values at runtime by finding the appropriate context provider.
302
+ Pass a custom `SourceFetch` for packaged bundles, virtual filesystems, or renderer-specific URLs:
474
303
 
475
304
  ```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
305
+ import { getSource, type SourceFetch } from "bippy/source";
494
306
 
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.
307
+ const sourceFetch: SourceFetch = async (url, init) => {
308
+ const artifact = sourceArtifacts.get(url);
309
+ if (!artifact) return fetch(url, init);
496
310
 
497
- ```typescript
498
- import { getSource } from "bippy/source";
311
+ return new Response(artifact.content, {
312
+ headers: artifact.sourceMapUrl ? { SourceMap: artifact.sourceMapUrl } : undefined,
313
+ });
314
+ };
499
315
 
500
- const fiber = getFiberFromHostInstance(hostInstance);
501
- const source = await getSource(fiber);
502
- // {
503
- // columnNumber: 12,
504
- // fileName: 'path/to/file.tsx',
505
- // lineNumber: 12,
506
- // }
316
+ const source = await getSource(fiber, true, sourceFetch);
507
317
  ```
508
318
 
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
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.
319
+ ### `getOwnerStack` and `getParentStack`
523
320
 
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.
321
+ Both functions return symbolicated component stacks above a Fiber. `getOwnerStack` follows the components that created the Fiber’s JSX. `getParentStack` follows every ancestor in the Fiber return chain.
527
322
 
528
323
  ```typescript
529
324
  import { getOwnerStack, getParentStack } from "bippy/source";
530
325
 
531
326
  const ownerFrames = await getOwnerStack(fiber);
532
- // [{ functionName: "Button", fileName: "src/button.tsx", lineNumber: 12, ... }, ...]
533
-
534
327
  const parentFrames = await getParentStack(fiber);
535
- // includes every wrapper between the fiber and the root
536
328
  ```
537
329
 
538
- ## example
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.
330
+ ## Acknowledgements
638
331
 
639
332
  the original bippy character is owned and created by [@dairyfreerice](https://www.instagram.com/dairyfreerice). this project is not related to the bippy brand, i just think the character is cute.