bippy 0.6.1 → 0.7.0-dev.697d535
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 +223 -463
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- 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 +153 -3
- package/dist/index.d.ts +153 -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 -27
- package/src/core.ts +353 -685
- 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/core.d.cts +0 -214
- package/dist/core.d.ts +0 -214
- package/dist/core2.d.cts +0 -3
- package/dist/core2.d.ts +0 -3
- 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,415 @@
|
|
|
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
|
|
9
|
+
bippy hacks into React internals.
|
|
18
10
|
|
|
19
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
return: Fiber | null;
|
|
18
|
+
## How React works internally
|
|
55
19
|
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
memoizedState: any;
|
|
24
|
+
Consider this component tree:
|
|
64
25
|
|
|
65
|
-
|
|
66
|
-
|
|
26
|
+
```tsx
|
|
27
|
+
const Button = () => <button>Save</button>;
|
|
67
28
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
29
|
+
const App = () => (
|
|
30
|
+
<main>
|
|
31
|
+
<Button />
|
|
32
|
+
</main>
|
|
33
|
+
);
|
|
71
34
|
```
|
|
72
35
|
|
|
73
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
63
|
+
The fields connect React’s component tree, pending work, and rendered output:
|
|
108
64
|
|
|
109
|
-
- `
|
|
110
|
-
|
|
111
|
-
- `
|
|
112
|
-
|
|
113
|
-
- `
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
+
## Install bippy
|
|
77
|
+
|
|
78
|
+
Install bippy:
|
|
123
79
|
|
|
124
80
|
```shell
|
|
125
81
|
npm install bippy
|
|
126
82
|
```
|
|
127
83
|
|
|
128
|
-
|
|
84
|
+
Import bippy before React or any React renderer.
|
|
129
85
|
|
|
130
|
-
###
|
|
86
|
+
### Next.js
|
|
131
87
|
|
|
132
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
### vite
|
|
94
|
+
### Vite
|
|
142
95
|
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
117
|
+
`getFiberFromHostInstance` is an alias for `getFiber`.
|
|
155
118
|
|
|
156
|
-
|
|
119
|
+
### `useFiber`
|
|
157
120
|
|
|
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
|
-
> ```
|
|
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
|
-
|
|
123
|
+
```tsx
|
|
124
|
+
import { useFiber } from "bippy";
|
|
168
125
|
|
|
169
|
-
|
|
126
|
+
const Component = () => {
|
|
127
|
+
const fiber = useFiber();
|
|
128
|
+
console.log(fiber?.type);
|
|
129
|
+
return null;
|
|
130
|
+
};
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### `instrument`
|
|
170
134
|
|
|
171
|
-
|
|
135
|
+
Registers lifecycle handlers and returns an unsubscribe function.
|
|
172
136
|
|
|
173
|
-
|
|
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";
|
|
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(
|
|
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
|
-
|
|
157
|
+
Call the returned function to unsubscribe those handlers.
|
|
158
|
+
|
|
159
|
+
### `getRDTHook`
|
|
196
160
|
|
|
197
|
-
|
|
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
|
-
|
|
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";
|
|
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(
|
|
179
|
+
traverseRenderedFibers(root, (fiber, phase) => {
|
|
180
|
+
console.log(rendererID, phase, fiber);
|
|
218
181
|
});
|
|
219
182
|
},
|
|
220
183
|
});
|
|
221
184
|
```
|
|
222
185
|
|
|
223
|
-
|
|
186
|
+
Call it with the same root across commits so bippy can compare the current and previous trees.
|
|
224
187
|
|
|
225
|
-
|
|
188
|
+
### `traverseFiber`
|
|
226
189
|
|
|
227
|
-
|
|
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 {
|
|
246
|
-
|
|
247
|
-
// ...
|
|
193
|
+
import { isHostFiber, traverseFiber } from "bippy";
|
|
248
194
|
|
|
249
|
-
|
|
250
|
-
|
|
195
|
+
const buttonFiber = traverseFiber(fiber, (candidateFiber) => {
|
|
196
|
+
return isHostFiber(candidateFiber) && candidateFiber.type === "button";
|
|
251
197
|
});
|
|
252
198
|
```
|
|
253
199
|
|
|
254
|
-
|
|
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
|
-
|
|
264
|
-
console.log(next, prev);
|
|
265
|
-
});
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
### traverseContexts
|
|
202
|
+
### `didFiberRender`
|
|
269
203
|
|
|
270
|
-
|
|
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 {
|
|
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
|
-
|
|
212
|
+
Use `traverseRenderedFibers` to inspect renders from a specific commit.
|
|
283
213
|
|
|
284
|
-
|
|
214
|
+
### `didFiberCommit`
|
|
285
215
|
|
|
286
|
-
|
|
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 {
|
|
219
|
+
import { didFiberCommit } from "bippy";
|
|
301
220
|
|
|
302
|
-
|
|
303
|
-
console.log("fiber is a host fiber");
|
|
304
|
-
}
|
|
221
|
+
console.log(didFiberCommit(fiber));
|
|
305
222
|
```
|
|
306
223
|
|
|
307
|
-
###
|
|
224
|
+
### `setFiberId`
|
|
308
225
|
|
|
309
|
-
|
|
226
|
+
Assigns a numeric ID to a Fiber.
|
|
310
227
|
|
|
311
228
|
```typescript
|
|
312
|
-
import {
|
|
229
|
+
import { setFiberId } from "bippy";
|
|
313
230
|
|
|
314
|
-
|
|
315
|
-
console.log("fiber is a composite fiber");
|
|
316
|
-
}
|
|
231
|
+
setFiberId(fiber, 123);
|
|
317
232
|
```
|
|
318
233
|
|
|
319
|
-
###
|
|
234
|
+
### `getFiberId`
|
|
320
235
|
|
|
321
|
-
|
|
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 {
|
|
239
|
+
import { getFiberId } from "bippy";
|
|
325
240
|
|
|
326
|
-
|
|
241
|
+
const fiberId = getFiberId(fiber);
|
|
327
242
|
```
|
|
328
243
|
|
|
329
|
-
###
|
|
244
|
+
### `isFiber`
|
|
330
245
|
|
|
331
|
-
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
};
|
|
340
|
-
const MemoizedComponent = memo(() => {
|
|
341
|
-
return <div>hello</div>;
|
|
342
|
-
});
|
|
248
|
+
```typescript
|
|
249
|
+
import { isFiber } from "bippy";
|
|
343
250
|
|
|
344
|
-
console.log(
|
|
251
|
+
console.log(isFiber(value));
|
|
345
252
|
```
|
|
346
253
|
|
|
347
|
-
###
|
|
254
|
+
### `isHostFiber`
|
|
348
255
|
|
|
349
|
-
|
|
256
|
+
Returns whether a Fiber represents a renderer host instance, such as a DOM element or React Native view.
|
|
350
257
|
|
|
351
|
-
```
|
|
352
|
-
import {
|
|
353
|
-
|
|
354
|
-
// ...
|
|
258
|
+
```typescript
|
|
259
|
+
import { isHostFiber } from "bippy";
|
|
355
260
|
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
###
|
|
266
|
+
### `isCompositeFiber`
|
|
370
267
|
|
|
371
|
-
|
|
268
|
+
Returns whether a Fiber represents a function, class, memo, or forward-ref component.
|
|
372
269
|
|
|
373
270
|
```typescript
|
|
374
|
-
|
|
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
|
-
|
|
386
|
-
[fiber, fiber.return, fiber.return.return, ...]
|
|
273
|
+
console.log(isCompositeFiber(fiber));
|
|
387
274
|
```
|
|
388
275
|
|
|
389
|
-
###
|
|
276
|
+
### `hasMemoCache`
|
|
390
277
|
|
|
391
|
-
|
|
278
|
+
Returns whether a Fiber uses a React Compiler memo cache.
|
|
392
279
|
|
|
393
280
|
```typescript
|
|
394
|
-
import {
|
|
281
|
+
import { hasMemoCache } from "bippy";
|
|
395
282
|
|
|
396
|
-
console.log(
|
|
283
|
+
console.log(hasMemoCache(fiber));
|
|
397
284
|
```
|
|
398
285
|
|
|
399
|
-
###
|
|
286
|
+
### `getDisplayName`
|
|
400
287
|
|
|
401
|
-
|
|
288
|
+
Returns the display name of a Fiber type.
|
|
402
289
|
|
|
403
290
|
```typescript
|
|
404
|
-
import {
|
|
291
|
+
import { getDisplayName } from "bippy";
|
|
405
292
|
|
|
406
|
-
console.log(
|
|
293
|
+
console.log(getDisplayName(fiber.type));
|
|
407
294
|
```
|
|
408
295
|
|
|
409
|
-
###
|
|
296
|
+
### `getType`
|
|
410
297
|
|
|
411
|
-
|
|
298
|
+
Unwraps memo and forward-ref wrappers and returns the underlying component definition.
|
|
412
299
|
|
|
413
300
|
```typescript
|
|
414
|
-
import {
|
|
301
|
+
import { getType } from "bippy";
|
|
415
302
|
|
|
416
|
-
|
|
417
|
-
console.log(fiber);
|
|
303
|
+
console.log(getType(fiber.type));
|
|
418
304
|
```
|
|
419
305
|
|
|
420
|
-
### getLatestFiber
|
|
306
|
+
### `getLatestFiber`
|
|
421
307
|
|
|
422
|
-
|
|
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
|
|
428
|
-
|
|
313
|
+
const fiber = getFiber(document.body);
|
|
314
|
+
const latestFiber = fiber ? getLatestFiber(fiber) : null;
|
|
429
315
|
```
|
|
430
316
|
|
|
431
|
-
###
|
|
317
|
+
### `getRenderer`
|
|
432
318
|
|
|
433
|
-
|
|
319
|
+
Returns the React renderer that owns a Fiber, or `null` when the renderer is unavailable.
|
|
434
320
|
|
|
435
321
|
```typescript
|
|
436
|
-
import {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
overrideProps(fiber,
|
|
440
|
-
|
|
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
|
-
|
|
329
|
+
Renderer capabilities are optional and vary by renderer version.
|
|
449
330
|
|
|
450
|
-
###
|
|
331
|
+
### React internals
|
|
451
332
|
|
|
452
|
-
|
|
333
|
+
The main `bippy` entry point exports the React internals used by its APIs.
|
|
453
334
|
|
|
454
335
|
```typescript
|
|
455
|
-
import {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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 {
|
|
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
|
-
|
|
488
|
-
|
|
370
|
+
const source = await getSource(fiber);
|
|
371
|
+
console.log(source);
|
|
489
372
|
```
|
|
490
373
|
|
|
491
|
-
|
|
374
|
+
Production builds may omit source information. Runtimes without `fetch` receive unsymbolicated locations.
|
|
492
375
|
|
|
493
|
-
|
|
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
|
-
|
|
501
|
-
const
|
|
381
|
+
const sourceFetch: SourceFetch = async (url, init) => {
|
|
382
|
+
const artifact = sourceArtifacts.get(url);
|
|
383
|
+
if (!artifact) return fetch(url, init);
|
|
502
384
|
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
###
|
|
403
|
+
### `getParentStack`
|
|
549
404
|
|
|
550
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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.
|