bippy 0.0.25 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,19 +1,54 @@
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. it is not recommended to depend 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
+
1
6
  # <img src="https://github.com/aidenybai/bippy/blob/main/.github/assets/bippy.png?raw=true" width="60" align="center" /> bippy
2
7
 
3
- a hacky way to get fibers from react. used internally for [`react-scan`](https://github.com/aidenybai/react-scan).
8
+ [![size](https://img.shields.io/bundlephobia/minzip/bippy?label=gzip&style=flat&colorA=000000&colorB=000000)](https://bundlephobia.com/package/bippy)
9
+ [![version](https://img.shields.io/npm/v/bippy?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/bippy)
10
+ [![downloads](https://img.shields.io/npm/dt/bippy.svg?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/bippy)
4
11
 
5
- bippy works by monkey-patching `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with [custom handlers](https://github.com/facebook/react/blob/6a4b46cd70d2672bc4be59dcb5b8dede22ed0cef/packages/react-refresh/src/ReactFreshRuntime.js#L427). this gives us access to react internals without needing to use react devtools.
12
+ bippy is a toolkit to **hack into react internals**
6
13
 
7
- > [!WARNING]
8
- > this project uses react internals, which can change at any time. **this is not recommended for usage and may break production apps** - unless you acknowledge this risk and know exactly you're doing.
14
+ 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.
15
+
16
+ - works outside of react – no react code modification needed
17
+ - utility functions that work across modern react (v17-19)
18
+ - no prior react source code knowledge required
19
+
20
+ ```jsx
21
+ import { instrument, traverseFiber } from 'bippy';
22
+
23
+ instrument({
24
+ onCommitFiberRoot(_, root) {
25
+ traverseFiber(root.current, (fiber) => {
26
+ // will print every fiber in the current React tree
27
+ console.log('fiber:', fiber);
28
+ });
29
+ },
30
+ });
31
+ ```
9
32
 
10
- ## tutorial: create a mini react-scan
33
+ <table>
34
+ <tbody>
35
+ <tr>
36
+ <td>
37
+ <a href="https://bippy.dev"><b>open live demo ↗</b></a>
38
+ </td>
39
+ </tr>
40
+ </tbody>
41
+ </table>
11
42
 
12
- [`react-scan`](https://github.com/aidenybai/react-scan) is a tool that highlights renders in your react app. under the hood, it uses bippy to detect rendered fibers.
43
+ ## how it works & motivation
13
44
 
14
- fibers are how "work" is represented in react. each fiber either represents a composite (function/class component) or a host (dom element). [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/).
45
+ bippy allows you to **access** and **use** react fibers **outside** of react components.
15
46
 
16
- a simplified version of a fiber looks roughly like this:
47
+ 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).
48
+
49
+ > 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/).
50
+
51
+ 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:
17
52
 
18
53
  ```typescript
19
54
  interface Fiber {
@@ -23,9 +58,15 @@ interface Fiber {
23
58
  child: Fiber | null;
24
59
  sibling: Fiber | null;
25
60
 
61
+ // stateNode is the host fiber (e.g. DOM element)
62
+ stateNode: Node | null;
63
+
26
64
  // parent fiber
27
65
  return: Fiber | null;
28
66
 
67
+ // the previous or current version of the fiber
68
+ alternate: Fiber | null;
69
+
29
70
  // saved props input
30
71
  memoizedProps: any;
31
72
 
@@ -34,133 +75,342 @@ interface Fiber {
34
75
 
35
76
  // contexts (useContext)
36
77
  dependencies: Dependencies | null;
78
+
79
+ // effects (useEffect, useLayoutEffect, etc.)
80
+ updateQueue: any;
37
81
  }
38
82
  ```
39
83
 
84
+ here, the `child`, `sibling`, and `return` properties are pointers to other fibers in the tree.
85
+
86
+ additionally, `memoizedProps`, `memoizedState`, and `dependencies` are the fiber's props, state, and contexts.
87
+
88
+ while all of the information is there, it's not super easy to work with, and changes frequently across different versions of react. bippy simplifies this by providing utility functions like:
89
+
90
+ - `createFiberVisitor` to detect renders and `traverseFiber` to traverse the overall fiber tree
91
+ - _(instead of `child`, `sibling`, and `return` pointers)_
92
+ - `traverseProps`, `traverseState`, and `traverseContexts` to traverse the fiber's props, state, and contexts
93
+ - _(instead of `memoizedProps`, `memoizedState`, and `dependencies`)_
94
+
40
95
  however, fibers aren't directly accessible by the user. so, we have to hack our way around to accessing it.
41
96
 
42
- 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 is intended for react devtools, but we can use it to our advantage.
97
+ 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.
43
98
 
44
99
  here's what it roughly looks like:
45
100
 
46
101
  ```typescript
47
102
  interface __REACT_DEVTOOLS_GLOBAL_HOOK__ {
48
103
  // list of renderers (react-dom, react-native, etc.)
49
- renderers: Map<RendererID, ReactRenderer>;
104
+ renderers: Map<RendererID, reactRenderer>;
50
105
 
51
- // called when react has rendered everythign and ready to apply changes to the host tree (e.g. DOM mutations)
106
+ // called when react has rendered everything for an update and the fiber tree is fully built and ready to
107
+ // apply changes to the host tree (e.g. DOM mutations)
52
108
  onCommitFiberRoot: (
53
109
  rendererID: RendererID,
54
- fiber: Record<string, unknown>,
55
- commitPriority?: number,
56
- didError?: boolean,
110
+ root: FiberRoot,
111
+ commitPriority?: number
57
112
  ) => void;
113
+
114
+ // called when effects run
115
+ onPostCommitFiberRoot: (rendererID: RendererID, root: FiberRoot) => void;
116
+
117
+ // called when a specific fiber unmounts
118
+ onCommitFiberUnmount: (rendererID: RendererID, fiber: Fiber) => void;
58
119
  }
59
120
  ```
60
121
 
61
- we can use bippy's utils and the `onCommitFiberRoot` handler to detect renders!
122
+ bippy works by monkey-patching `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with our own custom handlers. bippy simplifies this by providing utility functions like:
123
+
124
+ - `instrument` to safely patch `window.__REACT_DEVTOOLS_GLOBAL_HOOK__`
125
+ - _(instead of directly mutating `onCommitFiberRoot`, ...)_
126
+ - `secure` to wrap your handlers in a try/catch and determine if handlers are safe to run
127
+ - _(instead of rawdogging `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` handlers, which may crash your app)_
128
+ - `createFiberVisitor` to traverse the fiber tree and determine which fibers have actually rendered
129
+ - _(instead of `child`, `sibling`, and `return` pointers)_
130
+ - `traverseFiber` to traverse the fiber tree, regardless of whether it has rendered
131
+ - _(instead of `child`, `sibling`, and `return` pointers)_
132
+ - `setFiberId` / `getFiberId` to set and get a fiber's id
133
+ - _(instead of anonymous fibers with no identity)_
62
134
 
63
- ### 0. setup
135
+ ## how to use
64
136
 
65
- first, [create a new react project via stackblitz](https://stackblitz.com/fork/github/vitejs/vite/tree/main/packages/create-vite/template-react?file=src/App.jsx&terminal=dev)
137
+ you can either install via a npm (recommended) or a script tag.
66
138
 
67
- then, install bippy:
139
+ this package should be imported before a React app runs. this will add a special object to the global which is used by React for providing its internals to the tool for analysis (React Devtools does the same). as soon as React library is loaded and attached to the tool, bippy starts collecting data about what is going on in React's internals.
68
140
 
69
- ```bash
141
+ ```shell
70
142
  npm install bippy
71
143
  ```
72
144
 
73
- finally, re-run the dev server:
145
+ or, use via script tag. must be added before any other scripts run:
74
146
 
75
- ```bash
76
- npm run dev
147
+ ```html
148
+ <script src="https://unpkg.com/bippy"></script>
77
149
  ```
78
150
 
79
- ### 1. use `onCommitFiberRoot` to get fibers
151
+ > this will cause bippy to be accessible under a `window.Bippy` global.
80
152
 
81
- let's use `instrument` to stub the `__REACT_DEVTOOLS_GLOBAL_HOOK__` object, and setup a custom handler for `onCommitFiberRoot`.
153
+ next, you can use the api to get data about the fiber tree. below is a (useful) subset of the api. for the full api, read the [source code](https://github.com/aidenybai/bippy/blob/main/src/core.ts).
82
154
 
83
- ```jsx
84
- import { instrument } from 'bippy'; // must be imported BEFORE react
155
+ ### instrument
85
156
 
86
- // rest of your code ...
157
+ patches `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with your handlers. must be imported before react, and must be initialized to properly run any other methods.
87
158
 
88
- instrument({
89
- onCommitFiberRoot(rendererID, root) {
90
- const fiberRoot = root.current;
91
- console.log('fiberRoot', fiberRoot);
159
+ > use with the `secure` function to prevent uncaught errors from crashing your app.
160
+
161
+ ```typescript
162
+ import { instrument, secure } from 'bippy'; // must be imported BEFORE react
163
+ import * as React from 'react';
164
+
165
+ instrument(
166
+ secure({
167
+ onCommitFiberRoot(rendererID, root) {
168
+ console.log('root ready to commit', root);
169
+ },
170
+ onPostCommitFiberRoot(rendererID, root) {
171
+ console.log('root with effects committed', root);
172
+ },
173
+ onCommitFiberUnmount(rendererID, fiber) {
174
+ console.log('fiber unmounted', fiber);
175
+ },
176
+ })
177
+ );
178
+ ```
179
+
180
+ ### createFiberVisitor
181
+
182
+ not every fiber in the fiber tree renders. `createFiberVisitor` allows you to traverse the fiber tree and determine which fibers have actually rendered.
183
+
184
+ ```typescript
185
+ import { instrument, secure, createFiberVisitor } from 'bippy'; // must be imported BEFORE react
186
+ import * as React from 'react';
187
+
188
+ const visit = createFiberVisitor({
189
+ onRender(fiber) {
190
+ console.log('fiber rendered', fiber);
92
191
  },
93
192
  });
193
+
194
+ instrument(
195
+ secure({
196
+ onCommitFiberRoot(rendererID, root) {
197
+ visit(rendererID, root);
198
+ },
199
+ })
200
+ );
94
201
  ```
95
202
 
96
- running this should log `fiberRoot` to the console. i recommend you playing with this code to get a feel for how fibers work.
203
+ ### traverseFiber
97
204
 
98
- ### 2. create a fiber visitor
205
+ calls a callback on every fiber in the fiber tree.
99
206
 
100
- now, let's create a fiber visitor with `createFiberVisitor` to "visit" fibers that render. not every fiber actually renders, so we need to filter for the ones that do.
207
+ ```typescript
208
+ import { instrument, secure, traverseFiber } from 'bippy'; // must be imported BEFORE react
209
+ import * as React from 'react';
210
+
211
+ instrument(
212
+ secure({
213
+ onCommitFiberRoot(rendererID, root) {
214
+ traverseFiber(root.current, (fiber) => {
215
+ console.log(fiber);
216
+ });
217
+ },
218
+ })
219
+ );
220
+ ```
101
221
 
102
- ```jsx
103
- import { instrument, createFiberVisitor } from 'bippy'; // must be imported BEFORE react
222
+ ### traverseProps
104
223
 
105
- // rest of your code ...
224
+ traverses the props of a fiber.
106
225
 
107
- const visit = createFiberVisitor({
108
- onRender(fiber) {
109
- console.log('fiber render', fiber);
110
- },
226
+ ```typescript
227
+ import { traverseProps } from 'bippy';
228
+
229
+ // ...
230
+
231
+ traverseProps(fiber, (propName, next, prev) => {
232
+ console.log(propName, next, prev);
111
233
  });
234
+ ```
112
235
 
113
- instrument({
114
- onCommitFiberRoot(rendererID, root) {
115
- visit(rendererID, root);
116
- },
236
+ ### traverseState
237
+
238
+ traverses the state (useState, useReducer, etc.) and effects that set state of a fiber.
239
+
240
+ ```typescript
241
+ import { traverseState } from 'bippy';
242
+
243
+ // ...
244
+
245
+ traverseState(fiber, (next, prev) => {
246
+ console.log(next, prev);
117
247
  });
118
248
  ```
119
249
 
120
- ### 3. determine DOM nodes to highlight
250
+ ### traverseEffects
121
251
 
122
- next, we need to identify which DOM nodes we are going to highlight. we can do this by checking if the fiber is a host fiber, or if it's not, find the nearest host fiber.
252
+ traverses the effects (useEffect, useLayoutEffect, etc.) of a fiber.
123
253
 
124
- ```jsx
125
- import {
126
- instrument,
127
- isHostFiber,
128
- getNearestHostFiber,
129
- createFiberVisitor,
130
- } from 'bippy'; // must be imported BEFORE react
254
+ ```typescript
255
+ import { traverseEffects } from 'bippy';
131
256
 
132
- // rest of your code ...
257
+ // ...
133
258
 
134
- const highlightFiber = (fiber) => {
135
- if (!(fiber instanceof HTMLElement)) return;
259
+ traverseEffects(fiber, (effect) => {
260
+ console.log(effect);
261
+ });
262
+ ```
136
263
 
137
- console.log('highlight dom node', fiber.stateNode);
138
- };
264
+ ### traverseContexts
139
265
 
140
- const visit = createFiberVisitor({
141
- onRender(fiber) {
142
- if (isHostFiber(fiber)) {
143
- highlightFiber(fiber);
144
- } else {
145
- // can be a component
146
- const hostFiber = getNearestHostFiber(fiber);
147
- highlightFiber(hostFiber);
148
- }
149
- },
266
+ traverses the contexts (useContext) of a fiber.
267
+
268
+ ```typescript
269
+ import { traverseContexts } from 'bippy';
270
+
271
+ // ...
272
+
273
+ traverseContexts(fiber, (next, prev) => {
274
+ console.log(next, prev);
150
275
  });
276
+ ```
151
277
 
152
- instrument({
153
- onCommitFiberRoot(rendererID, root) {
154
- visit(rendererID, root);
155
- },
278
+ ### setFiberId / getFiberId
279
+
280
+ set and get a persistent identity for a fiber. by default, fibers are anonymous and have no identity.
281
+
282
+ ```typescript
283
+ import { setFiberId, getFiberId } from 'bippy';
284
+
285
+ // ...
286
+
287
+ setFiberId(fiber);
288
+ console.log('unique id for fiber:', getFiberId(fiber));
289
+ ```
290
+
291
+ ### isHostFiber
292
+
293
+ returns `true` if the fiber is a host fiber (e.g., a DOM node in react-dom).
294
+
295
+ ```typescript
296
+ import { isHostFiber } from 'bippy';
297
+
298
+ if (isHostFiber(fiber)) {
299
+ console.log('fiber is a host fiber');
300
+ }
301
+ ```
302
+
303
+ ### isCompositeFiber
304
+
305
+ 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).
306
+
307
+ ```typescript
308
+ import { isCompositeFiber } from 'bippy';
309
+
310
+ if (isCompositeFiber(fiber)) {
311
+ console.log('fiber is a composite fiber');
312
+ }
313
+ ```
314
+
315
+ ### getDisplayName
316
+
317
+ returns the display name of the fiber's component, falling back to the component's function or class name if available.
318
+
319
+ ```typescript
320
+ import { getDisplayName } from 'bippy';
321
+
322
+ console.log(getDisplayName(fiber));
323
+ ```
324
+
325
+ ### getType
326
+
327
+ returns the underlying type (the component definition) for a given fiber. for example, this could be a function component or class component.
328
+
329
+ ```jsx
330
+ import { getType } from 'bippy';
331
+ import { memo } from 'react';
332
+
333
+ const RealComponent = () => {
334
+ return <div>hello</div>;
335
+ };
336
+ const MemoizedComponent = memo(() => {
337
+ return <div>hello</div>;
156
338
  });
339
+
340
+ console.log(getType(fiberForMemoizedComponent) === RealComponent);
157
341
  ```
158
342
 
159
- ### 4. highlight DOM nodes
343
+ ### getNearestHostFiber / getNearestHostFibers
160
344
 
161
- now, let's implement the `highlightFiber` function to highlight the DOM node. the simplest way is to just overlay a div (with a red border) on top of the DOM node.
345
+ getNearestHostFiber returns the closest host fiber above or below a given fiber. getNearestHostFibers(fiber) returns all host fibers associated with the provided fiber and its subtree.
162
346
 
163
347
  ```jsx
348
+ import { getNearestHostFiber, getNearestHostFibers } from 'bippy';
349
+
350
+ // ...
351
+
352
+ function Component() {
353
+ return (
354
+ <>
355
+ <div>hello</div>
356
+ <div>world</div>
357
+ </>
358
+ );
359
+ }
360
+
361
+ console.log(getNearestHostFiber(fiberForComponent)); // <div>hello</div>
362
+ console.log(getNearestHostFibers(fiberForComponent)); // [<div>hello</div>, <div>world</div>]
363
+ ```
364
+
365
+ ### getTimings
366
+
367
+ returns the self and total render times for the fiber.
368
+
369
+ ```typescript
370
+ // timings don't exist in react production builds
371
+ if (fiber.actualDuration !== undefined) {
372
+ const { selfTime, totalTime } = getTimings(fiber);
373
+ console.log(selfTime, totalTime);
374
+ }
375
+ ```
376
+
377
+ ### getFiberStack
378
+
379
+ returns an array representing the stack of fibers from the current fiber up to the root.
380
+
381
+ ```typescript
382
+ [fiber, fiber.return, fiber.return.return, ...]
383
+ ```
384
+
385
+ ### getMutatedHostFibers
386
+
387
+ returns an array of all host fibers that have committed and rendered in the provided fiber's subtree.
388
+
389
+ ```typescript
390
+ import { getMutatedHostFibers } from 'bippy';
391
+
392
+ console.log(getMutatedHostFibers(fiber));
393
+ ```
394
+
395
+ ### isValidFiber
396
+
397
+ returns `true` if the given object is a valid React Fiber (i.e., has a tag, stateNode, return, child, sibling, etc.).
398
+
399
+ ```typescript
400
+ import { isValidFiber } from 'bippy';
401
+
402
+ console.log(isValidFiber(fiber));
403
+ ```
404
+
405
+ ## examples
406
+
407
+ the best way to understand bippy is to [read the source code](https://github.com/aidenybai/bippy/blob/main/src/core.ts). here are some examples of how you can use it:
408
+
409
+ ### a mini react-scan
410
+
411
+ here's a mini toy version of [`react-scan`](https://github.com/aidenybai/react-scan) that highlights renders in your app.
412
+
413
+ ```javascript
164
414
  import {
165
415
  instrument,
166
416
  isHostFiber,
@@ -168,11 +418,9 @@ import {
168
418
  createFiberVisitor,
169
419
  } from 'bippy'; // must be imported BEFORE react
170
420
 
171
- // rest of your code ...
172
-
173
421
  const highlightFiber = (fiber) => {
174
422
  if (!(fiber.stateNode instanceof HTMLElement)) return;
175
-
423
+ // fiber.stateNode is a DOM element
176
424
  const rect = fiber.stateNode.getBoundingClientRect();
177
425
  const highlight = document.createElement('div');
178
426
  highlight.style.border = '1px solid red';
@@ -188,33 +436,193 @@ const highlightFiber = (fiber) => {
188
436
  }, 100);
189
437
  };
190
438
 
439
+ /**
440
+ * `createFiberVisitor` traverses the fiber tree and determines which
441
+ * fibers have actually rendered.
442
+ *
443
+ * A fiber tree contains many fibers that may have not rendered. this
444
+ * can be because it bailed out (e.g. `useMemo`) or because it wasn't
445
+ * actually rendered (if <Child> re-rendered, then <Parent> didn't
446
+ * actually render, but exists in the fiber tree).
447
+ */
191
448
  const visit = createFiberVisitor({
192
449
  onRender(fiber) {
193
- if (isHostFiber(fiber)) {
194
- highlightFiber(fiber);
195
- } else {
196
- // can be a component
197
- const hostFiber = getNearestHostFiber(fiber);
198
- highlightFiber(hostFiber);
199
- }
450
+ /**
451
+ * `getNearestHostFiber` is a utility function that finds the
452
+ * nearest host fiber to a given fiber.
453
+ *
454
+ * a host fiber for `react-dom` is a fiber that has a DOM element
455
+ * as its `stateNode`.
456
+ */
457
+ const hostFiber = getNearestHostFiber(fiber);
458
+ highlightFiber(hostFiber);
200
459
  },
201
460
  });
202
461
 
203
- instrument({
204
- onCommitFiberRoot(rendererID, root) {
205
- visit(rendererID, root);
462
+ /**
463
+ * `instrument` is a function that installs the react DevTools global
464
+ * hook and allows you to set up custom handlers for react fiber events.
465
+ */
466
+ instrument(
467
+ /**
468
+ * `secure` is a function that wraps your handlers in a try/catch
469
+ * and prevents it from crashing the app. it also prevents it from
470
+ * running on unsupported react versions and during production.
471
+ *
472
+ * this is not required but highly recommended to provide "safeguards"
473
+ * in case something breaks.
474
+ */
475
+ secure({
476
+ /**
477
+ * `onCommitFiberRoot` is a handler that is called when react is
478
+ * ready to commit a fiber root. this means that react is has
479
+ * rendered your entire app and is ready to apply changes to
480
+ * the host tree (e.g. via DOM mutations).
481
+ */
482
+ onCommitFiberRoot(rendererID, root) {
483
+ visit(rendererID, root);
484
+ },
485
+ })
486
+ );
487
+ ```
488
+
489
+ ### a mini why-did-you-render
490
+
491
+ here's a mini toy version of [`why-did-you-render`](https://github.com/welldone-software/why-did-you-render) that logs why components re-render.
492
+
493
+ ```typescript
494
+ import {
495
+ instrument,
496
+ isHostFiber,
497
+ createFiberVisitor,
498
+ isCompositeFiber,
499
+ getDisplayName,
500
+ traverseProps,
501
+ traverseContexts,
502
+ traverseState,
503
+ } from 'bippy'; // must be imported BEFORE react
504
+
505
+ const visit = createFiberVisitor({
506
+ onRender(fiber) {
507
+ /**
508
+ * `isCompositeFiber` is a utility function that checks if a fiber is a composite fiber.
509
+ * a composite fiber is a fiber that represents a function or class component.
510
+ */
511
+ if (!isCompositeFiber(fiber)) return;
512
+
513
+ /**
514
+ * `getDisplayName` is a utility function that gets the display name of a fiber.
515
+ */
516
+ const displayName = getDisplayName(fiber);
517
+ if (!displayName) return;
518
+
519
+ const changes = [];
520
+
521
+ /**
522
+ * `traverseProps` is a utility function that traverses the props of a fiber.
523
+ */
524
+ traverseProps(fiber, (propName, next, prev) => {
525
+ if (next !== prev) {
526
+ changes.push({
527
+ name: `prop ${propName}`,
528
+ prev,
529
+ next,
530
+ });
531
+ }
532
+ });
533
+
534
+ let contextId = 0;
535
+ /**
536
+ * `traverseContexts` is a utility function that traverses the contexts of a fiber.
537
+ * Contexts don't have a "name" like props, so we use an id to identify them.
538
+ */
539
+ traverseContexts(fiber, (next, prev) => {
540
+ if (next !== prev) {
541
+ changes.push({
542
+ name: `context ${contextId}`,
543
+ prev,
544
+ next,
545
+ contextId,
546
+ });
547
+ }
548
+ contextId++;
549
+ });
550
+
551
+ let stateId = 0;
552
+ /**
553
+ * `traverseState` is a utility function that traverses the state of a fiber.
554
+ *
555
+ * State don't have a "name" like props, so we use an id to identify them.
556
+ */
557
+ traverseState(fiber, (value, prevValue) => {
558
+ if (next !== prev) {
559
+ changes.push({
560
+ name: `state ${stateId}`,
561
+ prev,
562
+ next,
563
+ });
564
+ }
565
+ stateId++;
566
+ });
567
+
568
+ console.group(
569
+ `%c${displayName}`,
570
+ 'background: hsla(0,0%,70%,.3); border-radius:3px; padding: 0 2px;'
571
+ );
572
+ for (const { name, prev, next } of changes) {
573
+ console.log(`${name}:`, prev, '!==', next);
574
+ }
575
+ console.groupEnd();
206
576
  },
207
577
  });
208
- ```
209
578
 
210
- ### 5. profit
579
+ instrument(
580
+ secure({
581
+ onCommitFiberRoot(rendererID, root) {
582
+ visit(rendererID, root);
583
+ },
584
+ })
585
+ );
586
+ ```
211
587
 
212
- try a completed version [here](https://bippy.million.dev)
588
+ ## glossary
589
+
590
+ - fiber: a "unit of execution" in react, representing a component or dom element
591
+ - commit: the process of applying changes to the host tree (e.g. DOM mutations)
592
+ - render: the process of building the fiber tree by executing component function/classes
593
+ - host tree: the tree of UI elements that react mutates (e.g. DOM elements)
594
+ - reconciler (or "renderer"): custom bindings for react, e.g. react-dom, react-native, react-three-fiber, etc to mutate the host tree
595
+ - `rendererID`: the id of the reconciler, starting at 1 (can be from multiple reconciler instances)
596
+ - `root`: a special `FiberRoot` type that contains the container fiber (the one you pass to `ReactDOM.createRoot`) in the `current` property
597
+ - `onCommitFiberRoot`: called when react is ready to commit a fiber root
598
+ - `onPostCommitFiberRoot`: called when react has committed a fiber root and effects have run
599
+ - `onCommitFiberUnmount`: called when a fiber unmounts
600
+
601
+ ## development
602
+
603
+ we use a pnpm monorepo, get started by running:
604
+
605
+ ```shell
606
+ pnpm install
607
+ # create dev builds
608
+ pnpm run dev
609
+ # run unit tests
610
+ pnpm run test
611
+ ```
213
612
 
214
- you can learn more about bippy by [reading the source code](https://github.com/aidenybai/bippy/blob/main/src/index.ts).
613
+ you can ad-hoc test by running `pnpm run dev` in the `/kitchen-sink` directory.
215
614
 
216
- looking for a more robust version of our mini react-scan? try out [react-scan](https://github.com/aidenybai/react-scan).
615
+ ```shell
616
+ cd kitchen-sink
617
+ pnpm run dev
618
+ ```
217
619
 
218
620
  ## misc
219
621
 
220
- 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
622
+ we use this project internally in [react-scan](https://github.com/aidenybai/react-scan), which is deployed with proper safeguards to ensure it's only used in development or error-guarded in production.
623
+
624
+ while i maintain this specifically for react-scan, those seeking more robust solutions 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.
625
+
626
+ 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.
627
+
628
+ 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.