bippy 0.6.1-dev.61475ed → 0.6.1-dev.8585943

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.
Files changed (69) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +180 -500
  3. package/dist/core.cjs +1 -1
  4. package/dist/core.d.cts +45 -105
  5. package/dist/core.d.ts +45 -105
  6. package/dist/core.js +1 -1
  7. package/dist/core2.cjs +9 -0
  8. package/dist/core2.d.cts +11 -3
  9. package/dist/core2.d.ts +11 -3
  10. package/dist/core2.js +9 -0
  11. package/dist/errors.d.cts +410 -0
  12. package/dist/errors.d.ts +410 -0
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +14 -3
  15. package/dist/index.d.ts +14 -3
  16. package/dist/index.js +1 -1
  17. package/dist/install-hook-only.cjs +1 -1
  18. package/dist/install-hook-only.d.cts +9 -1
  19. package/dist/install-hook-only.d.ts +9 -1
  20. package/dist/install-hook-only.js +1 -1
  21. package/dist/rdt-hook.cjs +1 -1
  22. package/dist/rdt-hook.js +1 -1
  23. package/dist/source.cjs +14 -5
  24. package/dist/source.d.cts +86 -69
  25. package/dist/source.d.ts +86 -69
  26. package/dist/source.js +14 -5
  27. package/package.json +26 -17
  28. package/src/core.ts +340 -673
  29. package/src/errors.ts +34 -0
  30. package/src/index.ts +1 -0
  31. package/src/install-hook-only.ts +2 -2
  32. package/src/rdt-hook.ts +160 -161
  33. package/src/react-internals/generated/react-work-tags.ts +318 -0
  34. package/src/react-internals/index.ts +67 -0
  35. package/src/react-internals/semver.ts +80 -0
  36. package/src/react-internals/types.ts +186 -0
  37. package/src/react.ts +76 -0
  38. package/src/source/constants.ts +1 -1
  39. package/src/source/error-stack.ts +11 -0
  40. package/src/source/get-display-name-from-source.ts +44 -40
  41. package/src/source/get-source.ts +43 -26
  42. package/src/source/index.ts +11 -1
  43. package/src/source/inspect-hooks.ts +220 -207
  44. package/src/source/owner-stack.ts +119 -174
  45. package/src/source/parse-debug-stack.ts +4 -4
  46. package/src/source/parse-hook-names.ts +14 -53
  47. package/src/source/parse-stack.ts +14 -31
  48. package/src/source/renderer-dispatchers.ts +30 -0
  49. package/src/source/symbolication.ts +709 -120
  50. package/dist/get-source.cjs +0 -19
  51. package/dist/get-source.js +0 -19
  52. package/dist/index.iife.js +0 -9
  53. package/dist/install-hook-only.iife.js +0 -9
  54. package/dist/react-refresh.cjs +0 -9
  55. package/dist/react-refresh.d.cts +0 -66
  56. package/dist/react-refresh.d.ts +0 -66
  57. package/dist/react-refresh.js +0 -9
  58. package/dist/unsubscribe.d.cts +0 -298
  59. package/dist/unsubscribe.d.ts +0 -298
  60. package/src/react-refresh/constants.ts +0 -9
  61. package/src/react-refresh/detect-hmr-transport.ts +0 -33
  62. package/src/react-refresh/index.ts +0 -173
  63. package/src/react-refresh/metro-hmr-transport.ts +0 -188
  64. package/src/react-refresh/next-webpack-hmr-transport.ts +0 -72
  65. package/src/react-refresh/normalize-hmr-file-path.ts +0 -24
  66. package/src/react-refresh/types.ts +0 -7
  67. package/src/react-refresh/vite-hmr-transport.ts +0 -116
  68. package/src/types.ts +0 -438
  69. package/src/unsubscribe.ts +0 -17
package/README.md CHANGED
@@ -1,655 +1,335 @@
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>;
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:
9
+ bippy hacks into React internals.
108
10
 
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)_
11
+ 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.
117
12
 
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.
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 internals unless you really, _really_ have to. By proceeding, you acknowledge the risk of breaking your own code or apps that use your code.
17
+
18
+ ## Table of contents
19
+
20
+ - [Install bippy](#install-bippy)
21
+ - [Next.js](#nextjs)
22
+ - [Vite](#vite)
23
+ - [React integration](#react-integration)
24
+ - [`useFiber`](#usefiber)
25
+ - [Instrumentation](#instrumentation)
26
+ - [`instrument`](#instrument)
27
+ - [`getRDTHook`](#getrdthook)
28
+ - [Fiber traversal](#fiber-traversal)
29
+ - [`traverseRenderedFibers`](#traverserenderedfibers)
30
+ - [`traverseFiber`](#traversefiber)
31
+ - [`didFiberRender` and `didFiberCommit`](#didfiberrender-and-didfibercommit)
32
+ - [Fiber inspection](#fiber-inspection)
33
+ - [`setFiberId` and `getFiberId`](#setfiberid-and-getfiberid)
34
+ - [Classification helpers](#classification-helpers)
35
+ - [`getDisplayName` and `getType`](#getdisplayname-and-gettype)
36
+ - [`getFiber`](#getfiber)
37
+ - [`getLatestFiber`](#getlatestfiber)
38
+ - [`getRenderer`](#getrenderer)
39
+ - [React internals](#react-internals)
40
+ - [Source inspection](#source-inspection)
41
+ - [`getSource`](#getsource)
42
+ - [`getOwnerStack` and `getParentStack`](#getownerstack-and-getparentstack)
43
+ - [Acknowledgements](#acknowledgements)
44
+
45
+ ## Install bippy
46
+
47
+ Install bippy:
123
48
 
124
49
  ```shell
125
50
  npm install bippy
126
51
  ```
127
52
 
128
- since bippy must load before react, some bundlers need specific configuration to get the import order right.
53
+ Import bippy before React or any React renderer.
129
54
 
130
- ### next.js
55
+ ### Next.js
131
56
 
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):
57
+ 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
58
 
134
59
  ```typescript
135
- // instrumentation-client.ts
136
60
  import "bippy";
137
61
  ```
138
62
 
139
- this file executes before react hydration, making it the ideal place to initialize bippy.
140
-
141
- ### vite
63
+ ### Vite
142
64
 
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:
65
+ Import bippy at the top of your Vite entry point, before any React imports:
144
66
 
145
67
  ```typescript
146
- // src/main.tsx
147
68
  import "bippy";
148
69
  import { StrictMode } from "react";
149
70
  import { createRoot } from "react-dom/client";
150
-
151
- // ... rest of your code
152
71
  ```
153
72
 
154
- the import order is critical: import bippy before any react packages.
73
+ ## React integration
155
74
 
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.
75
+ ### `useFiber`
157
76
 
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
- > ```
77
+ Returns the calling component’s Fiber. During server rendering it returns `undefined` because there is no client Fiber for the component.
166
78
 
167
- ## API reference
79
+ ```tsx
80
+ import { useFiber } from "bippy";
168
81
 
169
- ### instrument
82
+ const Component = () => {
83
+ const fiber = useFiber();
84
+ console.log(fiber?.type);
85
+ return null;
86
+ };
87
+ ```
88
+
89
+ ## Instrumentation
90
+
91
+ Listen for React lifecycle events with `instrument`.
92
+
93
+ ### `instrument`
94
+
95
+ Registers lifecycle handlers and returns an unsubscribe function.
170
96
 
171
- patches `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with your handlers. import bippy before react, and call `instrument` before any other methods.
97
+ Available handlers include:
172
98
 
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`).
99
+ - `onActive`: runs when instrumentation becomes active
100
+ - `onScheduleFiberRoot`: runs when React schedules a root
101
+ - `onCommitFiberRoot`: runs when React commits a root
102
+ - `onPostCommitFiberRoot`: runs after commit effects
103
+ - `onCommitFiberUnmount`: runs when React unmounts a Fiber
174
104
 
175
105
  ```typescript
176
- import { instrument } from "bippy"; // must be imported BEFORE react
106
+ import { instrument } from "bippy";
177
107
  import * as React from "react";
178
108
 
179
109
  const unsubscribe = instrument({
180
110
  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);
111
+ console.log(rendererID, root.current);
188
112
  },
189
113
  });
190
114
 
191
- // later, stop listening (other instrument() subscribers keep working)
192
115
  unsubscribe();
193
116
  ```
194
117
 
195
- ### getRDTHook
118
+ Call the returned function to unsubscribe those handlers.
119
+
120
+ ### `getRDTHook`
196
121
 
197
- returns the `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` object. great for advanced use cases, such as accessing or modifying the `renderers` property.
122
+ Returns `globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__`. Use it when an integration needs the complete renderer registry or another low-level hook capability.
198
123
 
199
124
  ```typescript
200
125
  import { getRDTHook } from "bippy";
201
126
 
202
127
  const hook = getRDTHook();
203
- console.log(hook);
128
+ console.log(hook.renderers);
204
129
  ```
205
130
 
206
- ### traverseRenderedFibers
207
-
208
- not every fiber in the fiber tree renders. `traverseRenderedFibers` allows you to traverse the fiber tree and determine which fibers have actually rendered.
209
-
210
- ```typescript
211
- import { instrument, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
212
- import * as React from "react";
131
+ ## Fiber traversal
213
132
 
214
- instrument({
215
- onCommitFiberRoot(rendererID, root) {
216
- traverseRenderedFibers(root, (fiber) => {
217
- console.log("fiber rendered", fiber);
218
- });
219
- },
220
- });
221
- ```
133
+ Traversal helpers walk a complete Fiber tree or select the Fibers involved in a commit.
222
134
 
223
- ### traverseFiber
135
+ ### `traverseRenderedFibers`
224
136
 
225
- calls a callback on every fiber in the fiber tree.
137
+ Visits Fibers that mounted, updated, or unmounted in a commit. The callback receives the Fiber and its `mount`, `update`, or `unmount` phase.
226
138
 
227
139
  ```typescript
228
- import { instrument, traverseFiber } from "bippy"; // must be imported BEFORE react
140
+ import { instrument, traverseRenderedFibers } from "bippy";
229
141
  import * as React from "react";
230
142
 
231
143
  instrument({
232
144
  onCommitFiberRoot(rendererID, root) {
233
- traverseFiber(root.current, (fiber) => {
234
- console.log(fiber);
145
+ traverseRenderedFibers(root, (fiber, phase) => {
146
+ console.log(rendererID, phase, fiber);
235
147
  });
236
148
  },
237
149
  });
238
150
  ```
239
151
 
240
- ### traverseProps
152
+ Call it with the same root across commits so bippy can compare the current and previous trees.
241
153
 
242
- traverses the props of a fiber.
154
+ ### `traverseFiber`
243
155
 
244
- ```typescript
245
- import { traverseProps } from "bippy";
156
+ 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.
246
157
 
247
- // ...
158
+ ```typescript
159
+ import { isHostFiber, traverseFiber } from "bippy";
248
160
 
249
- traverseProps(fiber, (propName, next, prev) => {
250
- console.log(propName, next, prev);
161
+ const buttonFiber = traverseFiber(root.current, (fiber) => {
162
+ return isHostFiber(fiber) && fiber.type === "button";
251
163
  });
252
164
  ```
253
165
 
254
- ### traverseState
166
+ The selector can also return a promise. In that case, `traverseFiber` returns a promise for the selected Fiber.
255
167
 
256
- traverses the state (`useState`, `useReducer`, etc.) and effects that set state of a fiber.
168
+ ### `didFiberRender` and `didFiberCommit`
257
169
 
258
- ```typescript
259
- import { traverseState } from "bippy";
170
+ Return whether a Fiber has rendered or committed. Use `traverseRenderedFibers` to inspect changes from a specific commit.
260
171
 
261
- // ...
172
+ ```typescript
173
+ import { didFiberCommit, didFiberRender } from "bippy";
262
174
 
263
- traverseState(fiber, (next, prev) => {
264
- console.log(next, prev);
265
- });
175
+ console.log(didFiberRender(fiber));
176
+ console.log(didFiberCommit(fiber));
266
177
  ```
267
178
 
268
- ### traverseContexts
269
-
270
- traverses the contexts (`useContext`) of a fiber.
271
-
272
- ```typescript
273
- import { traverseContexts } from "bippy";
274
-
275
- // ...
179
+ ## Fiber inspection
276
180
 
277
- traverseContexts(fiber, (next, prev) => {
278
- console.log(next, prev);
279
- });
280
- ```
181
+ Inspection helpers identify Fibers and read their component, host instance, and renderer metadata.
281
182
 
282
- ### setFiberId / getFiberId
183
+ ### `setFiberId` and `getFiberId`
283
184
 
284
- set and get a persistent identity for a fiber. by default, fibers are anonymous and have no identity.
185
+ Assign and read a stable numeric identity across Fiber updates. `getFiberId` creates an identity when one does not exist.
285
186
 
286
187
  ```typescript
287
- import { setFiberId, getFiberId } from "bippy";
188
+ import { getFiberId, setFiberId } from "bippy";
288
189
 
289
- // ...
290
-
291
- setFiberId(fiber);
292
- console.log("unique id for fiber:", getFiberId(fiber));
190
+ setFiberId(fiber, 123);
191
+ console.log(getFiberId(fiber));
293
192
  ```
294
193
 
295
- ### isHostFiber
194
+ ### Classification helpers
195
+
196
+ Use these predicates to narrow an unknown value or Fiber before reading renderer-specific fields:
296
197
 
297
- returns `true` if the fiber is a host fiber (e.g., a DOM node in react-dom).
198
+ | Helper | Result |
199
+ | ------------------ | ------------------------------------------------------- |
200
+ | `isFiber` | Performs a fast check for a Fiber-like object |
201
+ | `isValidFiber` | Checks the core fields required by a Fiber |
202
+ | `isHostFiber` | Narrows a Fiber to a host Fiber |
203
+ | `isCompositeFiber` | Finds function, class, memo, and other composite Fibers |
204
+ | `hasMemoCache` | Detects React Compiler memo cache data |
298
205
 
299
206
  ```typescript
300
207
  import { isHostFiber } from "bippy";
301
208
 
302
209
  if (isHostFiber(fiber)) {
303
- console.log("fiber is a host fiber");
210
+ console.log(fiber.stateNode);
304
211
  }
305
212
  ```
306
213
 
307
- ### isCompositeFiber
214
+ ### `getDisplayName` and `getType`
308
215
 
309
- 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).
216
+ `getDisplayName` reads a component name from a Fiber type. `getType` unwraps memo and forward-ref wrappers to return the underlying component definition.
310
217
 
311
218
  ```typescript
312
- import { isCompositeFiber } from "bippy";
219
+ import { getDisplayName, getType } from "bippy";
313
220
 
314
- if (isCompositeFiber(fiber)) {
315
- console.log("fiber is a composite fiber");
316
- }
221
+ console.log(getDisplayName(fiber.type));
222
+ console.log(getType(fiber.type));
317
223
  ```
318
224
 
319
- ### getDisplayName
225
+ ### `getFiber`
320
226
 
321
- returns the display name of the fiber's component, falling back to the component's function or class name if available.
227
+ 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.
322
228
 
323
229
  ```typescript
324
- import { getDisplayName } from "bippy";
325
-
326
- console.log(getDisplayName(fiber));
327
- ```
328
-
329
- ### getType
330
-
331
- returns the underlying type (the component definition) for a given fiber. for example, this could be a function component or class component.
332
-
333
- ```jsx
334
- import { getType } from "bippy";
335
- import { memo } from "react";
336
-
337
- const RealComponent = () => {
338
- return <div>hello</div>;
339
- };
340
- const MemoizedComponent = memo(() => {
341
- return <div>hello</div>;
342
- });
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.
350
-
351
- ```jsx
352
- import { getNearestHostFiber, getNearestHostFibers } from "bippy";
353
-
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.
230
+ import { getFiber } from "bippy";
372
231
 
373
- ```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
- }
232
+ const element = document.querySelector("button");
233
+ const fiber = getFiber(element);
379
234
  ```
380
235
 
381
- ### getFiberStack
382
-
383
- returns an array representing the stack of fibers from the current fiber up to the root.
384
-
385
- ```typescript
386
- [fiber, fiber.return, fiber.return.return, ...]
387
- ```
236
+ `getFiberFromHostInstance` remains available as an alias.
388
237
 
389
- ### getMutatedHostFibers
238
+ ### `getLatestFiber`
390
239
 
391
- returns an array of all host fibers that have committed and rendered in the provided fiber's subtree.
240
+ Returns the latest version of a Fiber. Use it when you retain a Fiber across renders.
392
241
 
393
242
  ```typescript
394
- import { getMutatedHostFibers } from "bippy";
243
+ import { getFiber, getLatestFiber } from "bippy";
395
244
 
396
- console.log(getMutatedHostFibers(fiber));
245
+ const fiber = getFiber(document.body);
246
+ const latestFiber = fiber ? getLatestFiber(fiber) : null;
397
247
  ```
398
248
 
399
- ### isValidFiber
249
+ ### `getRenderer`
400
250
 
401
- returns `true` if the given object is a valid React Fiber (i.e., has a tag, stateNode, return, child, sibling, etc.).
251
+ Returns the React renderer that owns a Fiber, or `null` when the renderer is unavailable.
402
252
 
403
253
  ```typescript
404
- import { isValidFiber } from "bippy";
254
+ import { getRenderer } from "bippy";
405
255
 
406
- console.log(isValidFiber(fiber));
256
+ const renderer = getRenderer(fiber);
257
+ renderer?.overrideProps?.(fiber, ["title"], "new title");
258
+ renderer?.scheduleUpdate?.(fiber);
407
259
  ```
408
260
 
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
- ```
261
+ Renderer capabilities are optional and vary by renderer version.
419
262
 
420
- ### getLatestFiber
263
+ ### React internals
421
264
 
422
- returns the latest fiber (since it may be double-buffered). usually use this in combination with `getFiberFromHostInstance`.
265
+ 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
266
 
424
267
  ```typescript
425
- import { getLatestFiber } from "bippy";
426
-
427
- const latestFiber = getLatestFiber(getFiberFromHostInstance(document.querySelector("div")));
428
- console.log(latestFiber);
268
+ import {
269
+ MutationMask,
270
+ ReactBuildType,
271
+ ReactFiberFlags,
272
+ ReactSymbols,
273
+ getReactWorkTags,
274
+ getReactWorkTagsForFiber,
275
+ getReactWorkTagsForRenderer,
276
+ } from "bippy";
277
+ import type {
278
+ Fiber,
279
+ FiberRoot,
280
+ ReactDevToolsGlobalHook,
281
+ ReactRenderer,
282
+ RendererDispatcherRef,
283
+ } from "bippy";
429
284
  ```
430
285
 
431
- ### overrideProps
432
-
433
- overrides component props at runtime by modifying the fiber's props.
286
+ These definitions follow React’s private implementation and may change between React versions.
434
287
 
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
- ```
288
+ ## Source inspection
447
289
 
448
- the function accepts a fiber and a partial object containing the props to override. bippy automatically flattens nested objects into property paths.
290
+ Source utilities resolve component locations, source maps, and component stacks. Import them from `bippy/source`.
449
291
 
450
- ### overrideHookState
292
+ ### `getSource`
451
293
 
452
- overrides hook state (`useState`, `useReducer`, etc.) at runtime by hook id.
294
+ Returns the source location for a Fiber across DOM, native, terminal, canvas, PDF, and custom renderers.
453
295
 
454
296
  ```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");
297
+ import { getSource } from "bippy/source";
459
298
 
460
- // override nested state object
461
- overrideHookState(fiber, 1, {
462
- user: {
463
- name: "john",
464
- age: 30,
465
- },
466
- });
299
+ const source = await getSource(fiber);
300
+ console.log(source);
467
301
  ```
468
302
 
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.
303
+ Production builds may omit source information. Runtimes without `fetch` receive unsymbolicated locations.
470
304
 
471
- ### overrideContext
472
-
473
- overrides react context values at runtime by finding the appropriate context provider.
305
+ Pass a custom `SourceFetch` for packaged bundles, virtual filesystems, or renderer-specific URLs:
474
306
 
475
307
  ```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.
308
+ import { getSource, type SourceFetch } from "bippy/source";
492
309
 
493
- ### getSource
310
+ const sourceFetch: SourceFetch = async (url, init) => {
311
+ const artifact = sourceArtifacts.get(url);
312
+ if (!artifact) return fetch(url, init);
494
313
 
495
- gets the source code location of a composite fiber.
496
-
497
- ```typescript
498
- import { getSource } from "bippy/source";
499
-
500
- // random fiber on the DOM
501
- const hostFiber = getFiberFromHostInstance(document.querySelector("div"));
314
+ return new Response(artifact.content, {
315
+ headers: artifact.sourceMapUrl ? { SourceMap: artifact.sourceMapUrl } : undefined,
316
+ });
317
+ };
502
318
 
503
- // get nearest composite fiber up the tree
504
- const compositeFiber = traverseFiber(
505
- hostFiber,
506
- (fiber) => {
507
- if (isCompositeFiber(fiber)) {
508
- return fiber;
509
- }
510
- },
511
- true,
512
- );
513
-
514
- const source = await getSource(compositeFiber);
515
- // {
516
- // columnNumber: 12,
517
- // fileName: 'path/to/file.tsx',
518
- // lineNumber: 12,
519
- // }
319
+ const source = await getSource(fiber, true, sourceFetch);
520
320
  ```
521
321
 
522
- > **caveats:**
523
- >
524
- > - only available in dev mode
525
- > - only works for composite fibers (function/class components)
526
- > - captures the location where the component is _used_, not where it's _defined_
527
- > - in react 18, resolves `_debugSource` directly (see [react#31981](https://github.com/facebook/react/issues/31981))
528
- > - in react >18, `_debugSource` is not available for host fibers
529
-
530
- ### getOwnerStack / getParentStack
531
-
532
- returns a symbolicated stack of components above a fiber.
533
-
534
- `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).
322
+ ### `getOwnerStack` and `getParentStack`
535
323
 
536
- `getParentStack` walks _all_ ancestors in the render tree (the fiber's `return` chain), including `{children}` wrappers. works on every react version.
324
+ 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.
537
325
 
538
326
  ```typescript
539
327
  import { getOwnerStack, getParentStack } from "bippy/source";
540
328
 
541
329
  const ownerFrames = await getOwnerStack(fiber);
542
- // [{ functionName: "Button", fileName: "src/button.tsx", lineNumber: 12, ... }, ...]
543
-
544
330
  const parentFrames = await getParentStack(fiber);
545
- // includes every wrapper between the fiber and the root
546
- ```
547
-
548
- ### instrumentReactRefresh
549
-
550
- subscribes to fast refresh (HMR) updates from `bippy/react-refresh`. works with any bundler that uses react-refresh (vite, next.js webpack, next.js turbopack, metro) without bundler-specific code: bippy auto-detects the bundler's HMR transport and augments each update with the hot-updated source file paths.
551
-
552
- the handler runs after react has re-rendered with the new component types, so `updatedFibers`/`staleFibers` are the mounted fibers matching the hot-swapped component types.
553
-
554
- returns an unsubscribe function (a no-op during SSR, so no environment checks needed). the returned function is also a `Disposable`, so it works with `using`.
555
-
556
- ```typescript
557
- import { instrumentReactRefresh } from "bippy/react-refresh";
558
- import { getDisplayName } from "bippy";
559
-
560
- const unsubscribe = instrumentReactRefresh({
561
- onRefresh(update) {
562
- for (const fiber of update.updatedFibers) {
563
- console.log("hot updated:", getDisplayName(fiber.type));
564
- }
565
- console.log("changed files:", update.filePaths);
566
- },
567
- });
568
-
569
- // later
570
- unsubscribe();
571
331
  ```
572
332
 
573
- ## example
574
-
575
- here's a mini toy version of [`react-scan`](https://github.com/aidenybai/react-scan) that highlights renders in your app.
576
-
577
- ```javascript
578
- import { instrument, getNearestHostFiber, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
579
-
580
- const highlightFiber = (fiber) => {
581
- if (!(fiber.stateNode instanceof HTMLElement)) return;
582
- // fiber.stateNode is a DOM element
583
- const rect = fiber.stateNode.getBoundingClientRect();
584
- const highlight = document.createElement("div");
585
- highlight.style.border = "1px solid red";
586
- highlight.style.position = "fixed";
587
- highlight.style.top = `${rect.top}px`;
588
- highlight.style.left = `${rect.left}px`;
589
- highlight.style.width = `${rect.width}px`;
590
- highlight.style.height = `${rect.height}px`;
591
- highlight.style.zIndex = "999999999";
592
- document.documentElement.appendChild(highlight);
593
- setTimeout(() => {
594
- document.documentElement.removeChild(highlight);
595
- }, 100);
596
- };
597
-
598
- /**
599
- * `instrument` is a function that installs the react DevTools global
600
- * hook and allows you to set up custom handlers for react fiber events.
601
- */
602
- instrument({
603
- /**
604
- * `onCommitFiberRoot` is a handler that is called when react is
605
- * ready to commit a fiber root. this means that react is has
606
- * rendered your entire app and is ready to apply changes to
607
- * the host tree (e.g. via DOM mutations).
608
- */
609
- onCommitFiberRoot(rendererID, root) {
610
- /**
611
- * `traverseRenderedFibers` traverses the fiber tree and determines which
612
- * fibers have actually rendered.
613
- *
614
- * A fiber tree contains many fibers that may have not rendered. this
615
- * can be because it bailed out (e.g. `useMemo`) or because it wasn't
616
- * actually rendered (if <Child> re-rendered, then <Parent> didn't
617
- * actually render, but exists in the fiber tree).
618
- */
619
- traverseRenderedFibers(root, (fiber) => {
620
- /**
621
- * `getNearestHostFiber` is a utility function that finds the
622
- * nearest host fiber to a given fiber.
623
- *
624
- * a host fiber for `react-dom` is a fiber that has a DOM element
625
- * as its `stateNode`.
626
- */
627
- const hostFiber = getNearestHostFiber(fiber);
628
- highlightFiber(hostFiber);
629
- });
630
- },
631
- });
632
- ```
633
-
634
- ## glossary
635
-
636
- - fiber: a “unit of execution” in react, representing a component or dom element
637
- - commit: the process of applying changes to the host tree (e.g. DOM mutations)
638
- - render: the process of building the fiber tree by executing component function/classes
639
- - host tree: the tree of UI elements that react mutates (e.g. DOM elements)
640
- - reconciler (or “renderer”): custom bindings for react, e.g. react-dom, react-native, react-three-fiber, etc to mutate the host tree
641
- - `rendererID`: the id of the reconciler, starting at 1 (can be from multiple reconciler instances)
642
- - `root`: a special `FiberRoot` type that contains the container fiber (the one you pass to `ReactDOM.createRoot`) in the `current` property
643
- - `onCommitFiberRoot`: called when react is ready to commit a fiber root
644
- - `onPostCommitFiberRoot`: called when react has committed a fiber root and effects have run
645
- - `onCommitFiberUnmount`: called when a fiber unmounts
646
-
647
- ## misc
648
-
649
- 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.
650
-
651
- 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.
652
-
653
- 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.
333
+ ## Acknowledgements
654
334
 
655
335
  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.