bippy 0.1.0 → 0.2.0

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
@@ -9,26 +9,44 @@
9
9
  [![version](https://img.shields.io/npm/v/bippy?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/bippy)
10
10
  [![downloads](https://img.shields.io/npm/dt/bippy.svg?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/bippy)
11
11
 
12
- a hacky way to get fibers from react. <small>used internally by [`react-scan`](https://github.com/aidenybai/react-scan)</small>
12
+ bippy is a toolkit to **hack into react internals**
13
13
 
14
- bippy _attempts\*_ to solve two problems:
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
15
 
16
- 1. it's not possible to write instrumentation for React without the end user changing code
17
- 2. doing anything useful with fibers requires you to know react source code very well
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
18
19
 
19
- bippy allows you to access fiber information from outside of react and provides friendly low-level utils for interacting with fibers.
20
+ ```jsx
21
+ import { onCommitFiberRoot, traverseFiber } from 'bippy';
20
22
 
21
- <sub><sup>\*disclaimer: "attempt" used loosely, i highly recommend not relying on this in production</sub></sup>
23
+ onCommitFiberRoot((root) => {
24
+ traverseFiber(root.current, (fiber) => {
25
+ // prints every fiber in the current React tree
26
+ console.log('fiber:', fiber);
27
+ });
28
+ });
29
+ ```
22
30
 
23
- ## how it works
31
+ <table>
32
+ <tbody>
33
+ <tr>
34
+ <td>
35
+ <a href="https://bippy.dev"><b>open live demo ↗</b></a>
36
+ </td>
37
+ </tr>
38
+ </tbody>
39
+ </table>
24
40
 
25
- bippy allows you to **access** and **use** fibers from outside of react.
41
+ ## how it works & motivation
42
+
43
+ bippy allows you to **access** and **use** react fibers **outside** of react components.
26
44
 
27
45
  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).
28
46
 
29
47
  > 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/).
30
48
 
31
- 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:
49
+ 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:
32
50
 
33
51
  ```typescript
34
52
  interface Fiber {
@@ -44,6 +62,9 @@ interface Fiber {
44
62
  // parent fiber
45
63
  return: Fiber | null;
46
64
 
65
+ // the previous or current version of the fiber
66
+ alternate: Fiber | null;
67
+
47
68
  // saved props input
48
69
  memoizedProps: any;
49
70
 
@@ -64,23 +85,23 @@ additionally, `memoizedProps`, `memoizedState`, and `dependencies` are the fiber
64
85
 
65
86
  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:
66
87
 
67
- - `createFiberVisitor` to detect renders and `traverseFiber` to traverse the overall fiber tree
88
+ - `traverseRenderedFibers` to detect renders and `traverseFiber` to traverse the overall fiber tree
68
89
  - _(instead of `child`, `sibling`, and `return` pointers)_
69
90
  - `traverseProps`, `traverseState`, and `traverseContexts` to traverse the fiber's props, state, and contexts
70
91
  - _(instead of `memoizedProps`, `memoizedState`, and `dependencies`)_
71
92
 
72
93
  however, fibers aren't directly accessible by the user. so, we have to hack our way around to accessing it.
73
94
 
74
- 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.
95
+ 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.
75
96
 
76
97
  here's what it roughly looks like:
77
98
 
78
99
  ```typescript
79
100
  interface __REACT_DEVTOOLS_GLOBAL_HOOK__ {
80
101
  // list of renderers (react-dom, react-native, etc.)
81
- renderers: Map<RendererID, ReactRenderer>;
102
+ renderers: Map<RendererID, reactRenderer>;
82
103
 
83
- // called when react has rendered everything for an update and is ready to
104
+ // called when react has rendered everything for an update and the fiber tree is fully built and ready to
84
105
  // apply changes to the host tree (e.g. DOM mutations)
85
106
  onCommitFiberRoot: (
86
107
  rendererID: RendererID,
@@ -92,7 +113,7 @@ interface __REACT_DEVTOOLS_GLOBAL_HOOK__ {
92
113
  onPostCommitFiberRoot: (rendererID: RendererID, root: FiberRoot) => void;
93
114
 
94
115
  // called when a specific fiber unmounts
95
- onCommitFiberUnmount: (rendererID: RendererID, Fiber: Fiber) => void;
116
+ onCommitFiberUnmount: (rendererID: RendererID, fiber: Fiber) => void;
96
117
  }
97
118
  ```
98
119
 
@@ -102,6 +123,305 @@ bippy works by monkey-patching `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with our
102
123
  - _(instead of directly mutating `onCommitFiberRoot`, ...)_
103
124
  - `secure` to wrap your handlers in a try/catch and determine if handlers are safe to run
104
125
  - _(instead of rawdogging `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` handlers, which may crash your app)_
126
+ - `traverseRenderedFibers` to traverse the fiber tree and determine which fibers have actually rendered
127
+ - _(instead of `child`, `sibling`, and `return` pointers)_
128
+ - `traverseFiber` to traverse the fiber tree, regardless of whether it has rendered
129
+ - _(instead of `child`, `sibling`, and `return` pointers)_
130
+ - `setFiberId` / `getFiberId` to set and get a fiber's id
131
+ - _(instead of anonymous fibers with no identity)_
132
+
133
+ ## how to use
134
+
135
+ you can either install via a npm (recommended) or a script tag.
136
+
137
+ 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.
138
+
139
+ ```shell
140
+ npm install bippy
141
+ ```
142
+
143
+ or, use via script tag:
144
+
145
+ ```html
146
+ <script src="https://unpkg.com/bippy"></script>
147
+ ```
148
+
149
+ > this will cause bippy to be accessible under a `window.Bippy` global.
150
+
151
+ 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).
152
+
153
+
154
+ ### onCommitFiberRoot
155
+
156
+ a utility function that wraps the `instrument` function and sets the `onCommitFiberRoot` hook.
157
+
158
+ ```typescript
159
+ import { onCommitFiberRoot } from 'bippy';
160
+
161
+ onCommitFiberRoot((root) => {
162
+ console.log('root ready to commit', root);
163
+ });
164
+ ```
165
+
166
+ ### instrument
167
+
168
+ > the underlying implementation for the `onCommitFiberRoot()` function. this is optional, unless you want to plug into more less common, advanced functionality.
169
+
170
+ patches `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with your handlers. must be imported before react, and must be initialized to properly run any other methods.
171
+
172
+ > use with the `secure` function to prevent uncaught errors from crashing your app.
173
+
174
+ ```typescript
175
+ import { instrument, secure } from 'bippy'; // must be imported BEFORE react
176
+ import * as React from 'react';
177
+
178
+ instrument(
179
+ secure({
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
+ onCommitFiberUnmount(rendererID, fiber) {
187
+ console.log('fiber unmounted', fiber);
188
+ },
189
+ })
190
+ );
191
+ ```
192
+
193
+ ### getRDTHook
194
+
195
+ returns the `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` object. great for advanced use cases, such as accessing or modifying the `renderers` property.
196
+
197
+ ```typescript
198
+ import { getRDTHook } from 'bippy';
199
+
200
+ const hook = getRDTHook();
201
+ console.log(hook);
202
+ ```
203
+
204
+ ### traverseRenderedFibers
205
+
206
+ not every fiber in the fiber tree renders. `traverseRenderedFibers` allows you to traverse the fiber tree and determine which fibers have actually rendered.
207
+
208
+ ```typescript
209
+ import { instrument, secure, traverseRenderedFibers } from 'bippy'; // must be imported BEFORE react
210
+ import * as React from 'react';
211
+
212
+ instrument(
213
+ secure({
214
+ onCommitFiberRoot(rendererID, root) {
215
+ traverseRenderedFibers(root, (fiber) => {
216
+ console.log('fiber rendered', fiber);
217
+ });
218
+ },
219
+ })
220
+ );
221
+ ```
222
+
223
+ ### traverseFiber
224
+
225
+ calls a callback on every fiber in the fiber tree.
226
+
227
+ ```typescript
228
+ import { instrument, secure, traverseFiber } from 'bippy'; // must be imported BEFORE react
229
+ import * as React from 'react';
230
+
231
+ instrument(
232
+ secure({
233
+ onCommitFiberRoot(rendererID, root) {
234
+ traverseFiber(root.current, (fiber) => {
235
+ console.log(fiber);
236
+ });
237
+ },
238
+ })
239
+ );
240
+ ```
241
+
242
+ ### traverseProps
243
+
244
+ traverses the props of a fiber.
245
+
246
+ ```typescript
247
+ import { traverseProps } from 'bippy';
248
+
249
+ // ...
250
+
251
+ traverseProps(fiber, (propName, next, prev) => {
252
+ console.log(propName, next, prev);
253
+ });
254
+ ```
255
+
256
+ ### traverseState
257
+
258
+ traverses the state (useState, useReducer, etc.) and effects that set state of a fiber.
259
+
260
+ ```typescript
261
+ import { traverseState } from 'bippy';
262
+
263
+ // ...
264
+
265
+ traverseState(fiber, (next, prev) => {
266
+ console.log(next, prev);
267
+ });
268
+ ```
269
+
270
+ ### traverseEffects
271
+
272
+ traverses the effects (useEffect, useLayoutEffect, etc.) of a fiber.
273
+
274
+ ```typescript
275
+ import { traverseEffects } from 'bippy';
276
+
277
+ // ...
278
+
279
+ traverseEffects(fiber, (effect) => {
280
+ console.log(effect);
281
+ });
282
+ ```
283
+
284
+ ### traverseContexts
285
+
286
+ traverses the contexts (useContext) of a fiber.
287
+
288
+ ```typescript
289
+ import { traverseContexts } from 'bippy';
290
+
291
+ // ...
292
+
293
+ traverseContexts(fiber, (next, prev) => {
294
+ console.log(next, prev);
295
+ });
296
+ ```
297
+
298
+
299
+ ### setFiberId / getFiberId
300
+
301
+ set and get a persistent identity for a fiber. by default, fibers are anonymous and have no identity.
302
+
303
+ ```typescript
304
+ import { setFiberId, getFiberId } from 'bippy';
305
+
306
+ // ...
307
+
308
+ setFiberId(fiber);
309
+ console.log('unique id for fiber:', getFiberId(fiber));
310
+ ```
311
+
312
+ ### isHostFiber
313
+
314
+ returns `true` if the fiber is a host fiber (e.g., a DOM node in react-dom).
315
+
316
+ ```typescript
317
+ import { isHostFiber } from 'bippy';
318
+
319
+ if (isHostFiber(fiber)) {
320
+ console.log('fiber is a host fiber');
321
+ }
322
+ ```
323
+
324
+ ### isCompositeFiber
325
+
326
+ 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).
327
+
328
+ ```typescript
329
+ import { isCompositeFiber } from 'bippy';
330
+
331
+ if (isCompositeFiber(fiber)) {
332
+ console.log('fiber is a composite fiber');
333
+ }
334
+ ```
335
+
336
+ ### getDisplayName
337
+
338
+ returns the display name of the fiber's component, falling back to the component's function or class name if available.
339
+
340
+ ```typescript
341
+ import { getDisplayName } from 'bippy';
342
+
343
+ console.log(getDisplayName(fiber));
344
+ ```
345
+
346
+ ### getType
347
+
348
+ returns the underlying type (the component definition) for a given fiber. for example, this could be a function component or class component.
349
+
350
+ ```jsx
351
+ import { getType } from 'bippy';
352
+ import { memo } from 'react';
353
+
354
+ const RealComponent = () => {
355
+ return <div>hello</div>;
356
+ };
357
+ const MemoizedComponent = memo(() => {
358
+ return <div>hello</div>;
359
+ });
360
+
361
+ console.log(getType(fiberForMemoizedComponent) === RealComponent);
362
+ ```
363
+
364
+ ### getNearestHostFiber / getNearestHostFibers
365
+
366
+ 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.
367
+
368
+ ```jsx
369
+ import { getNearestHostFiber, getNearestHostFibers } from 'bippy';
370
+
371
+ // ...
372
+
373
+ function Component() {
374
+ return (
375
+ <>
376
+ <div>hello</div>
377
+ <div>world</div>
378
+ </>
379
+ );
380
+ }
381
+
382
+ console.log(getNearestHostFiber(fiberForComponent)); // <div>hello</div>
383
+ console.log(getNearestHostFibers(fiberForComponent)); // [<div>hello</div>, <div>world</div>]
384
+ ```
385
+
386
+ ### getTimings
387
+
388
+ returns the self and total render times for the fiber.
389
+
390
+ ```typescript
391
+ // timings don't exist in react production builds
392
+ if (fiber.actualDuration !== undefined) {
393
+ const { selfTime, totalTime } = getTimings(fiber);
394
+ console.log(selfTime, totalTime);
395
+ }
396
+ ```
397
+
398
+ ### getFiberStack
399
+
400
+ returns an array representing the stack of fibers from the current fiber up to the root.
401
+
402
+ ```typescript
403
+ [fiber, fiber.return, fiber.return.return, ...]
404
+ ```
405
+
406
+ ### getMutatedHostFibers
407
+
408
+ returns an array of all host fibers that have committed and rendered in the provided fiber's subtree.
409
+
410
+ ```typescript
411
+ import { getMutatedHostFibers } from 'bippy';
412
+
413
+ console.log(getMutatedHostFibers(fiber));
414
+ ```
415
+
416
+ ### isValidFiber
417
+
418
+ returns `true` if the given object is a valid React Fiber (i.e., has a tag, stateNode, return, child, sibling, etc.).
419
+
420
+ ```typescript
421
+ import { isValidFiber } from 'bippy';
422
+
423
+ console.log(isValidFiber(fiber));
424
+ ```
105
425
 
106
426
  ## examples
107
427
 
@@ -116,7 +436,7 @@ import {
116
436
  instrument,
117
437
  isHostFiber,
118
438
  getNearestHostFiber,
119
- createFiberVisitor,
439
+ traverseRenderedFibers,
120
440
  } from 'bippy'; // must be imported BEFORE react
121
441
 
122
442
  const highlightFiber = (fiber) => {
@@ -138,50 +458,46 @@ const highlightFiber = (fiber) => {
138
458
  };
139
459
 
140
460
  /**
141
- * `createFiberVisitor` traverses the fiber tree and determines which
142
- * fibers have actually rendered.
143
- *
144
- * A fiber tree contains many fibers that may have not rendered. this
145
- * can be because it bailed out (e.g. `useMemo`) or because it wasn't
146
- * actually rendered (if <Child> re-rendered, then <Parent> didn't
147
- * actually render, but exists in the fiber tree).
148
- */
149
- const visit = createFiberVisitor({
150
- onRender(fiber) {
151
- /**
152
- * `getNearestHostFiber` is a utility function that finds the
153
- * nearest host fiber to a given fiber.
154
- *
155
- * a host fiber for `react-dom` is a fiber that has a DOM element
156
- * as its `stateNode`.
157
- */
158
- const hostFiber = getNearestHostFiber(fiber);
159
- highlightFiber(hostFiber);
160
- },
161
- });
162
-
163
- /**
164
- * `instrument` is a function that installs the React DevTools global
165
- * hook and allows you to set up custom handlers for React fiber events.
461
+ * `instrument` is a function that installs the react DevTools global
462
+ * hook and allows you to set up custom handlers for react fiber events.
166
463
  */
167
464
  instrument(
168
465
  /**
169
466
  * `secure` is a function that wraps your handlers in a try/catch
170
467
  * and prevents it from crashing the app. it also prevents it from
171
- * running on unsupported React versions and during production.
468
+ * running on unsupported react versions and during production.
172
469
  *
173
470
  * this is not required but highly recommended to provide "safeguards"
174
471
  * in case something breaks.
175
472
  */
176
473
  secure({
177
474
  /**
178
- * `onCommitFiberRoot` is a handler that is called when React is
179
- * ready to commit a fiber root. this means that React is has
475
+ * `onCommitFiberRoot` is a handler that is called when react is
476
+ * ready to commit a fiber root. this means that react is has
180
477
  * rendered your entire app and is ready to apply changes to
181
478
  * the host tree (e.g. via DOM mutations).
182
479
  */
183
480
  onCommitFiberRoot(rendererID, root) {
184
- visit(rendererID, root);
481
+ /**
482
+ * `traverseRenderedFibers` traverses the fiber tree and determines which
483
+ * fibers have actually rendered.
484
+ *
485
+ * A fiber tree contains many fibers that may have not rendered. this
486
+ * can be because it bailed out (e.g. `useMemo`) or because it wasn't
487
+ * actually rendered (if <Child> re-rendered, then <Parent> didn't
488
+ * actually render, but exists in the fiber tree).
489
+ */
490
+ traverseRenderedFibers(root, (fiber) => {
491
+ /**
492
+ * `getNearestHostFiber` is a utility function that finds the
493
+ * nearest host fiber to a given fiber.
494
+ *
495
+ * a host fiber for `react-dom` is a fiber that has a DOM element
496
+ * as its `stateNode`.
497
+ */
498
+ const hostFiber = getNearestHostFiber(fiber);
499
+ highlightFiber(hostFiber);
500
+ });
185
501
  },
186
502
  })
187
503
  );
@@ -195,7 +511,7 @@ here's a mini toy version of [`why-did-you-render`](https://github.com/welldone-
195
511
  import {
196
512
  instrument,
197
513
  isHostFiber,
198
- createFiberVisitor,
514
+ traverseRenderedFibers,
199
515
  isCompositeFiber,
200
516
  getDisplayName,
201
517
  traverseProps,
@@ -203,89 +519,123 @@ import {
203
519
  traverseState,
204
520
  } from 'bippy'; // must be imported BEFORE react
205
521
 
206
- const visit = createFiberVisitor({
207
- onRender(fiber) {
208
- /**
209
- * `isCompositeFiber` is a utility function that checks if a fiber is a composite fiber.
210
- * a composite fiber is a fiber that represents a function or class component.
211
- */
212
- if (!isCompositeFiber(fiber)) return;
213
-
214
- /**
215
- * `getDisplayName` is a utility function that gets the display name of a fiber.
216
- */
217
- const displayName = getDisplayName(fiber);
218
- if (!displayName) return;
219
-
220
- const changes = [];
221
-
222
- /**
223
- * `traverseProps` is a utility function that traverses the props of a fiber.
224
- */
225
- traverseProps(fiber, (propName, next, prev) => {
226
- if (next !== prev) {
227
- changes.push({
228
- name: `prop ${propName}`,
229
- prev,
230
- next,
522
+ instrument(
523
+ secure({
524
+ onCommitFiberRoot(rendererID, root) {
525
+ traverseRenderedFibers(root, (fiber) => {
526
+ /**
527
+ * `isCompositeFiber` is a utility function that checks if a fiber is a composite fiber.
528
+ * a composite fiber is a fiber that represents a function or class component.
529
+ */
530
+ if (!isCompositeFiber(fiber)) return;
531
+
532
+ /**
533
+ * `getDisplayName` is a utility function that gets the display name of a fiber.
534
+ */
535
+ const displayName = getDisplayName(fiber);
536
+ if (!displayName) return;
537
+
538
+ const changes = [];
539
+
540
+ /**
541
+ * `traverseProps` is a utility function that traverses the props of a fiber.
542
+ */
543
+ traverseProps(fiber, (propName, next, prev) => {
544
+ if (next !== prev) {
545
+ changes.push({
546
+ name: `prop ${propName}`,
547
+ prev,
548
+ next,
549
+ });
550
+ }
231
551
  });
232
- }
233
- });
234
552
 
235
- let contextId = 0;
236
- /**
237
- * `traverseContexts` is a utility function that traverses the contexts of a fiber.
238
- * Contexts don't have a "name" like props, so we use an id to identify them.
239
- */
240
- traverseContexts(fiber, (next, prev) => {
241
- if (next !== prev) {
242
- changes.push({
243
- name: `context ${contextId}`,
244
- prev,
245
- next,
246
- contextId,
553
+ let contextId = 0;
554
+ /**
555
+ * `traverseContexts` is a utility function that traverses the contexts of a fiber.
556
+ * Contexts don't have a "name" like props, so we use an id to identify them.
557
+ */
558
+ traverseContexts(fiber, (next, prev) => {
559
+ if (next !== prev) {
560
+ changes.push({
561
+ name: `context ${contextId}`,
562
+ prev,
563
+ next,
564
+ contextId,
565
+ });
566
+ }
567
+ contextId++;
247
568
  });
248
- }
249
- contextId++;
250
- });
251
569
 
252
- let stateId = 0;
253
- /**
254
- * `traverseState` is a utility function that traverses the state of a fiber.
255
- *
256
- * State don't have a "name" like props, so we use an id to identify them.
257
- */
258
- traverseState(fiber, (value, prevValue) => {
259
- if (next !== prev) {
260
- changes.push({
261
- name: `state ${stateId}`,
262
- prev,
263
- next,
570
+ let stateId = 0;
571
+ /**
572
+ * `traverseState` is a utility function that traverses the state of a fiber.
573
+ *
574
+ * State don't have a "name" like props, so we use an id to identify them.
575
+ */
576
+ traverseState(fiber, (value, prevValue) => {
577
+ if (next !== prev) {
578
+ changes.push({
579
+ name: `state ${stateId}`,
580
+ prev,
581
+ next,
582
+ });
583
+ }
584
+ stateId++;
264
585
  });
265
- }
266
- stateId++;
267
- });
268
-
269
- console.group(
270
- `%c${displayName}`,
271
- 'background: hsla(0,0%,70%,.3); border-radius:3px; padding: 0 2px;'
272
- );
273
- for (const { name, prev, next } of changes) {
274
- console.log(`${name}:`, prev, '!==', next);
275
- }
276
- console.groupEnd();
277
- },
278
- });
279
586
 
280
- instrument(
281
- secure({
282
- onCommitFiberRoot(rendererID, root) {
283
- visit(rendererID, root);
587
+ console.group(
588
+ `%c${displayName}`,
589
+ 'background: hsla(0,0%,70%,.3); border-radius:3px; padding: 0 2px;'
590
+ );
591
+ for (const { name, prev, next } of changes) {
592
+ console.log(`${name}:`, prev, '!==', next);
593
+ }
594
+ console.groupEnd();
595
+ });
284
596
  },
285
597
  })
286
598
  );
287
599
  ```
288
600
 
601
+ ## glossary
602
+
603
+ - fiber: a "unit of execution" in react, representing a component or dom element
604
+ - commit: the process of applying changes to the host tree (e.g. DOM mutations)
605
+ - render: the process of building the fiber tree by executing component function/classes
606
+ - host tree: the tree of UI elements that react mutates (e.g. DOM elements)
607
+ - reconciler (or "renderer"): custom bindings for react, e.g. react-dom, react-native, react-three-fiber, etc to mutate the host tree
608
+ - `rendererID`: the id of the reconciler, starting at 1 (can be from multiple reconciler instances)
609
+ - `root`: a special `FiberRoot` type that contains the container fiber (the one you pass to `ReactDOM.createRoot`) in the `current` property
610
+ - `onCommitFiberRoot`: called when react is ready to commit a fiber root
611
+ - `onPostCommitFiberRoot`: called when react has committed a fiber root and effects have run
612
+ - `onCommitFiberUnmount`: called when a fiber unmounts
613
+
614
+ ## development
615
+
616
+ we use a pnpm monorepo, get started by running:
617
+
618
+ ```shell
619
+ pnpm install
620
+ # create dev builds
621
+ pnpm run dev
622
+ # run unit tests
623
+ pnpm run test
624
+ ```
625
+
626
+ you can ad-hoc test by running `pnpm run dev` in the `/kitchen-sink` directory.
627
+
628
+ ```shell
629
+ cd kitchen-sink
630
+ pnpm run dev
631
+ ```
632
+
289
633
  ## misc
290
634
 
635
+ 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.
636
+
637
+ 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.
638
+
639
+ 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.
640
+
291
641
  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.