bippy 0.1.0 → 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/LICENSE +1 -1
- package/README.md +355 -18
- package/dist/core.cjs +742 -0
- package/dist/core.d.cts +185 -0
- package/dist/core.d.ts +185 -0
- package/dist/core.js +686 -0
- package/dist/index.cjs +285 -145
- package/dist/index.d.cts +6 -210
- package/dist/index.d.ts +6 -210
- package/dist/index.global.js +4 -5
- package/dist/index.js +260 -145
- package/dist/scan/index.cjs +1087 -0
- package/dist/scan/index.d.cts +24 -0
- package/dist/scan/index.d.ts +24 -0
- package/dist/scan/index.global.js +9 -0
- package/dist/scan/index.js +1079 -0
- package/dist/types-CxxtX49h.d.cts +69 -0
- package/dist/types-CxxtX49h.d.ts +69 -0
- package/package.json +19 -11
package/LICENSE
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copyright 2024 Aiden Bai
|
|
1
|
+
Copyright 2024 Aiden Bai, Million Software, Inc.
|
|
2
2
|
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
4
|
|
package/README.md
CHANGED
|
@@ -9,26 +9,46 @@
|
|
|
9
9
|
[](https://npmjs.com/package/bippy)
|
|
10
10
|
[](https://npmjs.com/package/bippy)
|
|
11
11
|
|
|
12
|
-
a
|
|
12
|
+
bippy is a toolkit to **hack into react internals**
|
|
13
13
|
|
|
14
|
-
bippy
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
20
|
+
```jsx
|
|
21
|
+
import { instrument, traverseFiber } from 'bippy';
|
|
20
22
|
|
|
21
|
-
|
|
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
|
+
```
|
|
32
|
+
|
|
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>
|
|
22
42
|
|
|
23
|
-
## how it works
|
|
43
|
+
## how it works & motivation
|
|
24
44
|
|
|
25
|
-
bippy allows you to **access** and **use** fibers
|
|
45
|
+
bippy allows you to **access** and **use** react fibers **outside** of react components.
|
|
26
46
|
|
|
27
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).
|
|
28
48
|
|
|
29
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/).
|
|
30
50
|
|
|
31
|
-
fibers are useful because they contain information about the
|
|
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:
|
|
32
52
|
|
|
33
53
|
```typescript
|
|
34
54
|
interface Fiber {
|
|
@@ -44,6 +64,9 @@ interface Fiber {
|
|
|
44
64
|
// parent fiber
|
|
45
65
|
return: Fiber | null;
|
|
46
66
|
|
|
67
|
+
// the previous or current version of the fiber
|
|
68
|
+
alternate: Fiber | null;
|
|
69
|
+
|
|
47
70
|
// saved props input
|
|
48
71
|
memoizedProps: any;
|
|
49
72
|
|
|
@@ -71,16 +94,16 @@ while all of the information is there, it's not super easy to work with, and cha
|
|
|
71
94
|
|
|
72
95
|
however, fibers aren't directly accessible by the user. so, we have to hack our way around to accessing it.
|
|
73
96
|
|
|
74
|
-
luckily, react [reads from a property](https://github.com/facebook/react/blob/6a4b46cd70d2672bc4be59dcb5b8dede22ed0cef/packages/react-reconciler/src/
|
|
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.
|
|
75
98
|
|
|
76
99
|
here's what it roughly looks like:
|
|
77
100
|
|
|
78
101
|
```typescript
|
|
79
102
|
interface __REACT_DEVTOOLS_GLOBAL_HOOK__ {
|
|
80
103
|
// list of renderers (react-dom, react-native, etc.)
|
|
81
|
-
renderers: Map<RendererID,
|
|
104
|
+
renderers: Map<RendererID, reactRenderer>;
|
|
82
105
|
|
|
83
|
-
// called when react has rendered everything for an update and is ready to
|
|
106
|
+
// called when react has rendered everything for an update and the fiber tree is fully built and ready to
|
|
84
107
|
// apply changes to the host tree (e.g. DOM mutations)
|
|
85
108
|
onCommitFiberRoot: (
|
|
86
109
|
rendererID: RendererID,
|
|
@@ -92,7 +115,7 @@ interface __REACT_DEVTOOLS_GLOBAL_HOOK__ {
|
|
|
92
115
|
onPostCommitFiberRoot: (rendererID: RendererID, root: FiberRoot) => void;
|
|
93
116
|
|
|
94
117
|
// called when a specific fiber unmounts
|
|
95
|
-
onCommitFiberUnmount: (rendererID: RendererID,
|
|
118
|
+
onCommitFiberUnmount: (rendererID: RendererID, fiber: Fiber) => void;
|
|
96
119
|
}
|
|
97
120
|
```
|
|
98
121
|
|
|
@@ -102,6 +125,282 @@ bippy works by monkey-patching `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with our
|
|
|
102
125
|
- _(instead of directly mutating `onCommitFiberRoot`, ...)_
|
|
103
126
|
- `secure` to wrap your handlers in a try/catch and determine if handlers are safe to run
|
|
104
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)_
|
|
134
|
+
|
|
135
|
+
## how to use
|
|
136
|
+
|
|
137
|
+
you can either install via a npm (recommended) or a script tag.
|
|
138
|
+
|
|
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.
|
|
140
|
+
|
|
141
|
+
```shell
|
|
142
|
+
npm install bippy
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
or, use via script tag. must be added before any other scripts run:
|
|
146
|
+
|
|
147
|
+
```html
|
|
148
|
+
<script src="https://unpkg.com/bippy"></script>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
> this will cause bippy to be accessible under a `window.Bippy` global.
|
|
152
|
+
|
|
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).
|
|
154
|
+
|
|
155
|
+
### instrument
|
|
156
|
+
|
|
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.
|
|
158
|
+
|
|
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);
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
instrument(
|
|
195
|
+
secure({
|
|
196
|
+
onCommitFiberRoot(rendererID, root) {
|
|
197
|
+
visit(rendererID, root);
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### traverseFiber
|
|
204
|
+
|
|
205
|
+
calls a callback on every fiber in the fiber tree.
|
|
206
|
+
|
|
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
|
+
```
|
|
221
|
+
|
|
222
|
+
### traverseProps
|
|
223
|
+
|
|
224
|
+
traverses the props of a fiber.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { traverseProps } from 'bippy';
|
|
228
|
+
|
|
229
|
+
// ...
|
|
230
|
+
|
|
231
|
+
traverseProps(fiber, (propName, next, prev) => {
|
|
232
|
+
console.log(propName, next, prev);
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
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);
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### traverseEffects
|
|
251
|
+
|
|
252
|
+
traverses the effects (useEffect, useLayoutEffect, etc.) of a fiber.
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { traverseEffects } from 'bippy';
|
|
256
|
+
|
|
257
|
+
// ...
|
|
258
|
+
|
|
259
|
+
traverseEffects(fiber, (effect) => {
|
|
260
|
+
console.log(effect);
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### traverseContexts
|
|
265
|
+
|
|
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);
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
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>;
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
console.log(getType(fiberForMemoizedComponent) === RealComponent);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### getNearestHostFiber / getNearestHostFibers
|
|
344
|
+
|
|
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.
|
|
346
|
+
|
|
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
|
+
```
|
|
105
404
|
|
|
106
405
|
## examples
|
|
107
406
|
|
|
@@ -161,22 +460,22 @@ const visit = createFiberVisitor({
|
|
|
161
460
|
});
|
|
162
461
|
|
|
163
462
|
/**
|
|
164
|
-
* `instrument` is a function that installs the
|
|
165
|
-
* hook and allows you to set up custom handlers for
|
|
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.
|
|
166
465
|
*/
|
|
167
466
|
instrument(
|
|
168
467
|
/**
|
|
169
468
|
* `secure` is a function that wraps your handlers in a try/catch
|
|
170
469
|
* and prevents it from crashing the app. it also prevents it from
|
|
171
|
-
* running on unsupported
|
|
470
|
+
* running on unsupported react versions and during production.
|
|
172
471
|
*
|
|
173
472
|
* this is not required but highly recommended to provide "safeguards"
|
|
174
473
|
* in case something breaks.
|
|
175
474
|
*/
|
|
176
475
|
secure({
|
|
177
476
|
/**
|
|
178
|
-
* `onCommitFiberRoot` is a handler that is called when
|
|
179
|
-
* ready to commit a fiber root. this means that
|
|
477
|
+
* `onCommitFiberRoot` is a handler that is called when react is
|
|
478
|
+
* ready to commit a fiber root. this means that react is has
|
|
180
479
|
* rendered your entire app and is ready to apply changes to
|
|
181
480
|
* the host tree (e.g. via DOM mutations).
|
|
182
481
|
*/
|
|
@@ -286,6 +585,44 @@ instrument(
|
|
|
286
585
|
);
|
|
287
586
|
```
|
|
288
587
|
|
|
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
|
+
```
|
|
612
|
+
|
|
613
|
+
you can ad-hoc test by running `pnpm run dev` in the `/kitchen-sink` directory.
|
|
614
|
+
|
|
615
|
+
```shell
|
|
616
|
+
cd kitchen-sink
|
|
617
|
+
pnpm run dev
|
|
618
|
+
```
|
|
619
|
+
|
|
289
620
|
## misc
|
|
290
621
|
|
|
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
|
+
|
|
291
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.
|