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.
- package/LICENSE +1 -1
- package/README.md +180 -500
- package/dist/core.cjs +1 -1
- package/dist/core.d.cts +45 -105
- package/dist/core.d.ts +45 -105
- package/dist/core.js +1 -1
- package/dist/core2.cjs +9 -0
- package/dist/core2.d.cts +11 -3
- package/dist/core2.d.ts +11 -3
- package/dist/core2.js +9 -0
- package/dist/errors.d.cts +410 -0
- package/dist/errors.d.ts +410 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.d.cts +9 -1
- package/dist/install-hook-only.d.ts +9 -1
- package/dist/install-hook-only.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +14 -5
- package/dist/source.d.cts +86 -69
- package/dist/source.d.ts +86 -69
- package/dist/source.js +14 -5
- package/package.json +26 -17
- package/src/core.ts +340 -673
- package/src/errors.ts +34 -0
- package/src/index.ts +1 -0
- package/src/install-hook-only.ts +2 -2
- package/src/rdt-hook.ts +160 -161
- package/src/react-internals/generated/react-work-tags.ts +318 -0
- package/src/react-internals/index.ts +67 -0
- package/src/react-internals/semver.ts +80 -0
- package/src/react-internals/types.ts +186 -0
- package/src/react.ts +76 -0
- package/src/source/constants.ts +1 -1
- package/src/source/error-stack.ts +11 -0
- package/src/source/get-display-name-from-source.ts +44 -40
- package/src/source/get-source.ts +43 -26
- package/src/source/index.ts +11 -1
- package/src/source/inspect-hooks.ts +220 -207
- package/src/source/owner-stack.ts +119 -174
- package/src/source/parse-debug-stack.ts +4 -4
- package/src/source/parse-hook-names.ts +14 -53
- package/src/source/parse-stack.ts +14 -31
- package/src/source/renderer-dispatchers.ts +30 -0
- package/src/source/symbolication.ts +709 -120
- package/dist/get-source.cjs +0 -19
- package/dist/get-source.js +0 -19
- package/dist/index.iife.js +0 -9
- package/dist/install-hook-only.iife.js +0 -9
- package/dist/react-refresh.cjs +0 -9
- package/dist/react-refresh.d.cts +0 -66
- package/dist/react-refresh.d.ts +0 -66
- package/dist/react-refresh.js +0 -9
- package/dist/unsubscribe.d.cts +0 -298
- package/dist/unsubscribe.d.ts +0 -298
- package/src/react-refresh/constants.ts +0 -9
- package/src/react-refresh/detect-hmr-transport.ts +0 -33
- package/src/react-refresh/index.ts +0 -173
- package/src/react-refresh/metro-hmr-transport.ts +0 -188
- package/src/react-refresh/next-webpack-hmr-transport.ts +0 -72
- package/src/react-refresh/normalize-hmr-file-path.ts +0 -24
- package/src/react-refresh/types.ts +0 -7
- package/src/react-refresh/vite-hmr-transport.ts +0 -116
- package/src/types.ts +0 -438
- package/src/unsubscribe.ts +0 -17
package/README.md
CHANGED
|
@@ -1,655 +1,335 @@
|
|
|
1
|
-
>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
>
|
|
5
|
-
|
|
6
|
-
# <img src="https://github.com/aidenybai/bippy/blob/main/.github/public/bippy.png?raw=true" width="60" align="center" /> bippy
|
|
1
|
+
<h1>
|
|
2
|
+
<img src="./.github/public/bippy.png" width="48" alt="" valign="middle" />
|
|
3
|
+
bippy
|
|
4
|
+
</h1>
|
|
7
5
|
|
|
8
6
|
[](https://npmjs.com/package/bippy)
|
|
9
7
|
[](https://npmjs.com/package/bippy)
|
|
10
8
|
|
|
11
|
-
bippy
|
|
12
|
-
|
|
13
|
-
by default, you cannot access react internals. bippy bypasses this by “pretending” to be react devtools, giving you access to the fiber tree and other internals.
|
|
14
|
-
|
|
15
|
-
- works outside of react: no react code modification needed
|
|
16
|
-
- utility functions that work across modern react (v17-19)
|
|
17
|
-
- no prior react source code knowledge required
|
|
18
|
-
|
|
19
|
-
```jsx
|
|
20
|
-
import { instrument, traverseFiber } from "bippy"; // must be imported BEFORE react
|
|
21
|
-
|
|
22
|
-
instrument({
|
|
23
|
-
onCommitFiberRoot(rendererID, root) {
|
|
24
|
-
traverseFiber(root.current, (fiber) => {
|
|
25
|
-
// prints every fiber in the current React tree
|
|
26
|
-
console.log("fiber:", fiber);
|
|
27
|
-
});
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## how it works & motivation
|
|
33
|
-
|
|
34
|
-
bippy allows you to **access** and **use** react fibers **outside** of react components.
|
|
35
|
-
|
|
36
|
-
a react fiber is a “unit of execution.” this means react will do something based on the data in a fiber. each fiber either represents a composite (function/class component) or a host (dom element).
|
|
37
|
-
|
|
38
|
-
> here is a [live visualization](https://jser.pro/ddir/rie?reactVersion=18.3.1&snippetKey=hq8jm2ylzb9u8eh468) of what the fiber tree looks like, and here is a [deep dive article](https://jser.dev/2023-07-18-how-react-rerenders/).
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
53
|
+
Import bippy before React or any React renderer.
|
|
129
54
|
|
|
130
|
-
###
|
|
55
|
+
### Next.js
|
|
131
56
|
|
|
132
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
### vite
|
|
63
|
+
### Vite
|
|
142
64
|
|
|
143
|
-
|
|
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
|
-
|
|
73
|
+
## React integration
|
|
155
74
|
|
|
156
|
-
|
|
75
|
+
### `useFiber`
|
|
157
76
|
|
|
158
|
-
|
|
159
|
-
> import "bippy/install-hook-only"; // only installs the hook
|
|
160
|
-
> import { getRDTHook, traverseFiber } from "bippy/core"; // import only what you need
|
|
161
|
-
> import * as React from "react"; // import react AFTER the hook is installed
|
|
162
|
-
>
|
|
163
|
-
> const hook = getRDTHook();
|
|
164
|
-
> // define your own utilities or use only specific ones
|
|
165
|
-
> ```
|
|
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
|
-
|
|
79
|
+
```tsx
|
|
80
|
+
import { useFiber } from "bippy";
|
|
168
81
|
|
|
169
|
-
|
|
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
|
-
|
|
97
|
+
Available handlers include:
|
|
172
98
|
|
|
173
|
-
|
|
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";
|
|
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(
|
|
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
|
-
|
|
118
|
+
Call the returned function to unsubscribe those handlers.
|
|
119
|
+
|
|
120
|
+
### `getRDTHook`
|
|
196
121
|
|
|
197
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
135
|
+
### `traverseRenderedFibers`
|
|
224
136
|
|
|
225
|
-
|
|
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,
|
|
140
|
+
import { instrument, traverseRenderedFibers } from "bippy";
|
|
229
141
|
import * as React from "react";
|
|
230
142
|
|
|
231
143
|
instrument({
|
|
232
144
|
onCommitFiberRoot(rendererID, root) {
|
|
233
|
-
|
|
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
|
-
|
|
152
|
+
Call it with the same root across commits so bippy can compare the current and previous trees.
|
|
241
153
|
|
|
242
|
-
|
|
154
|
+
### `traverseFiber`
|
|
243
155
|
|
|
244
|
-
|
|
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
|
-
|
|
250
|
-
|
|
161
|
+
const buttonFiber = traverseFiber(root.current, (fiber) => {
|
|
162
|
+
return isHostFiber(fiber) && fiber.type === "button";
|
|
251
163
|
});
|
|
252
164
|
```
|
|
253
165
|
|
|
254
|
-
|
|
166
|
+
The selector can also return a promise. In that case, `traverseFiber` returns a promise for the selected Fiber.
|
|
255
167
|
|
|
256
|
-
|
|
168
|
+
### `didFiberRender` and `didFiberCommit`
|
|
257
169
|
|
|
258
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
});
|
|
175
|
+
console.log(didFiberRender(fiber));
|
|
176
|
+
console.log(didFiberCommit(fiber));
|
|
266
177
|
```
|
|
267
178
|
|
|
268
|
-
|
|
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
|
-
|
|
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
|
|
183
|
+
### `setFiberId` and `getFiberId`
|
|
283
184
|
|
|
284
|
-
|
|
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 {
|
|
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
|
-
###
|
|
194
|
+
### Classification helpers
|
|
195
|
+
|
|
196
|
+
Use these predicates to narrow an unknown value or Fiber before reading renderer-specific fields:
|
|
296
197
|
|
|
297
|
-
|
|
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(
|
|
210
|
+
console.log(fiber.stateNode);
|
|
304
211
|
}
|
|
305
212
|
```
|
|
306
213
|
|
|
307
|
-
###
|
|
214
|
+
### `getDisplayName` and `getType`
|
|
308
215
|
|
|
309
|
-
|
|
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 {
|
|
219
|
+
import { getDisplayName, getType } from "bippy";
|
|
313
220
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
221
|
+
console.log(getDisplayName(fiber.type));
|
|
222
|
+
console.log(getType(fiber.type));
|
|
317
223
|
```
|
|
318
224
|
|
|
319
|
-
###
|
|
225
|
+
### `getFiber`
|
|
320
226
|
|
|
321
|
-
|
|
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 {
|
|
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
|
-
|
|
374
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
238
|
+
### `getLatestFiber`
|
|
390
239
|
|
|
391
|
-
|
|
240
|
+
Returns the latest version of a Fiber. Use it when you retain a Fiber across renders.
|
|
392
241
|
|
|
393
242
|
```typescript
|
|
394
|
-
import {
|
|
243
|
+
import { getFiber, getLatestFiber } from "bippy";
|
|
395
244
|
|
|
396
|
-
|
|
245
|
+
const fiber = getFiber(document.body);
|
|
246
|
+
const latestFiber = fiber ? getLatestFiber(fiber) : null;
|
|
397
247
|
```
|
|
398
248
|
|
|
399
|
-
###
|
|
249
|
+
### `getRenderer`
|
|
400
250
|
|
|
401
|
-
|
|
251
|
+
Returns the React renderer that owns a Fiber, or `null` when the renderer is unavailable.
|
|
402
252
|
|
|
403
253
|
```typescript
|
|
404
|
-
import {
|
|
254
|
+
import { getRenderer } from "bippy";
|
|
405
255
|
|
|
406
|
-
|
|
256
|
+
const renderer = getRenderer(fiber);
|
|
257
|
+
renderer?.overrideProps?.(fiber, ["title"], "new title");
|
|
258
|
+
renderer?.scheduleUpdate?.(fiber);
|
|
407
259
|
```
|
|
408
260
|
|
|
409
|
-
|
|
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
|
-
###
|
|
263
|
+
### React internals
|
|
421
264
|
|
|
422
|
-
|
|
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 {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
290
|
+
Source utilities resolve component locations, source maps, and component stacks. Import them from `bippy/source`.
|
|
449
291
|
|
|
450
|
-
###
|
|
292
|
+
### `getSource`
|
|
451
293
|
|
|
452
|
-
|
|
294
|
+
Returns the source location for a Fiber across DOM, native, terminal, canvas, PDF, and custom renderers.
|
|
453
295
|
|
|
454
296
|
```typescript
|
|
455
|
-
import {
|
|
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
|
-
|
|
461
|
-
|
|
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
|
-
|
|
303
|
+
Production builds may omit source information. Runtimes without `fetch` receive unsymbolicated locations.
|
|
470
304
|
|
|
471
|
-
|
|
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 {
|
|
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
|
-
|
|
310
|
+
const sourceFetch: SourceFetch = async (url, init) => {
|
|
311
|
+
const artifact = sourceArtifacts.get(url);
|
|
312
|
+
if (!artifact) return fetch(url, init);
|
|
494
313
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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.
|