bippy 0.6.1 → 0.7.0-dev.07e6032

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 (67) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +223 -463
  3. package/dist/core.cjs +1 -1
  4. package/dist/core.js +1 -1
  5. package/dist/errors.d.cts +410 -0
  6. package/dist/errors.d.ts +410 -0
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.cts +154 -3
  9. package/dist/index.d.ts +154 -3
  10. package/dist/index.js +1 -1
  11. package/dist/install-hook-only.cjs +1 -1
  12. package/dist/install-hook-only.d.cts +9 -1
  13. package/dist/install-hook-only.d.ts +9 -1
  14. package/dist/install-hook-only.js +1 -1
  15. package/dist/rdt-hook.cjs +1 -1
  16. package/dist/rdt-hook.js +1 -1
  17. package/dist/source.cjs +14 -5
  18. package/dist/source.d.cts +86 -69
  19. package/dist/source.d.ts +86 -69
  20. package/dist/source.js +14 -5
  21. package/package.json +26 -27
  22. package/src/core.ts +367 -685
  23. package/src/errors.ts +34 -0
  24. package/src/index.ts +1 -0
  25. package/src/install-hook-only.ts +6 -2
  26. package/src/rdt-hook.ts +177 -156
  27. package/src/react-internals/generated/react-work-tags.ts +318 -0
  28. package/src/react-internals/index.ts +73 -0
  29. package/src/react-internals/semver.ts +80 -0
  30. package/src/react-internals/types.ts +186 -0
  31. package/src/react.ts +76 -0
  32. package/src/source/constants.ts +1 -1
  33. package/src/source/error-stack.ts +11 -0
  34. package/src/source/get-display-name-from-source.ts +44 -40
  35. package/src/source/get-source.ts +43 -26
  36. package/src/source/index.ts +11 -1
  37. package/src/source/inspect-hooks.ts +220 -207
  38. package/src/source/owner-stack.ts +121 -174
  39. package/src/source/parse-debug-stack.ts +4 -4
  40. package/src/source/parse-hook-names.ts +14 -53
  41. package/src/source/parse-stack.ts +14 -31
  42. package/src/source/renderer-dispatchers.ts +30 -0
  43. package/src/source/symbolication.ts +709 -120
  44. package/dist/core.d.cts +0 -214
  45. package/dist/core.d.ts +0 -214
  46. package/dist/core2.d.cts +0 -3
  47. package/dist/core2.d.ts +0 -3
  48. package/dist/get-source.cjs +0 -19
  49. package/dist/get-source.js +0 -19
  50. package/dist/index.iife.js +0 -9
  51. package/dist/install-hook-only.iife.js +0 -9
  52. package/dist/react-refresh.cjs +0 -9
  53. package/dist/react-refresh.d.cts +0 -66
  54. package/dist/react-refresh.d.ts +0 -66
  55. package/dist/react-refresh.js +0 -9
  56. package/dist/unsubscribe.d.cts +0 -298
  57. package/dist/unsubscribe.d.ts +0 -298
  58. package/src/react-refresh/constants.ts +0 -9
  59. package/src/react-refresh/detect-hmr-transport.ts +0 -33
  60. package/src/react-refresh/index.ts +0 -173
  61. package/src/react-refresh/metro-hmr-transport.ts +0 -188
  62. package/src/react-refresh/next-webpack-hmr-transport.ts +0 -72
  63. package/src/react-refresh/normalize-hmr-file-path.ts +0 -24
  64. package/src/react-refresh/types.ts +0 -7
  65. package/src/react-refresh/vite-hmr-transport.ts +0 -116
  66. package/src/types.ts +0 -438
  67. package/src/unsubscribe.ts +0 -17
package/README.md CHANGED
@@ -1,655 +1,415 @@
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
9
+ bippy hacks into React internals.
18
10
 
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;
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.
46
12
 
47
- child: Fiber | null;
48
- sibling: Fiber | null;
49
-
50
- // stateNode is the host fiber (e.g. DOM element)
51
- stateNode: Node | null;
13
+ > [!WARNING]
14
+ > ⚠️⚠️⚠️ **This project may break production apps and cause unexpected behavior.** ⚠️⚠️⚠️
15
+ >
16
+ > This project uses React internals, which can change at any time. We don’t recommend depending on them unless you have to. By proceeding, you acknowledge the risk of breaking your own code or apps that use your code.
52
17
 
53
- // parent fiber
54
- return: Fiber | null;
18
+ ## How React works internally
55
19
 
56
- // the previous or current version of the fiber
57
- alternate: Fiber | null;
20
+ A Fiber is both a node in React’s internal representation of the UI and a unit of work that React can schedule. As a [UI runtime](https://overreacted.io/react-as-a-ui-runtime/), React produces and maintains a tree in a host environment such as the browser. Fiber is the data structure React uses to reconcile that UI.
58
21
 
59
- // saved props input
60
- memoizedProps: any;
22
+ React builds the Fiber tree as it renders your [component tree](https://react.dev/learn/understanding-your-ui-as-a-tree). Each Fiber is a mutable object representing a component, host element, text node, or internal boundary. It stores the node’s props, state, position in the tree, and pending work.
61
23
 
62
- // state (useState, useReducer, useSES, etc.)
63
- memoizedState: any;
24
+ Consider this component tree:
64
25
 
65
- // contexts (useContext)
66
- dependencies: Dependencies | null;
26
+ ```tsx
27
+ const Button = () => <button>Save</button>;
67
28
 
68
- // effects (useEffect, useLayoutEffect, etc.)
69
- updateQueue: any;
70
- }
29
+ const App = () => (
30
+ <main>
31
+ <Button />
32
+ </main>
33
+ );
71
34
  ```
72
35
 
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.
36
+ Each rendered tree has a [`FiberRoot`](https://github.com/facebook/react/blob/beef6d60f46a97f5c20471df81760fcf365d63ef/packages/react-reconciler/src/ReactInternalTypes.js#L212-L221) container. Its `current` field points to the `HostRoot` Fiber at the top of the tree. `HostRoot`, `FunctionComponent`, and `HostComponent` are [React’s internal work tags](https://github.com/facebook/react/blob/beef6d60f46a97f5c20471df81760fcf365d63ef/packages/react-reconciler/src/ReactWorkTags.js#L44-L49).
85
37
 
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;
38
+ ```text
39
+ FiberRoot
40
+ └── current HostRoot Fiber
41
+ └── App FunctionComponent
42
+ └── main HostComponent
43
+ └── Button FunctionComponent
44
+ └── button HostComponent
45
+ ```
98
46
 
99
- // called when effects run
100
- onPostCommitFiberRoot: (rendererID: RendererID, root: FiberRoot) => void;
47
+ Fibers are actual linked objects. React defines their shape in the [`Fiber` type](https://github.com/facebook/react/blob/beef6d60f46a97f5c20471df81760fcf365d63ef/packages/react-reconciler/src/ReactInternalTypes.js#L87-L174) and initializes them in [`FiberNode`](https://github.com/facebook/react/blob/beef6d60f46a97f5c20471df81760fcf365d63ef/packages/react-reconciler/src/ReactFiber.js#L134-L173). The host Fiber for `<button>` resembles this object:
101
48
 
102
- // called when a specific fiber unmounts
103
- onCommitFiberUnmount: (rendererID: RendererID, fiber: Fiber) => void;
49
+ ```js
50
+ FiberNode {
51
+ tag: 5,
52
+ type: "button",
53
+ stateNode: HTMLButtonElement {},
54
+ return: FiberNode { … },
55
+ child: null,
56
+ sibling: null,
57
+ memoizedProps: { children: "Save" },
58
+ flags: 0,
59
+ alternate: null
104
60
  }
105
61
  ```
106
62
 
107
- bippy works by monkey-patching `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with our own custom handlers. bippy simplifies this by providing utility functions like:
63
+ The fields connect React’s component tree, pending work, and rendered output:
108
64
 
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)_
65
+ - [`tag`](https://github.com/facebook/react/blob/beef6d60f46a97f5c20471df81760fcf365d63ef/packages/react-reconciler/src/ReactInternalTypes.js#L100-L101) identifies the Fiber’s internal node kind
66
+ - [`type`](https://github.com/facebook/react/blob/beef6d60f46a97f5c20471df81760fcf365d63ef/packages/react-reconciler/src/ReactInternalTypes.js#L106-L114) identifies the component or host element, while `stateNode` points to its renderer-owned instance
67
+ - [`return`, `child`, and `sibling`](https://github.com/facebook/react/blob/beef6d60f46a97f5c20471df81760fcf365d63ef/packages/react-reconciler/src/ReactInternalTypes.js#L122-L131) form the linked tree
68
+ - [`memoizedProps`](https://github.com/facebook/react/blob/beef6d60f46a97f5c20471df81760fcf365d63ef/packages/react-reconciler/src/ReactInternalTypes.js#L142-L150) and `memoizedState` contain the inputs used to produce the current output
69
+ - [`flags`](https://github.com/facebook/react/blob/beef6d60f46a97f5c20471df81760fcf365d63ef/packages/react-reconciler/src/ReactFiberFlags.js#L14-L52) records work that React must perform during the commit phase
70
+ - [`alternate`](https://github.com/facebook/react/blob/beef6d60f46a97f5c20471df81760fcf365d63ef/packages/react-reconciler/src/ReactFiber.js#L322-L351) links the current Fiber to its work-in-progress counterpart
117
71
 
118
- ## how to use
72
+ During an update, React builds a work-in-progress tree beside the current tree. React can pause or discard this work. During the [commit phase](https://react.dev/learn/render-and-commit#step-3-react-commits-changes-to-the-dom), React applies the finished work and makes that tree current.
119
73
 
120
- we recommend installing via npm.
74
+ React does not expose Fiber as a public API. bippy “hacks into React” by accessing it anyway, giving you a consistent way to inspect Fiber trees across React versions and renderers.
121
75
 
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.
76
+ ## Install bippy
77
+
78
+ Install bippy:
123
79
 
124
80
  ```shell
125
81
  npm install bippy
126
82
  ```
127
83
 
128
- since bippy must load before react, some bundlers need specific configuration to get the import order right.
84
+ Import bippy before React or any React renderer.
129
85
 
130
- ### next.js
86
+ ### Next.js
131
87
 
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):
88
+ 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
89
 
134
90
  ```typescript
135
- // instrumentation-client.ts
136
91
  import "bippy";
137
92
  ```
138
93
 
139
- this file executes before react hydration, making it the ideal place to initialize bippy.
140
-
141
- ### vite
94
+ ### Vite
142
95
 
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:
96
+ Import bippy at the top of your Vite entry point, before any React imports:
144
97
 
145
98
  ```typescript
146
- // src/main.tsx
147
99
  import "bippy";
148
100
  import { StrictMode } from "react";
149
101
  import { createRoot } from "react-dom/client";
102
+ ```
103
+
104
+ ## API Reference
150
105
 
151
- // ... rest of your code
106
+ ### `getFiber`
107
+
108
+ 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.
109
+
110
+ ```typescript
111
+ import { getFiber } from "bippy";
112
+
113
+ const element = document.querySelector("button");
114
+ const fiber = getFiber(element);
152
115
  ```
153
116
 
154
- the import order is critical: import bippy before any react packages.
117
+ `getFiberFromHostInstance` is an alias for `getFiber`.
155
118
 
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.
119
+ ### `useFiber`
157
120
 
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
- > ```
121
+ Returns the calling component’s Fiber. During server rendering it returns `undefined` because there is no client Fiber for the component.
166
122
 
167
- ## API reference
123
+ ```tsx
124
+ import { useFiber } from "bippy";
168
125
 
169
- ### instrument
126
+ const Component = () => {
127
+ const fiber = useFiber();
128
+ console.log(fiber?.type);
129
+ return null;
130
+ };
131
+ ```
132
+
133
+ ### `instrument`
170
134
 
171
- patches `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with your handlers. import bippy before react, and call `instrument` before any other methods.
135
+ Registers lifecycle handlers and returns an unsubscribe function.
172
136
 
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`).
137
+ Available handlers include:
138
+
139
+ - `onActive`: runs when instrumentation becomes active
140
+ - `onScheduleFiberRoot`: runs when React schedules a root
141
+ - `onCommitFiberRoot`: runs when React commits a root
142
+ - `onPostCommitFiberRoot`: runs after commit effects
143
+ - `onCommitFiberUnmount`: runs when React unmounts a Fiber
174
144
 
175
145
  ```typescript
176
- import { instrument } from "bippy"; // must be imported BEFORE react
177
- import * as React from "react";
146
+ import { instrument } from "bippy";
178
147
 
179
148
  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
149
  onCommitFiberUnmount(rendererID, fiber) {
187
- console.log("fiber unmounted", fiber);
150
+ console.log(rendererID, fiber);
188
151
  },
189
152
  });
190
153
 
191
- // later, stop listening (other instrument() subscribers keep working)
192
154
  unsubscribe();
193
155
  ```
194
156
 
195
- ### getRDTHook
157
+ Call the returned function to unsubscribe those handlers.
158
+
159
+ ### `getRDTHook`
196
160
 
197
- returns the `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` object. great for advanced use cases, such as accessing or modifying the `renderers` property.
161
+ Returns the React DevTools global hook at `globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__`. Use it to access registered renderers and Fiber roots directly.
198
162
 
199
163
  ```typescript
200
164
  import { getRDTHook } from "bippy";
201
165
 
202
166
  const hook = getRDTHook();
203
- console.log(hook);
167
+ console.log(hook.renderers);
204
168
  ```
205
169
 
206
- ### traverseRenderedFibers
170
+ ### `traverseRenderedFibers`
207
171
 
208
- not every fiber in the fiber tree renders. `traverseRenderedFibers` allows you to traverse the fiber tree and determine which fibers have actually rendered.
172
+ Visits Fibers that mounted, updated, or unmounted in a commit. The callback receives the Fiber and its `mount`, `update`, or `unmount` phase.
209
173
 
210
174
  ```typescript
211
- import { instrument, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
212
- import * as React from "react";
175
+ import { instrument, traverseRenderedFibers } from "bippy";
213
176
 
214
177
  instrument({
215
178
  onCommitFiberRoot(rendererID, root) {
216
- traverseRenderedFibers(root, (fiber) => {
217
- console.log("fiber rendered", fiber);
179
+ traverseRenderedFibers(root, (fiber, phase) => {
180
+ console.log(rendererID, phase, fiber);
218
181
  });
219
182
  },
220
183
  });
221
184
  ```
222
185
 
223
- ### traverseFiber
186
+ Call it with the same root across commits so bippy can compare the current and previous trees.
224
187
 
225
- calls a callback on every fiber in the fiber tree.
188
+ ### `traverseFiber`
226
189
 
227
- ```typescript
228
- import { instrument, traverseFiber } from "bippy"; // must be imported BEFORE react
229
- import * as React from "react";
230
-
231
- instrument({
232
- onCommitFiberRoot(rendererID, root) {
233
- traverseFiber(root.current, (fiber) => {
234
- console.log(fiber);
235
- });
236
- },
237
- });
238
- ```
239
-
240
- ### traverseProps
241
-
242
- traverses the props of a fiber.
190
+ 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.
243
191
 
244
192
  ```typescript
245
- import { traverseProps } from "bippy";
246
-
247
- // ...
193
+ import { isHostFiber, traverseFiber } from "bippy";
248
194
 
249
- traverseProps(fiber, (propName, next, prev) => {
250
- console.log(propName, next, prev);
195
+ const buttonFiber = traverseFiber(fiber, (candidateFiber) => {
196
+ return isHostFiber(candidateFiber) && candidateFiber.type === "button";
251
197
  });
252
198
  ```
253
199
 
254
- ### traverseState
255
-
256
- traverses the state (`useState`, `useReducer`, etc.) and effects that set state of a fiber.
257
-
258
- ```typescript
259
- import { traverseState } from "bippy";
260
-
261
- // ...
200
+ The selector can also return a promise. In that case, `traverseFiber` returns a promise for the selected Fiber.
262
201
 
263
- traverseState(fiber, (next, prev) => {
264
- console.log(next, prev);
265
- });
266
- ```
267
-
268
- ### traverseContexts
202
+ ### `didFiberRender`
269
203
 
270
- traverses the contexts (`useContext`) of a fiber.
204
+ Returns whether a Fiber has rendered. It does not identify whether the render happened during a specific commit.
271
205
 
272
206
  ```typescript
273
- import { traverseContexts } from "bippy";
207
+ import { didFiberRender } from "bippy";
274
208
 
275
- // ...
276
-
277
- traverseContexts(fiber, (next, prev) => {
278
- console.log(next, prev);
279
- });
209
+ console.log(didFiberRender(fiber));
280
210
  ```
281
211
 
282
- ### setFiberId / getFiberId
212
+ Use `traverseRenderedFibers` to inspect renders from a specific commit.
283
213
 
284
- set and get a persistent identity for a fiber. by default, fibers are anonymous and have no identity.
214
+ ### `didFiberCommit`
285
215
 
286
- ```typescript
287
- import { setFiberId, getFiberId } from "bippy";
288
-
289
- // ...
290
-
291
- setFiberId(fiber);
292
- console.log("unique id for fiber:", getFiberId(fiber));
293
- ```
294
-
295
- ### isHostFiber
296
-
297
- returns `true` if the fiber is a host fiber (e.g., a DOM node in react-dom).
216
+ Returns whether a Fiber or its subtree has committed work. It does not identify a specific commit.
298
217
 
299
218
  ```typescript
300
- import { isHostFiber } from "bippy";
219
+ import { didFiberCommit } from "bippy";
301
220
 
302
- if (isHostFiber(fiber)) {
303
- console.log("fiber is a host fiber");
304
- }
221
+ console.log(didFiberCommit(fiber));
305
222
  ```
306
223
 
307
- ### isCompositeFiber
224
+ ### `setFiberId`
308
225
 
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).
226
+ Assigns a numeric ID to a Fiber.
310
227
 
311
228
  ```typescript
312
- import { isCompositeFiber } from "bippy";
229
+ import { setFiberId } from "bippy";
313
230
 
314
- if (isCompositeFiber(fiber)) {
315
- console.log("fiber is a composite fiber");
316
- }
231
+ setFiberId(fiber, 123);
317
232
  ```
318
233
 
319
- ### getDisplayName
234
+ ### `getFiberId`
320
235
 
321
- returns the display name of the fiber's component, falling back to the component's function or class name if available.
236
+ Returns a stable numeric ID across Fiber updates. It creates an ID when none has been assigned.
322
237
 
323
238
  ```typescript
324
- import { getDisplayName } from "bippy";
239
+ import { getFiberId } from "bippy";
325
240
 
326
- console.log(getDisplayName(fiber));
241
+ const fiberId = getFiberId(fiber);
327
242
  ```
328
243
 
329
- ### getType
244
+ ### `isFiber`
330
245
 
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";
246
+ Returns whether a value contains the core fields required by a Fiber.
336
247
 
337
- const RealComponent = () => {
338
- return <div>hello</div>;
339
- };
340
- const MemoizedComponent = memo(() => {
341
- return <div>hello</div>;
342
- });
248
+ ```typescript
249
+ import { isFiber } from "bippy";
343
250
 
344
- console.log(getType(fiberForMemoizedComponent) === RealComponent);
251
+ console.log(isFiber(value));
345
252
  ```
346
253
 
347
- ### getNearestHostFiber / getNearestHostFibers
254
+ ### `isHostFiber`
348
255
 
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.
256
+ Returns whether a Fiber represents a renderer host instance, such as a DOM element or React Native view.
350
257
 
351
- ```jsx
352
- import { getNearestHostFiber, getNearestHostFibers } from "bippy";
353
-
354
- // ...
258
+ ```typescript
259
+ import { isHostFiber } from "bippy";
355
260
 
356
- function Component() {
357
- return (
358
- <>
359
- <div>hello</div>
360
- <div>world</div>
361
- </>
362
- );
261
+ if (isHostFiber(fiber)) {
262
+ console.log(fiber.stateNode);
363
263
  }
364
-
365
- console.log(getNearestHostFiber(fiberForComponent)); // <div>hello</div>
366
- console.log(getNearestHostFibers(fiberForComponent)); // [<div>hello</div>, <div>world</div>]
367
264
  ```
368
265
 
369
- ### getTimings
266
+ ### `isCompositeFiber`
370
267
 
371
- returns the self and total render times for the fiber.
268
+ Returns whether a Fiber represents a function, class, memo, or forward-ref component.
372
269
 
373
270
  ```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.
271
+ import { isCompositeFiber } from "bippy";
384
272
 
385
- ```typescript
386
- [fiber, fiber.return, fiber.return.return, ...]
273
+ console.log(isCompositeFiber(fiber));
387
274
  ```
388
275
 
389
- ### getMutatedHostFibers
276
+ ### `hasMemoCache`
390
277
 
391
- returns an array of all host fibers that have committed and rendered in the provided fiber's subtree.
278
+ Returns whether a Fiber uses a React Compiler memo cache.
392
279
 
393
280
  ```typescript
394
- import { getMutatedHostFibers } from "bippy";
281
+ import { hasMemoCache } from "bippy";
395
282
 
396
- console.log(getMutatedHostFibers(fiber));
283
+ console.log(hasMemoCache(fiber));
397
284
  ```
398
285
 
399
- ### isValidFiber
286
+ ### `getDisplayName`
400
287
 
401
- returns `true` if the given object is a valid React Fiber (i.e., has a tag, stateNode, return, child, sibling, etc.).
288
+ Returns the display name of a Fiber type.
402
289
 
403
290
  ```typescript
404
- import { isValidFiber } from "bippy";
291
+ import { getDisplayName } from "bippy";
405
292
 
406
- console.log(isValidFiber(fiber));
293
+ console.log(getDisplayName(fiber.type));
407
294
  ```
408
295
 
409
- ### getFiberFromHostInstance
296
+ ### `getType`
410
297
 
411
- returns the fiber associated with a given host instance (e.g., a DOM element).
298
+ Unwraps memo and forward-ref wrappers and returns the underlying component definition.
412
299
 
413
300
  ```typescript
414
- import { getFiberFromHostInstance } from "bippy";
301
+ import { getType } from "bippy";
415
302
 
416
- const fiber = getFiberFromHostInstance(document.querySelector("div"));
417
- console.log(fiber);
303
+ console.log(getType(fiber.type));
418
304
  ```
419
305
 
420
- ### getLatestFiber
306
+ ### `getLatestFiber`
421
307
 
422
- returns the latest fiber (since it may be double-buffered). usually use this in combination with `getFiberFromHostInstance`.
308
+ Returns the latest version of a Fiber. Use it when you retain a Fiber across renders.
423
309
 
424
310
  ```typescript
425
- import { getLatestFiber } from "bippy";
311
+ import { getFiber, getLatestFiber } from "bippy";
426
312
 
427
- const latestFiber = getLatestFiber(getFiberFromHostInstance(document.querySelector("div")));
428
- console.log(latestFiber);
313
+ const fiber = getFiber(document.body);
314
+ const latestFiber = fiber ? getLatestFiber(fiber) : null;
429
315
  ```
430
316
 
431
- ### overrideProps
317
+ ### `getRenderer`
432
318
 
433
- overrides component props at runtime by modifying the fiber's props.
319
+ Returns the React renderer that owns a Fiber, or `null` when the renderer is unavailable.
434
320
 
435
321
  ```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
- });
322
+ import { getRenderer } from "bippy";
323
+
324
+ const renderer = getRenderer(fiber);
325
+ renderer?.overrideProps?.(fiber, ["title"], "new title");
326
+ renderer?.scheduleUpdate?.(fiber);
446
327
  ```
447
328
 
448
- the function accepts a fiber and a partial object containing the props to override. bippy automatically flattens nested objects into property paths.
329
+ Renderer capabilities are optional and vary by renderer version.
449
330
 
450
- ### overrideHookState
331
+ ### React internals
451
332
 
452
- overrides hook state (`useState`, `useReducer`, etc.) at runtime by hook id.
333
+ The main `bippy` entry point exports the React internals used by its APIs.
453
334
 
454
335
  ```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
- });
467
- ```
468
-
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.
470
-
471
- ### overrideContext
472
-
473
- overrides react context values at runtime by finding the appropriate context provider.
336
+ import {
337
+ MutationMask,
338
+ ReactBuildType,
339
+ ReactFiberFlags,
340
+ ReactSymbols,
341
+ getReactWorkTags,
342
+ getReactWorkTagsForFiber,
343
+ getReactWorkTagsForRenderer,
344
+ } from "bippy";
345
+ import type {
346
+ Fiber,
347
+ FiberRoot,
348
+ ReactDevToolsGlobalHook,
349
+ ReactRenderer,
350
+ RendererDispatcherRef,
351
+ } from "bippy";
352
+ ```
353
+
354
+ These definitions follow React’s private implementation and may change between React versions.
355
+
356
+ ### `getSource`
357
+
358
+ Returns the source location for a Fiber from these renderers:
359
+
360
+ - DOM
361
+ - Native
362
+ - Terminal
363
+ - Canvas
364
+ - PDF
365
+ - Custom
474
366
 
475
367
  ```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
- });
368
+ import { getSource } from "bippy/source";
486
369
 
487
- // override with primitive value
488
- overrideContext(fiber, ThemeContext, "dark");
370
+ const source = await getSource(fiber);
371
+ console.log(source);
489
372
  ```
490
373
 
491
- the function traverses up the fiber tree to find the context provider matching the provided context type and overrides its value.
374
+ Production builds may omit source information. Runtimes without `fetch` receive unsymbolicated locations.
492
375
 
493
- ### getSource
494
-
495
- gets the source code location of a composite fiber.
376
+ Pass a custom `SourceFetch` for packaged bundles, virtual filesystems, or renderer-specific URLs:
496
377
 
497
378
  ```typescript
498
- import { getSource } from "bippy/source";
379
+ import { getSource, type SourceFetch } from "bippy/source";
499
380
 
500
- // random fiber on the DOM
501
- const hostFiber = getFiberFromHostInstance(document.querySelector("div"));
381
+ const sourceFetch: SourceFetch = async (url, init) => {
382
+ const artifact = sourceArtifacts.get(url);
383
+ if (!artifact) return fetch(url, init);
502
384
 
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
- );
385
+ const sourceMapUrl = artifact.sourceMapUrl;
386
+ const headers = sourceMapUrl ? { SourceMap: sourceMapUrl } : undefined;
387
+ return new Response(artifact.content, { headers });
388
+ };
513
389
 
514
- const source = await getSource(compositeFiber);
515
- // {
516
- // columnNumber: 12,
517
- // fileName: 'path/to/file.tsx',
518
- // lineNumber: 12,
519
- // }
390
+ const source = await getSource(fiber, true, sourceFetch);
520
391
  ```
521
392
 
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).
393
+ ### `getOwnerStack`
535
394
 
536
- `getParentStack` walks _all_ ancestors in the render tree (the fiber's `return` chain), including `{children}` wrappers. works on every react version.
395
+ Returns the symbolicated stack of components that created a Fiber’s JSX. It falls back to the parent stack when owner information is unavailable.
537
396
 
538
397
  ```typescript
539
- import { getOwnerStack, getParentStack } from "bippy/source";
398
+ import { getOwnerStack } from "bippy/source";
540
399
 
541
400
  const ownerFrames = await getOwnerStack(fiber);
542
- // [{ functionName: "Button", fileName: "src/button.tsx", lineNumber: 12, ... }, ...]
543
-
544
- const parentFrames = await getParentStack(fiber);
545
- // includes every wrapper between the fiber and the root
546
401
  ```
547
402
 
548
- ### instrumentReactRefresh
403
+ ### `getParentStack`
549
404
 
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`.
405
+ Returns the symbolicated stack of every ancestor in a Fiber’s return chain.
555
406
 
556
407
  ```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
- ```
408
+ import { getParentStack } from "bippy/source";
572
409
 
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
- });
410
+ const parentFrames = await getParentStack(fiber);
632
411
  ```
633
412
 
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.
413
+ ## Acknowledgements
654
414
 
655
- 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.
415
+ [@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.