bippy 0.6.0 → 0.6.1-dev.03b829a

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.
Files changed (66) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +126 -136
  3. package/dist/core.cjs +1 -1
  4. package/dist/core.d.cts +45 -60
  5. package/dist/core.d.ts +45 -60
  6. package/dist/core.js +1 -1
  7. package/dist/core2.cjs +9 -0
  8. package/dist/core2.d.cts +11 -3
  9. package/dist/core2.d.ts +11 -3
  10. package/dist/core2.js +9 -0
  11. package/dist/errors.d.cts +447 -0
  12. package/dist/errors.d.ts +447 -0
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +11 -3
  15. package/dist/index.d.ts +11 -3
  16. package/dist/index.iife.js +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/install-hook-only.cjs +1 -1
  19. package/dist/install-hook-only.d.cts +9 -1
  20. package/dist/install-hook-only.d.ts +9 -1
  21. package/dist/install-hook-only.iife.js +1 -1
  22. package/dist/install-hook-only.js +1 -1
  23. package/dist/rdt-hook.cjs +1 -1
  24. package/dist/rdt-hook.js +1 -1
  25. package/dist/source.cjs +14 -4
  26. package/dist/source.d.cts +27 -9
  27. package/dist/source.d.ts +27 -9
  28. package/dist/source.js +14 -4
  29. package/package.json +32 -22
  30. package/src/core.ts +368 -314
  31. package/src/errors.ts +34 -0
  32. package/src/install-hook-only.ts +2 -2
  33. package/src/rdt-hook.ts +150 -153
  34. package/src/react-internals/generated/react-work-tags.ts +318 -0
  35. package/src/react-internals/index.ts +67 -0
  36. package/src/react-internals/semver.ts +78 -0
  37. package/src/react-internals/types.ts +269 -0
  38. package/src/source/constants.ts +1 -1
  39. package/src/source/error-stack.ts +11 -0
  40. package/src/source/get-display-name-from-source.ts +1 -1
  41. package/src/source/get-source.ts +4 -3
  42. package/src/source/index.ts +9 -1
  43. package/src/source/inspect-hooks.ts +221 -169
  44. package/src/source/owner-stack.ts +88 -114
  45. package/src/source/parse-debug-stack.ts +4 -4
  46. package/src/source/parse-hook-names.ts +1 -1
  47. package/src/source/parse-stack.ts +1 -1
  48. package/src/source/symbolication.ts +488 -88
  49. package/dist/get-source.cjs +0 -19
  50. package/dist/get-source.js +0 -19
  51. package/dist/react-refresh.cjs +0 -9
  52. package/dist/react-refresh.d.cts +0 -66
  53. package/dist/react-refresh.d.ts +0 -66
  54. package/dist/react-refresh.js +0 -9
  55. package/dist/unsubscribe.d.cts +0 -298
  56. package/dist/unsubscribe.d.ts +0 -298
  57. package/src/react-refresh/constants.ts +0 -9
  58. package/src/react-refresh/detect-hmr-transport.ts +0 -33
  59. package/src/react-refresh/index.ts +0 -173
  60. package/src/react-refresh/metro-hmr-transport.ts +0 -188
  61. package/src/react-refresh/next-webpack-hmr-transport.ts +0 -72
  62. package/src/react-refresh/normalize-hmr-file-path.ts +0 -24
  63. package/src/react-refresh/types.ts +0 -7
  64. package/src/react-refresh/vite-hmr-transport.ts +0 -116
  65. package/src/types.ts +0 -438
  66. package/src/unsubscribe.ts +0 -17
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2024 Aiden Bai
1
+ Copyright 2024-present Aiden Bai
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
@@ -1,7 +1,7 @@
1
1
  > [!WARNING]
2
2
  > ⚠️⚠️⚠️ **this project may break production apps and cause unexpected behavior** ⚠️⚠️⚠️
3
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.
4
+ > this project uses react internals, which can change at any time. we don't recommend depending on internals unless you really, _really_ have to. by proceeding, you acknowledge the risk of breaking your own code or apps that use your code.
5
5
 
6
6
  # <img src="https://github.com/aidenybai/bippy/blob/main/.github/public/bippy.png?raw=true" width="60" align="center" /> bippy
7
7
 
@@ -10,17 +10,17 @@
10
10
 
11
11
  bippy is a toolkit to **hack into react internals**
12
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.
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
14
 
15
- - works outside of react no react code modification needed
15
+ - works outside of react: no react code modification needed
16
16
  - utility functions that work across modern react (v17-19)
17
17
  - no prior react source code knowledge required
18
18
 
19
19
  ```jsx
20
- import { onCommitFiberRoot, traverseFiber } from "bippy"; // must be imported BEFORE react
20
+ import { instrument, traverseFiber } from "bippy"; // must be imported BEFORE react
21
21
 
22
22
  instrument({
23
- onCommitFiberRoot: (root) => {
23
+ onCommitFiberRoot(rendererID, root) {
24
24
  traverseFiber(root.current, (fiber) => {
25
25
  // prints every fiber in the current React tree
26
26
  console.log("fiber:", fiber);
@@ -33,7 +33,7 @@ instrument({
33
33
 
34
34
  bippy allows you to **access** and **use** react fibers **outside** of react components.
35
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).
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
37
 
38
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
39
 
@@ -74,14 +74,14 @@ here, the `child`, `sibling`, and `return` properties are pointers to other fibe
74
74
 
75
75
  additionally, `memoizedProps`, `memoizedState`, and `dependencies` are the fiber's props, state, and contexts.
76
76
 
77
- 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:
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
78
 
79
79
  - `traverseRenderedFibers` to detect renders and `traverseFiber` to traverse the overall fiber tree
80
80
  - _(instead of `child`, `sibling`, and `return` pointers)_
81
81
  - `traverseProps`, `traverseState`, and `traverseContexts` to traverse the fiber's props, state, and contexts
82
82
  - _(instead of `memoizedProps`, `memoizedState`, and `dependencies`)_
83
83
 
84
- however, fibers aren't directly accessible by the user. so, we have to hack our way around to accessing it.
84
+ however, react doesn't expose fibers to you directly. so, we have to hack our way around to access them.
85
85
 
86
86
  luckily, react [reads from a property](https://github.com/facebook/react/blob/6a4b46cd70d2672bc4be59dcb5b8dede22ed0cef/packages/react-reconciler/src/ReactFiberDevToolsHook.js#L48) in the window object: `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` and runs handlers on it when certain events happen. this property must exist before react's bundle is executed. this is intended for react devtools, but we can use it to our advantage.
87
87
 
@@ -107,9 +107,7 @@ interface __REACT_DEVTOOLS_GLOBAL_HOOK__ {
107
107
  bippy works by monkey-patching `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with our own custom handlers. bippy simplifies this by providing utility functions like:
108
108
 
109
109
  - `instrument` to safely patch `window.__REACT_DEVTOOLS_GLOBAL_HOOK__`
110
- - _(instead of directly mutating `onCommitFiberRoot`, ...)_
111
- - `secure` to wrap your handlers in a try/catch and determine if handlers are safe to run
112
- - _(instead of rawdogging `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` handlers, which may crash your app)_
110
+ - _(instead of directly mutating `onCommitFiberRoot`, )_
113
111
  - `traverseRenderedFibers` to traverse the fiber tree and determine which fibers have actually rendered
114
112
  - _(instead of `child`, `sibling`, and `return` pointers)_
115
113
  - `traverseFiber` to traverse the fiber tree, regardless of whether it has rendered
@@ -121,13 +119,13 @@ bippy works by monkey-patching `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with our
121
119
 
122
120
  we recommend installing via npm.
123
121
 
124
- 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.
122
+ import this package before your react app runs. it adds a special object to the global scope that react reports its internals to (react devtools uses the same mechanism). as soon as react loads and attaches, bippy starts collecting data about what is going on in react's internals.
125
123
 
126
124
  ```shell
127
125
  npm install bippy
128
126
  ```
129
127
 
130
- since bippy needs to be imported before react, some bundlers require specific configuration to ensure the correct import order.
128
+ since bippy must load before react, some bundlers need specific configuration to get the import order right.
131
129
 
132
130
  ### next.js
133
131
 
@@ -153,9 +151,9 @@ import { createRoot } from "react-dom/client";
153
151
  // ... rest of your code
154
152
  ```
155
153
 
156
- the import order is critical: bippy must be imported before any react packages.
154
+ the import order is critical: import bippy before any react packages.
157
155
 
158
- > **note for library maintainers**: if you're building a library and want to define your own utility functions while minimizing bundle size, you can use `bippy/install-hook-only` (~90 bytes) instead of the main `bippy` export. this only installs the react devtools hook without importing any utility functions, allowing you to import only what you need from `bippy/core` or define your own fiber utilities. that said, the full `bippy` package is only ~4kb gzipped, so bundle size is rarely a concern.
156
+ > **note for library maintainers**: if you're building a library and want to define your own utility functions while minimizing bundle size, you can use `bippy/install-hook-only` (~90 bytes) instead of the main `bippy` export. this only installs the react devtools hook without importing any utility functions, allowing you to import only what you need from `bippy/core` or define your own fiber utilities. that said, the full `bippy` package is only ~4 KB gzipped, so bundle size is rarely a concern.
159
157
 
160
158
  > ```typescript
161
159
  > import "bippy/install-hook-only"; // only installs the hook
@@ -170,29 +168,32 @@ the import order is critical: bippy must be imported before any react packages.
170
168
 
171
169
  ### instrument
172
170
 
173
- 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
+ patches `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` with your handlers. import bippy before react, and call `instrument` before any other methods.
174
172
 
175
- > use with the `secure` function to prevent uncaught errors from crashing your app.
173
+ bippy patches each hook event once and dispatches it to a set of listeners, so multiple `instrument` calls compose instead of replacing each other. `instrument` returns an unsubscribe function that removes exactly the handlers you registered (also a `Disposable`, so it works with `using`).
176
174
 
177
175
  ```typescript
178
- import { instrument, secure } from "bippy"; // must be imported BEFORE react
176
+ import { instrument } from "bippy"; // must be imported BEFORE react
179
177
  import * as React from "react";
180
178
 
181
- instrument(
182
- secure({
183
- onCommitFiberRoot(rendererID, root) {
184
- console.log("root ready to commit", root);
185
- },
186
- onPostCommitFiberRoot(rendererID, root) {
187
- console.log("root with effects committed", root);
188
- },
189
- onCommitFiberUnmount(rendererID, fiber) {
190
- console.log("fiber unmounted", fiber);
191
- },
192
- }),
193
- );
179
+ 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
+ onCommitFiberUnmount(rendererID, fiber) {
187
+ console.log("fiber unmounted", fiber);
188
+ },
189
+ });
190
+
191
+ // later, stop listening (other instrument() subscribers keep working)
192
+ unsubscribe();
194
193
  ```
195
194
 
195
+ instrumentation, React DevTools, and hook-listener failures propagate unchanged. callback dispatch stops at the failure so the caller controls error handling. failures created by bippy, such as unsupported hooks and source-map timeouts, use the exported `BippyError` subclasses.
196
+
196
197
  ### getRDTHook
197
198
 
198
199
  returns the `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` object. great for advanced use cases, such as accessing or modifying the `renderers` property.
@@ -209,18 +210,16 @@ console.log(hook);
209
210
  not every fiber in the fiber tree renders. `traverseRenderedFibers` allows you to traverse the fiber tree and determine which fibers have actually rendered.
210
211
 
211
212
  ```typescript
212
- import { instrument, secure, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
213
+ import { instrument, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
213
214
  import * as React from "react";
214
215
 
215
- instrument(
216
- secure({
217
- onCommitFiberRoot(rendererID, root) {
218
- traverseRenderedFibers(root, (fiber) => {
219
- console.log("fiber rendered", fiber);
220
- });
221
- },
222
- }),
223
- );
216
+ instrument({
217
+ onCommitFiberRoot(rendererID, root) {
218
+ traverseRenderedFibers(root, (fiber) => {
219
+ console.log("fiber rendered", fiber);
220
+ });
221
+ },
222
+ });
224
223
  ```
225
224
 
226
225
  ### traverseFiber
@@ -228,18 +227,16 @@ instrument(
228
227
  calls a callback on every fiber in the fiber tree.
229
228
 
230
229
  ```typescript
231
- import { instrument, secure, traverseFiber } from "bippy"; // must be imported BEFORE react
230
+ import { instrument, traverseFiber } from "bippy"; // must be imported BEFORE react
232
231
  import * as React from "react";
233
232
 
234
- instrument(
235
- secure({
236
- onCommitFiberRoot(rendererID, root) {
237
- traverseFiber(root.current, (fiber) => {
238
- console.log(fiber);
239
- });
240
- },
241
- }),
242
- );
233
+ instrument({
234
+ onCommitFiberRoot(rendererID, root) {
235
+ traverseFiber(root.current, (fiber) => {
236
+ console.log(fiber);
237
+ });
238
+ },
239
+ });
243
240
  ```
244
241
 
245
242
  ### traverseProps
@@ -342,16 +339,14 @@ import { memo } from "react";
342
339
  const RealComponent = () => {
343
340
  return <div>hello</div>;
344
341
  };
345
- const MemoizedComponent = memo(() => {
346
- return <div>hello</div>;
347
- });
342
+ const MemoizedComponent = memo(RealComponent);
348
343
 
349
344
  console.log(getType(fiberForMemoizedComponent) === RealComponent);
350
345
  ```
351
346
 
352
347
  ### getNearestHostFiber / getNearestHostFibers
353
348
 
354
- 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.
349
+ `getNearestHostFiber` returns the closest host fiber above or below a given fiber. `getNearestHostFibers` returns all host fibers associated with the provided fiber and its subtree.
355
350
 
356
351
  ```jsx
357
352
  import { getNearestHostFiber, getNearestHostFibers } from "bippy";
@@ -433,28 +428,6 @@ const latestFiber = getLatestFiber(getFiberFromHostInstance(document.querySelect
433
428
  console.log(latestFiber);
434
429
  ```
435
430
 
436
- ### getFiberSource
437
-
438
- returns the source code location of a fiber.
439
-
440
- ```typescript
441
- import { getFiberSource } from "bippy/source";
442
-
443
- const fiber = getFiberFromHostInstance(document.querySelector("div"));
444
-
445
- console.log(await getFiberSource(fiber));
446
- ```
447
-
448
- > note: in order to get accurate source locations in react >= 19, you need to add this in your `tsconfig.json`:
449
- >
450
- > ```json
451
- > {
452
- > "compilerOptions": {
453
- > "jsxImportSource": "bippy/dist"
454
- > }
455
- > }
456
- > ```
457
-
458
431
  ### overrideProps
459
432
 
460
433
  overrides component props at runtime by modifying the fiber's props.
@@ -472,7 +445,7 @@ overrideProps(fiber, {
472
445
  });
473
446
  ```
474
447
 
475
- the function accepts a fiber and a partial object containing the props to override. nested objects are automatically flattened into property paths.
448
+ the function accepts a fiber and a partial object containing the props to override. bippy automatically flattens nested objects into property paths.
476
449
 
477
450
  ### overrideHookState
478
451
 
@@ -493,7 +466,7 @@ overrideHookState(fiber, 1, {
493
466
  });
494
467
  ```
495
468
 
496
- the hook id parameter corresponds to the order of hooks in the component (0-indexed). the function can accept either a primitive value or an object for nested state updates.
469
+ the hook id parameter corresponds to the order of hooks in the component (0-indexed). pass either a primitive value or an object for nested state updates.
497
470
 
498
471
  ### overrideContext
499
472
 
@@ -519,26 +492,13 @@ the function traverses up the fiber tree to find the context provider matching t
519
492
 
520
493
  ### getSource
521
494
 
522
- gets the source code location of a composite fiber.
495
+ gets the source code location of any fiber with development source metadata. resolution is based on fiber debug data and source maps, so it is independent of the renderer's host instances and works with DOM, native, terminal, canvas, PDF, and custom reconcilers.
523
496
 
524
497
  ```typescript
525
498
  import { getSource } from "bippy/source";
526
499
 
527
- // random fiber on the DOM
528
- const hostFiber = getFiberFromHostInstance(document.querySelector("div"));
529
-
530
- // get nearest composite fiber up the tree
531
- const compositeFiber = traverseFiber(
532
- hostFiber,
533
- (fiber) => {
534
- if (isCompositeFiber(fiber)) {
535
- return fiber;
536
- }
537
- },
538
- true,
539
- );
540
-
541
- const source = await getSource(compositeFiber);
500
+ const fiber = getFiberFromHostInstance(hostInstance);
501
+ const source = await getSource(fiber);
542
502
  // {
543
503
  // columnNumber: 12,
544
504
  // fileName: 'path/to/file.tsx',
@@ -549,17 +509,38 @@ const source = await getSource(compositeFiber);
549
509
  > **caveats:**
550
510
  >
551
511
  > - only available in dev mode
552
- > - only works for composite fibers (function/class components)
553
- > - captures the location where the component is _used_, not where it's _defined_
554
- > - in react 18, resolves `_debugSource` directly (see [react#31981](https://github.com/facebook/react/issues/31981))
555
- > - in react >18, `_debugSource` is not available for host fibers
512
+ > - source availability is controlled by react and the renderer; production builds normally remove debug metadata
513
+ > - captures the location where the element is _used_; definition locations are recovered when react exposes an owned child debug stack
514
+ > - react 18 requires `_debugSource` from the JSX source transform (see [react#31981](https://github.com/facebook/react/issues/31981))
515
+ > - react 19 uses `_debugStack` and works for both composite and host fibers
516
+ > - source-map fetching is optional; runtimes without `fetch` still receive the unsymbolicated source location
517
+
518
+ `getSourceMap` accepts an optional fetch implementation plus request limits, an abort signal, and a timeout. its cache is scoped to the fetch implementation so credentials or virtual file systems cannot leak results into each other.
519
+
520
+ ### getOwnerStack / getParentStack
521
+
522
+ returns a symbolicated stack of components above a fiber.
523
+
524
+ `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).
525
+
526
+ `getParentStack` walks _all_ ancestors in the render tree (the fiber's `return` chain), including `{children}` wrappers. works on every react version.
527
+
528
+ ```typescript
529
+ import { getOwnerStack, getParentStack } from "bippy/source";
530
+
531
+ const ownerFrames = await getOwnerStack(fiber);
532
+ // [{ functionName: "Button", fileName: "src/button.tsx", lineNumber: 12, ... }, ...]
533
+
534
+ const parentFrames = await getParentStack(fiber);
535
+ // includes every wrapper between the fiber and the root
536
+ ```
556
537
 
557
538
  ## example
558
539
 
559
540
  here's a mini toy version of [`react-scan`](https://github.com/aidenybai/react-scan) that highlights renders in your app.
560
541
 
561
542
  ```javascript
562
- import { instrument, secure, getNearestHostFiber, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
543
+ import { instrument, getNearestHostFiber, traverseRenderedFibers } from "bippy"; // must be imported BEFORE react
563
544
 
564
545
  const highlightFiber = (fiber) => {
565
546
  if (!(fiber.stateNode instanceof HTMLElement)) return;
@@ -583,55 +564,64 @@ const highlightFiber = (fiber) => {
583
564
  * `instrument` is a function that installs the react DevTools global
584
565
  * hook and allows you to set up custom handlers for react fiber events.
585
566
  */
586
- instrument(
567
+ instrument({
587
568
  /**
588
- * `secure` is a function that wraps your handlers in a try/catch
589
- * and prevents it from crashing the app. it also prevents it from
590
- * running on unsupported react versions and during production.
591
- *
592
- * this is not required but highly recommended to provide "safeguards"
593
- * in case something breaks.
569
+ * `onCommitFiberRoot` is a handler that is called when react is
570
+ * ready to commit a fiber root. this means that react is has
571
+ * rendered your entire app and is ready to apply changes to
572
+ * the host tree (e.g. via DOM mutations).
594
573
  */
595
- secure({
574
+ onCommitFiberRoot(rendererID, root) {
596
575
  /**
597
- * `onCommitFiberRoot` is a handler that is called when react is
598
- * ready to commit a fiber root. this means that react is has
599
- * rendered your entire app and is ready to apply changes to
600
- * the host tree (e.g. via DOM mutations).
576
+ * `traverseRenderedFibers` traverses the fiber tree and determines which
577
+ * fibers have actually rendered.
578
+ *
579
+ * A fiber tree contains many fibers that may have not rendered. this
580
+ * can be because it bailed out (e.g. `useMemo`) or because it wasn't
581
+ * actually rendered (if <Child> re-rendered, then <Parent> didn't
582
+ * actually render, but exists in the fiber tree).
601
583
  */
602
- onCommitFiberRoot(rendererID, root) {
584
+ traverseRenderedFibers(root, (fiber) => {
603
585
  /**
604
- * `traverseRenderedFibers` traverses the fiber tree and determines which
605
- * fibers have actually rendered.
586
+ * `getNearestHostFiber` is a utility function that finds the
587
+ * nearest host fiber to a given fiber.
606
588
  *
607
- * A fiber tree contains many fibers that may have not rendered. this
608
- * can be because it bailed out (e.g. `useMemo`) or because it wasn't
609
- * actually rendered (if <Child> re-rendered, then <Parent> didn't
610
- * actually render, but exists in the fiber tree).
589
+ * a host fiber for `react-dom` is a fiber that has a DOM element
590
+ * as its `stateNode`.
611
591
  */
612
- traverseRenderedFibers(root, (fiber) => {
613
- /**
614
- * `getNearestHostFiber` is a utility function that finds the
615
- * nearest host fiber to a given fiber.
616
- *
617
- * a host fiber for `react-dom` is a fiber that has a DOM element
618
- * as its `stateNode`.
619
- */
620
- const hostFiber = getNearestHostFiber(fiber);
621
- highlightFiber(hostFiber);
622
- });
623
- },
624
- }),
625
- );
592
+ const hostFiber = getNearestHostFiber(fiber);
593
+ highlightFiber(hostFiber);
594
+ });
595
+ },
596
+ });
626
597
  ```
627
598
 
599
+ ## renderer support
600
+
601
+ bippy observes renderers through the React DevTools global hook. a renderer is automatically supported when it injects its reconciler and forwards commits to that hook.
602
+
603
+ | level | renderers | coverage |
604
+ | ------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
605
+ | automatic | React DOM, Remotion | unit matrix and browser e2e |
606
+ | automatic | React Native Fabric, React Native Skia | iOS and Android Detox e2e |
607
+ | automatic | React Three Fiber, Ink, react-nil | unit matrix |
608
+ | automatic | `@opentui/react`, `@pixi/react`, React BabylonJS | unit matrix with non-DOM host instances |
609
+ | compatibility | `@react-pdf/renderer` | its real reconciler root is forwarded through a synthetic DevTools hook bridge because react-pdf does not inject itself |
610
+ | compatibility | React Konva | its exported reconciler is injected by a test bridge because upstream automatic injection is disabled |
611
+
612
+ compatibility entries are not zero-config support claims. the bridge implementations in [`packages/bippy/tests/renderer-adapters.tsx`](packages/bippy/tests/renderer-adapters.tsx) verify bippy's fiber APIs against the real host trees while keeping the missing upstream DevTools integration explicit. React Native Windows, macOS, and NativeScript are not currently asserted.
613
+
614
+ terminal renderers must load bippy before their reconciler initializes. importing `bippy` first works in Node and Bun; `bippy/install-hook-only` is also available as a minimal prelude when application import order is controlled elsewhere. the test suite verifies OpenTUI in clean Node and Bun processes and verifies that Ink can replace the hook with full React DevTools without losing either renderer.
615
+
616
+ `@testing-library/react` is a React DOM testing utility, not a renderer. bippy uses it throughout the test suite, including hydration, event-driven updates, portals, unmounts, and error-boundary recovery.
617
+
628
618
  ## glossary
629
619
 
630
- - fiber: a "unit of execution" in react, representing a component or dom element
620
+ - fiber: a unit of execution in react, representing a component or dom element
631
621
  - commit: the process of applying changes to the host tree (e.g. DOM mutations)
632
622
  - render: the process of building the fiber tree by executing component function/classes
633
623
  - host tree: the tree of UI elements that react mutates (e.g. DOM elements)
634
- - reconciler (or "renderer"): custom bindings for react, e.g. react-dom, react-native, react-three-fiber, etc to mutate the host tree
624
+ - reconciler (or renderer): custom bindings for react, e.g. react-dom, react-native, react-three-fiber, etc to mutate the host tree
635
625
  - `rendererID`: the id of the reconciler, starting at 1 (can be from multiple reconciler instances)
636
626
  - `root`: a special `FiberRoot` type that contains the container fiber (the one you pass to `ReactDOM.createRoot`) in the `current` property
637
627
  - `onCommitFiberRoot`: called when react is ready to commit a fiber root
@@ -640,7 +630,7 @@ instrument(
640
630
 
641
631
  ## misc
642
632
 
643
- bippy was initially created for [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.
633
+ 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.
644
634
 
645
635
  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.
646
636
 
package/dist/core.cjs CHANGED
@@ -6,4 +6,4 @@
6
6
  * This source code is licensed under the MIT license found in the
7
7
  * LICENSE file in the root directory of this source tree.
8
8
  */
9
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./rdt-hook.cjs`),t=60111,n=`Symbol(react.concurrent_mode)`,r=`Symbol(react.async_mode)`,i=13366,a=e=>typeof e==`object`&&!!e&&`$$typeof`in e&&[`Symbol(react.element)`,`Symbol(react.transitional.element)`].includes(String(e.$$typeof)),o=e=>typeof e==`object`&&!!e&&`tag`in e&&`stateNode`in e&&`return`in e&&`child`in e&&`sibling`in e&&`flags`in e,s=e=>{switch(e.tag){case 5:case 26:case 27:return!0;default:return typeof e.type==`string`}},c=e=>{switch(e.tag){case 1:case 11:case 0:case 14:case 15:return!0;default:return!1}},l=e=>!e||typeof e!=`object`?!1:`pendingProps`in e&&!(`containerInfo`in e),ee=(e,t)=>{try{let n=e.dependencies,r=e.alternate?.dependencies;if(!n||!r||typeof n!=`object`||!(`firstContext`in n)||typeof r!=`object`||!(`firstContext`in r))return!1;let i=n.firstContext,a=r.firstContext;for(;i&&typeof i==`object`&&`memoizedValue`in i||a&&typeof a==`object`&&`memoizedValue`in a;){if(t(i,a)===!0)return!0;i=i?.next,a=a?.next}}catch{}return!1},u=(e,t)=>{try{let n=e.memoizedState,r=e.alternate?.memoizedState;for(;n||r;){if(t(n,r)===!0)return!0;n=n?.next,r=r?.next}}catch{}return!1},d=(e,t)=>{try{let n=e.memoizedProps,r=e.alternate?.memoizedProps||{};for(let e of Object.keys(n))if(t(e,n[e],r[e])===!0)return!0;for(let e of Object.keys(r))if(!(e in n)&&t(e,n[e],r[e])===!0)return!0}catch{}return!1},f=e=>{let t=e.memoizedProps,n=e.alternate?.memoizedProps||{},r=e.flags??e.effectTag??0;switch(e.tag){case 1:case 9:case 11:case 0:case 14:case 15:return(r&1)==1;default:return e.alternate?n!==t||e.alternate.memoizedState!==e.memoizedState||e.alternate.ref!==e.ref:!0}},p=e=>!!(e.flags&(i|8)||e.subtreeFlags&(i|8)),te=e=>{let t=[],n=[e];for(;n.length;){let e=n.pop();e&&(s(e)&&p(e)&&f(e)&&t.push(e),e.child&&n.push(e.child),e.sibling&&n.push(e.sibling))}return t},ne=e=>{let t=[],n=e;for(;n.return;)t.push(n),n=n.return;return t},m=e=>{switch(e.tag){case 18:return!0;case 7:case 6:case 23:case 22:return!0;case 3:return!1;default:{let i=typeof e.type==`object`&&e.type!==null?e.type.$$typeof:e.type;if(typeof i==`symbol`)return i.description===`react.concurrent_mode`||i.description===`react.async_mode`;switch(i){case t:case n:case r:return!0;default:return!1}}}},h=(e,t=!1)=>{let n=_(e,s,t);return n||=_(e,s,!t),n},g=e=>{let t=[],n=[];for(s(e)?t.push(e):e.child&&n.push(e.child);n.length;){let e=n.pop();if(!e)break;s(e)?t.push(e):e.child&&n.push(e.child),e.sibling&&n.push(e.sibling)}return t};function _(e,t,n=!1){if(!e)return null;let r=t(e);if(r instanceof Promise)return(async()=>{if(await r===!0)return e;let i=n?e.return:e.child;for(;i;){let e=await y(i,t,n);if(e)return e;i=n?null:i.sibling}return null})();if(r===!0)return e;let i=n?e.return:e.child;for(;i;){let e=v(i,t,n);if(e)return e;i=n?null:i.sibling}return null}const v=(e,t,n=!1)=>{if(!e)return null;if(t(e)===!0)return e;let r=n?e.return:e.child;for(;r;){let e=v(r,t,n);if(e)return e;r=n?null:r.sibling}return null},y=async(e,t,n=!1)=>{if(!e)return null;if(await t(e)===!0)return e;let r=n?e.return:e.child;for(;r;){let e=await y(r,t,n);if(e)return e;r=n?null:r.sibling}return null},b=e=>{let t=e?.actualDuration??0,n=t,r=e?.child??null;for(;t>0&&r!=null;)n-=r.actualDuration??0,r=r.sibling;return{selfTime:n,totalTime:t}},x=e=>!!e.updateQueue?.memoCache,S=e=>{let t=e;return typeof t==`function`?t:typeof t==`object`&&t?S(t.type||t.render):null},re=e=>{let t=e;if(typeof t==`string`)return t;if(typeof t!=`function`&&!(typeof t==`object`&&t))return null;let n=t.displayName||t.name||null;if(n)return n;let r=S(t);return r&&(r.displayName||r.name)||null},ie=e=>{try{if(typeof e.version==`string`&&e.bundleType>0)return`development`}catch{}return`production`},ae=()=>{let t=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;return!!t?._instrumentationIsActive||e.l(t)||e.c(t)},C=new Set,w=e=>{let t=e.alternate;if(!t)return e;if(t.actualStartTime&&e.actualStartTime)return t.actualStartTime>e.actualStartTime?t:e;for(let t of C){let n=_(t.current,t=>{if(t===e)return!0});if(n)return n}return e};let T=0;const E=new WeakMap,D=(e,t=T++)=>{E.set(e,t)},O=e=>{let t=E.get(e);return!t&&e.alternate&&(t=E.get(e.alternate)),t||(t=T++,D(e,t)),t},k=(e,t,n)=>{let r=t;for(;r!=null;){if(E.has(r)||O(r),!m(r)&&f(r)&&e(r,`mount`),r.tag===13)if(r.memoizedState!==null){let t=r.child,n=t?t.sibling:null;if(n){let t=n.child;t!==null&&k(e,t,!1)}}else{let t=null;r.child!==null&&(t=r.child.child),t!==null&&k(e,t,!1)}else r.child!=null&&k(e,r.child,!0);r=n?r.sibling:null}},A=(e,t,n,r)=>{if(E.has(t)||O(t),!n)return;E.has(n)||O(n);let i=t.tag===13,a=!m(t);a&&f(t)&&e(t,`update`);let o=i&&n.memoizedState!==null,s=i&&t.memoizedState!==null;if(o&&s){let r=t.child?.sibling??null,i=n.child?.sibling??null;r!==null&&i!==null&&A(e,r,i,t)}else if(o&&!s){let n=t.child;n!==null&&k(e,n,!0)}else if(!o&&s){M(e,n);let r=t.child?.sibling??null;r!==null&&k(e,r,!0)}else if(t.child!==n.child){let n=t.child;for(;n;){if(n.alternate){let i=n.alternate;A(e,n,i,a?t:r)}else k(e,n,!1);n=n.sibling}}},j=(e,t)=>{(t.tag===3||!m(t))&&e(t,`unmount`)},M=(e,t)=>{let n=t.tag===13&&t.memoizedState!==null,r=t.child;for(n&&(r=(t.child?.sibling??null)?.child??null);r!==null;)r.return!==null&&(j(e,r),M(e,r)),r=r.sibling};let oe=0;const N=new WeakMap,se=(e,t)=>{let n=`current`in e?e.current:e,r=N.get(e);r||(r={id:oe++,prevFiber:null},N.set(e,r));let{prevFiber:i}=r;if(!n)j(t,n);else if(i!==null){let e=i&&i.memoizedState!=null&&i.memoizedState.element!=null&&i.memoizedState.isDehydrated!==!0,r=n.memoizedState!=null&&n.memoizedState.element!=null&&n.memoizedState.isDehydrated!==!0;!e&&r?k(t,n,!1):e&&r?A(t,n,n.alternate,null):e&&!r&&j(t,n)}else k(t,n,!0);r.prevFiber=n},P=new Set;let F=!1;const I=()=>{if(F||!e.a())return;let t=e.i();if(t?.renderers){F=!0,Q(t);for(let e of t.renderers.values())P.add(e);e.u(e=>{P.add(e)})}},L=t=>{if(!e.a())return null;let n=t;for(;n.return;)n=n.return;let r=Z.get(n.stateNode);return r===void 0?null:e.i().renderers?.get(r)??null},R=e=>{I();let t=L(e);return t?[t]:Array.from(P)},z=(e,t,n,r)=>{for(let i of e)try{i.overrideProps?.(t,n,r)}catch{}},B=(e,t)=>{let n=e.memoizedState;for(let e=0;e<t;e++){if(!n?.next)return null;n=n.next}let r=n?.queue;return H(r)&&typeof r.dispatch==`function`?r.dispatch:null},V=(e,t)=>{let n=e;for(;n;){let e=n.type;if(e===t||e?.Provider===t)return n;n=n.return}return null},H=e=>Object.prototype.toString.call(e)===`[object Object]`&&(Object.getPrototypeOf(e)===Object.prototype||Object.getPrototypeOf(e)===null),U=(e,t=[])=>{if(!H(e))return[{path:t,value:e}];let n=[];for(let r in e){let i=e[r],a=t.concat(r);H(i)?n.push(...U(i,a)):n.push({path:a,value:i})}return n},W=e=>H(e)?U(e):[{path:[],value:e}],G=(e,t)=>{let n=R(e);for(let{path:r,value:i}of W(t))z(n,e,r,i)},ce=(e,t,n)=>{let r=R(e).filter(e=>!!e.overrideHookState),i=W(n);if(r.length>0){let n=String(t);for(let t of r)for(let{path:r,value:a}of i)try{t.overrideHookState?.(e,n,r,a)}catch{}return}if(H(n))return;let a=B(e,t);if(a)try{a(n)}catch{}},le=(e,t,n)=>{let r=V(e,t);if(!r)return;let i=R(r);for(let{path:e,value:t}of W(n))z(i,r,[`value`,...e],t),r.alternate&&z(i,r.alternate,[`value`,...e],t)},K=new Set,q=new Set,J=new Set,Y=new Set,X=new WeakMap,Z=new WeakMap,Q=e=>{let t=X.get(e)??{};if(X.set(e,t),!t.onCommitFiberRoot||e.onCommitFiberRoot!==t.onCommitFiberRoot){let n=e.onCommitFiberRoot,r=(t,i,a)=>{if(n?.(t,i,a),X.get(e)?.onCommitFiberRoot===r){C.add(i),Z.set(i,t);for(let e of K)e(t,i,a)}};t.onCommitFiberRoot=r,e.onCommitFiberRoot=r}if(!t.onCommitFiberUnmount||e.onCommitFiberUnmount!==t.onCommitFiberUnmount){let n=e.onCommitFiberUnmount,r=(t,i)=>{if(n?.(t,i),X.get(e)?.onCommitFiberUnmount===r)for(let e of q)e(t,i)};t.onCommitFiberUnmount=r,e.onCommitFiberUnmount=r}if(!t.onPostCommitFiberRoot||e.onPostCommitFiberRoot!==t.onPostCommitFiberRoot){let n=e.onPostCommitFiberRoot,r=(t,i)=>{if(n?.(t,i),X.get(e)?.onPostCommitFiberRoot===r)for(let e of J)e(t,i)};t.onPostCommitFiberRoot=r,e.onPostCommitFiberRoot=r}if(!t.onScheduleFiberRoot||e.onScheduleFiberRoot!==t.onScheduleFiberRoot){let n=e.onScheduleFiberRoot,r=(t,i,a)=>{if(n?.(t,i,a),X.get(e)?.onScheduleFiberRoot===r)for(let e of Y)e(t,i,a)};t.onScheduleFiberRoot=r,e.onScheduleFiberRoot=r}},ue=t=>{let n=e.i(t.onActive);n._instrumentationSource=t.name??e.t,Q(n);let{onActive:r,onCommitFiberRoot:i,onCommitFiberUnmount:a,onPostCommitFiberRoot:o,onScheduleFiberRoot:s}=t;return i&&K.add(i),a&&q.add(a),o&&J.add(o),s&&Y.add(s),e.m(()=>{r&&e.n.delete(r),i&&K.delete(i),a&&q.delete(a),o&&J.delete(o),s&&Y.delete(s)})},$=new Set,de=e=>e.startsWith(`__reactContainer$`)||e.startsWith(`__reactInternalInstance$`)||e.startsWith(`__reactFiber`),fe=e=>{let t=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;if(t?.renderers)for(let n of t.renderers.values())try{let t=n.findFiberByHostInstance?.(e);if(t)return t}catch{}if(typeof e==`object`&&e){if(`_reactRootContainer`in e)return e._reactRootContainer?._internalRoot?.current?.child;let t=e.__internalInstanceHandle??e._internalInstanceHandle;if(l(t))return t;let n=e;for(let e of $){let t=n[e];if(t)return t}for(let e of Object.keys(n))if(de(e))return $.add(e),n[e]||null}return null};exports.ActivityComponentTag=28,exports.BIPPY_INSTRUMENTATION_STRING=e.t,exports.CONCURRENT_MODE_NUMBER=t,exports.CONCURRENT_MODE_SYMBOL_DESCRIPTION=`react.concurrent_mode`,exports.CONCURRENT_MODE_SYMBOL_STRING=n,exports.ClassComponentTag=1,exports.ContextConsumerTag=9,exports.DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION=`react.async_mode`,exports.DEPRECATED_ASYNC_MODE_SYMBOL_STRING=r,exports.DehydratedSuspenseComponentTag=18,exports.ELEMENT_TYPE_SYMBOL_STRING=`Symbol(react.element)`,exports.ForwardRefTag=11,exports.FragmentTag=7,exports.FunctionComponentTag=0,exports.HostComponentTag=5,exports.HostHoistableTag=26,exports.HostPortalTag=4,exports.HostRootTag=3,exports.HostSingletonTag=27,exports.HostTextTag=6,exports.LazyComponentTag=16,exports.LegacyHiddenComponentTag=23,exports.MemoComponentTag=14,exports.OffscreenComponentTag=22,exports.SimpleMemoComponentTag=15,exports.SuspenseComponentTag=13,exports.SuspenseListComponentTag=19,exports.TRANSITIONAL_ELEMENT_TYPE_SYMBOL_STRING=`Symbol(react.transitional.element)`,exports.ViewTransitionComponentTag=30,exports._fiberRoots=C,exports._onActiveListeners=e.n,exports._renderers=e.r,exports.detectReactBuildType=ie,exports.didFiberCommit=p,exports.didFiberRender=f,exports.getDisplayName=re,exports.getFiberFromHostInstance=fe,exports.getFiberId=O,exports.getFiberStack=ne,exports.getLatestFiber=w,exports.getMutatedHostFibers=te,exports.getNearestHostFiber=h,exports.getNearestHostFibers=g,exports.getRDTHook=e.i,exports.getTimings=b,exports.getType=S,exports.hasMemoCache=x,exports.hasRDTHook=e.a,exports.installRDTHook=e.o,exports.instrument=ue,exports.isClientEnvironment=e.s,exports.isCompositeFiber=c,exports.isFiber=l,exports.isHostFiber=s,exports.isInstrumentationActive=ae,exports.isReactRefresh=e.c,exports.isRealReactDevtools=e.l,exports.isValidElement=a,exports.isValidFiber=o,exports.onRendererInject=e.u,exports.overrideContext=le,exports.overrideHookState=ce,exports.overrideProps=G,exports.patchRDTHook=e.d,exports.safelyInstallRDTHook=e.f,exports.setFiberId=D,exports.toUnsubscribe=e.m,exports.traverseContexts=ee,exports.traverseFiber=_,exports.traverseProps=d,exports.traverseRenderedFibers=se,exports.traverseState=u,exports.version=e.p;
9
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./rdt-hook.cjs"),t=require("./core2.cjs");exports.BIPPY_INSTRUMENTATION_STRING=e.t,exports.BippyError=t.M,exports.BippyHookInspectionError=t.N,exports.BippyHookRenderError=t.P,exports.BippySourceMapError=t.F,exports.BippyUnsupportedHookError=t.I,exports.ReactSymbols=t.R,exports._fiberRoots=t.t,exports._onActiveListeners=e.n,exports._renderers=e.r,exports.detectReactBuildType=t.n,exports.didFiberCommit=t.r,exports.didFiberRender=t.i,exports.getDisplayName=t.a,exports.getFiberFromHostInstance=t.o,exports.getFiberId=t.s,exports.getFiberStack=t.c,exports.getLatestFiber=t.l,exports.getMutatedHostFibers=t.u,exports.getNearestHostFiber=t.d,exports.getNearestHostFibers=t.f,exports.getRDTHook=e.a,exports.getReactWorkTags=t.z,exports.getTimings=t.p,exports.getType=t.m,exports.hasMemoCache=t.h,exports.hasRDTHook=e.o,exports.installRDTHook=e.s,exports.instrument=t.g,exports.isCompositeFiber=t._,exports.isFiber=t.v,exports.isHostFiber=t.y,exports.isInstrumentationActive=t.b,exports.isRealReactDevtools=e.c,exports.isValidElement=t.x,exports.isValidFiber=t.S,exports.onRendererInject=e.u,exports.overrideContext=t.C,exports.overrideHookState=t.w,exports.overrideProps=t.T,exports.patchRDTHook=e.d,exports.setFiberId=t.E,exports.traverseContexts=t.D,exports.traverseFiber=t.O,exports.traverseProps=t.k,exports.traverseRenderedFibers=t.A,exports.traverseState=t.j,exports.version=e.f;