bippy 0.0.23 → 0.0.25

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
@@ -2,76 +2,219 @@
2
2
 
3
3
  a hacky way to get fibers from react. used internally for [`react-scan`](https://github.com/aidenybai/react-scan).
4
4
 
5
- bippy works by setting a "fake" version of the `__REACT_DEVTOOLS_GLOBAL_HOOK__` object. this gives us access to react internals without actually using react devtools.
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.
6
6
 
7
7
  > [!WARNING]
8
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.
9
9
 
10
- ## example
10
+ ## tutorial: create a mini react-scan
11
11
 
12
- here i wrote a `getRenderInfo` function, where you're able to pass any component identifier and get back the number of times it rendered, as well as the total and self time. this is done by traversing the fiber tree during a [commit](https://react.dev/learn/render-and-commit) via `onCommitFiberRoot`.
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.
13
13
 
14
- inspect it live [here](https://bippy.million.dev/).
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/).
15
+
16
+ a simplified version of a fiber looks roughly like this:
17
+
18
+ ```typescript
19
+ interface Fiber {
20
+ // component type (function/class)
21
+ type: any;
22
+
23
+ child: Fiber | null;
24
+ sibling: Fiber | null;
25
+
26
+ // parent fiber
27
+ return: Fiber | null;
28
+
29
+ // saved props input
30
+ memoizedProps: any;
31
+
32
+ // state (useState, useReducer, useSES, etc.)
33
+ memoizedState: any;
34
+
35
+ // contexts (useContext)
36
+ dependencies: Dependencies | null;
37
+ }
38
+ ```
39
+
40
+ however, fibers aren't directly accessible by the user. so, we have to hack our way around to accessing it.
41
+
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.
43
+
44
+ here's what it roughly looks like:
45
+
46
+ ```typescript
47
+ interface __REACT_DEVTOOLS_GLOBAL_HOOK__ {
48
+ // list of renderers (react-dom, react-native, etc.)
49
+ renderers: Map<RendererID, ReactRenderer>;
50
+
51
+ // called when react has rendered everythign and ready to apply changes to the host tree (e.g. DOM mutations)
52
+ onCommitFiberRoot: (
53
+ rendererID: RendererID,
54
+ fiber: Record<string, unknown>,
55
+ commitPriority?: number,
56
+ didError?: boolean,
57
+ ) => void;
58
+ }
59
+ ```
60
+
61
+ we can use bippy's utils and the `onCommitFiberRoot` handler to detect renders!
62
+
63
+ ### 0. setup
64
+
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)
66
+
67
+ then, install bippy:
68
+
69
+ ```bash
70
+ npm install bippy
71
+ ```
72
+
73
+ finally, re-run the dev server:
74
+
75
+ ```bash
76
+ npm run dev
77
+ ```
78
+
79
+ ### 1. use `onCommitFiberRoot` to get fibers
80
+
81
+ let's use `instrument` to stub the `__REACT_DEVTOOLS_GLOBAL_HOOK__` object, and setup a custom handler for `onCommitFiberRoot`.
82
+
83
+ ```jsx
84
+ import { instrument } from 'bippy'; // must be imported BEFORE react
85
+
86
+ // rest of your code ...
87
+
88
+ instrument({
89
+ onCommitFiberRoot(rendererID, root) {
90
+ const fiberRoot = root.current;
91
+ console.log('fiberRoot', fiberRoot);
92
+ },
93
+ });
94
+ ```
95
+
96
+ running this should log `fiberRoot` to the console. i recommend you playing with this code to get a feel for how fibers work.
97
+
98
+ ### 2. create a fiber visitor
99
+
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.
101
+
102
+ ```jsx
103
+ import { instrument, createFiberVisitor } from 'bippy'; // must be imported BEFORE react
104
+
105
+ // rest of your code ...
106
+
107
+ const visit = createFiberVisitor({
108
+ onRender(fiber) {
109
+ console.log('fiber render', fiber);
110
+ },
111
+ });
112
+
113
+ instrument({
114
+ onCommitFiberRoot(rendererID, root) {
115
+ visit(rendererID, root);
116
+ },
117
+ });
118
+ ```
119
+
120
+ ### 3. determine DOM nodes to highlight
121
+
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.
15
123
 
16
124
  ```jsx
17
125
  import {
18
126
  instrument,
127
+ isHostFiber,
128
+ getNearestHostFiber,
19
129
  createFiberVisitor,
20
- getTimings,
21
- getDisplayName,
22
130
  } from 'bippy'; // must be imported BEFORE react
23
- import React, { useState } from 'react';
24
- import ReactDOM from 'react-dom/client';
25
-
26
- const componentRenderMap = new WeakMap();
27
-
28
- const visitor = createFiberVisitor({
29
- onRender(fiber, phase) {
30
- const componentType = fiber.elementType;
31
- if (
32
- typeof componentType !== 'function' &&
33
- (typeof componentType !== 'object' || !componentType)
34
- ) {
35
- return;
131
+
132
+ // rest of your code ...
133
+
134
+ const highlightFiber = (fiber) => {
135
+ if (!(fiber instanceof HTMLElement)) return;
136
+
137
+ console.log('highlight dom node', fiber.stateNode);
138
+ };
139
+
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);
36
148
  }
37
- const render = componentRenderMap.get(componentType) || {
38
- count: 0,
39
- selfTime: 0,
40
- totalTime: 0,
41
- displayName: getDisplayName(componentType),
42
- };
43
- render.count++;
44
- const { selfTime, totalTime } = getTimings(fiber);
45
- render.selfTime += selfTime;
46
- render.totalTime += totalTime;
47
- componentRenderMap.set(componentType, render);
48
- console.log(phase, render);
49
149
  },
50
150
  });
51
151
 
52
152
  instrument({
53
- onCommitFiberRoot: (rendererID, fiberRoot) => {
54
- visitor(rendererID, fiberRoot);
153
+ onCommitFiberRoot(rendererID, root) {
154
+ visit(rendererID, root);
55
155
  },
56
156
  });
157
+ ```
158
+
159
+ ### 4. highlight DOM nodes
160
+
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.
57
162
 
58
- export const getRenderInfo = (componentType) => {
59
- return componentRenderMap.get(componentType);
163
+ ```jsx
164
+ import {
165
+ instrument,
166
+ isHostFiber,
167
+ getNearestHostFiber,
168
+ createFiberVisitor,
169
+ } from 'bippy'; // must be imported BEFORE react
170
+
171
+ // rest of your code ...
172
+
173
+ const highlightFiber = (fiber) => {
174
+ if (!(fiber.stateNode instanceof HTMLElement)) return;
175
+
176
+ const rect = fiber.stateNode.getBoundingClientRect();
177
+ const highlight = document.createElement('div');
178
+ highlight.style.border = '1px solid red';
179
+ highlight.style.position = 'fixed';
180
+ highlight.style.top = `${rect.top}px`;
181
+ highlight.style.left = `${rect.left}px`;
182
+ highlight.style.width = `${rect.width}px`;
183
+ highlight.style.height = `${rect.height}px`;
184
+ highlight.style.zIndex = 999999999;
185
+ document.documentElement.appendChild(highlight);
186
+ setTimeout(() => {
187
+ document.documentElement.removeChild(highlight);
188
+ }, 100);
60
189
  };
61
190
 
62
- function App() {
63
- const [count, setCount] = useState(0);
64
- const renderInfo = getRenderInfo(App);
65
- return (
66
- <button onClick={() => setCount(count + 1)}>
67
- rendered: {JSON.stringify(renderInfo, null, 2)}
68
- </button>
69
- );
70
- }
191
+ const visit = createFiberVisitor({
192
+ 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
+ }
200
+ },
201
+ });
71
202
 
72
- ReactDOM.createRoot(document.getElementById('root')).render(<App />);
203
+ instrument({
204
+ onCommitFiberRoot(rendererID, root) {
205
+ visit(rendererID, root);
206
+ },
207
+ });
73
208
  ```
74
209
 
210
+ ### 5. profit
211
+
212
+ try a completed version [here](https://bippy.million.dev)
213
+
214
+ you can learn more about bippy by [reading the source code](https://github.com/aidenybai/bippy/blob/main/src/index.ts).
215
+
216
+ looking for a more robust version of our mini react-scan? try out [react-scan](https://github.com/aidenybai/react-scan).
217
+
75
218
  ## misc
76
219
 
77
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
package/dist/index.cjs CHANGED
@@ -10,6 +10,8 @@
10
10
  */
11
11
 
12
12
  // src/index.ts
13
+ var version = "0.0.25";
14
+ var BIPPY_INSTRUMENTATION_STRING = `bippy-${version}`;
13
15
  var ClassComponentTag = 1;
14
16
  var FunctionComponentTag = 0;
15
17
  var ContextConsumerTag = 9;
@@ -94,7 +96,7 @@ var traverseProps = (fiber, selector) => {
94
96
  for (const propName of allKeys) {
95
97
  const prevValue = prevProps?.[propName];
96
98
  const nextValue = nextProps?.[propName];
97
- if (selector(prevValue, nextValue) === true) return true;
99
+ if (selector(propName, nextValue, prevValue) === true) return true;
98
100
  }
99
101
  } catch {
100
102
  }
@@ -249,39 +251,43 @@ var checkDCE = (fn) => {
249
251
  };
250
252
  var NO_OP = () => {
251
253
  };
254
+ var installRDTHook = (onActive) => {
255
+ const renderers = /* @__PURE__ */ new Map();
256
+ let i = 0;
257
+ const rdtHook = {
258
+ checkDCE,
259
+ supportsFiber: true,
260
+ supportsFlight: true,
261
+ renderers,
262
+ onCommitFiberRoot: NO_OP,
263
+ onCommitFiberUnmount: NO_OP,
264
+ onPostCommitFiberRoot: NO_OP,
265
+ inject(renderer) {
266
+ const nextID = ++i;
267
+ renderers.set(nextID, renderer);
268
+ if (!rdtHook._instrumentationIsActive) {
269
+ rdtHook._instrumentationIsActive = true;
270
+ onActive?.();
271
+ }
272
+ return nextID;
273
+ },
274
+ _instrumentationSource: BIPPY_INSTRUMENTATION_STRING,
275
+ _instrumentationIsActive: false
276
+ };
277
+ try {
278
+ Object.defineProperty(globalThis, "__REACT_DEVTOOLS_GLOBAL_HOOK__", {
279
+ configurable: true,
280
+ value: rdtHook
281
+ });
282
+ } catch {
283
+ }
284
+ return rdtHook;
285
+ };
252
286
  var getRDTHook = (onActive) => {
253
287
  let rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
254
288
  if (rdtHook) onActive?.();
255
289
  if (!window.hasOwnProperty("__REACT_DEVTOOLS_GLOBAL_HOOK__")) {
256
- const renderers = /* @__PURE__ */ new Map();
257
- let i = 0;
258
- rdtHook ??= {
259
- checkDCE,
260
- supportsFiber: true,
261
- supportsFlight: true,
262
- renderers,
263
- onCommitFiberRoot: NO_OP,
264
- onCommitFiberUnmount: NO_OP,
265
- onPostCommitFiberRoot: NO_OP,
266
- inject(renderer) {
267
- const nextID = ++i;
268
- renderers.set(nextID, renderer);
269
- if (!rdtHook._instrumentationIsActive) {
270
- rdtHook._instrumentationIsActive = true;
271
- onActive?.();
272
- }
273
- return nextID;
274
- },
275
- _instrumentationSource: "bippy",
276
- _instrumentationIsActive: Boolean(rdtHook)
277
- };
278
- try {
279
- Object.defineProperty(globalThis, "__REACT_DEVTOOLS_GLOBAL_HOOK__", {
280
- configurable: true,
281
- value: rdtHook
282
- });
283
- } catch {
284
- }
290
+ rdtHook = installRDTHook(onActive);
285
291
  }
286
292
  return rdtHook;
287
293
  };
@@ -289,10 +295,6 @@ var isInstrumentationActive = () => {
289
295
  const rdtHook = getRDTHook();
290
296
  return Boolean(rdtHook._instrumentationIsActive) || isUsingRDT();
291
297
  };
292
- if (typeof window !== "undefined") {
293
- performance.now();
294
- getRDTHook();
295
- }
296
298
  var mountFiberRecursively = (onRender, firstChild, traverseSiblings) => {
297
299
  let fiber = firstChild;
298
300
  while (fiber != null) {
@@ -409,7 +411,9 @@ var createFiberVisitor = ({
409
411
  }
410
412
  const { prevFiber } = rootInstance;
411
413
  try {
412
- if (prevFiber !== null) {
414
+ if (!rootFiber) {
415
+ unmountFiber(onRender, root);
416
+ } else if (prevFiber !== null) {
413
417
  const wasMounted = prevFiber && prevFiber.memoizedState != null && prevFiber.memoizedState.element != null && // A dehydrated root is not considered mounted
414
418
  prevFiber.memoizedState.isDehydrated !== true;
415
419
  const isMounted = rootFiber.memoizedState != null && rootFiber.memoizedState.element != null && // A dehydrated root is not considered mounted
@@ -447,7 +451,7 @@ var instrument = ({
447
451
  name
448
452
  }) => {
449
453
  const devtoolsHook = getRDTHook(onActive);
450
- devtoolsHook._instrumentationSource = name ?? "bippy";
454
+ devtoolsHook._instrumentationSource = name ?? BIPPY_INSTRUMENTATION_STRING;
451
455
  const prevOnCommitFiberRoot = devtoolsHook.onCommitFiberRoot;
452
456
  if (onCommitFiberRoot) {
453
457
  devtoolsHook.onCommitFiberRoot = (rendererID, root, priority) => {
@@ -473,7 +477,13 @@ var instrument = ({
473
477
  }
474
478
  return devtoolsHook;
475
479
  };
480
+ var isBrowser = typeof document !== "undefined" && typeof document.createElement === "function";
481
+ var isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
482
+ if (isBrowser || !isNode) {
483
+ installRDTHook();
484
+ }
476
485
 
486
+ exports.BIPPY_INSTRUMENTATION_STRING = BIPPY_INSTRUMENTATION_STRING;
477
487
  exports.CONCURRENT_MODE_NUMBER = CONCURRENT_MODE_NUMBER;
478
488
  exports.CONCURRENT_MODE_SYMBOL_STRING = CONCURRENT_MODE_SYMBOL_STRING;
479
489
  exports.ChildDeletion = ChildDeletion;
@@ -518,6 +528,7 @@ exports.getRDTHook = getRDTHook;
518
528
  exports.getTimings = getTimings;
519
529
  exports.getType = getType;
520
530
  exports.hasMemoCache = hasMemoCache;
531
+ exports.installRDTHook = installRDTHook;
521
532
  exports.instrument = instrument;
522
533
  exports.isCompositeFiber = isCompositeFiber;
523
534
  exports.isHostFiber = isHostFiber;
@@ -533,3 +544,4 @@ exports.traverseState = traverseState;
533
544
  exports.unmountFiber = unmountFiber;
534
545
  exports.unmountFiberChildrenRecursively = unmountFiberChildrenRecursively;
535
546
  exports.updateFiberRecursively = updateFiberRecursively;
547
+ exports.version = version;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { Fiber, FiberRoot } from 'react-reconciler';
2
+ import { FiberRoot, Fiber } from 'react-reconciler';
3
3
 
4
4
  interface ReactRenderer {
5
5
  version: string;
@@ -10,13 +10,15 @@ interface ReactDevToolsGlobalHook {
10
10
  supportsFiber: boolean;
11
11
  supportsFlight: boolean;
12
12
  renderers: Map<number, ReactRenderer>;
13
- onCommitFiberRoot: (rendererID: number, root: unknown, priority: void | number) => void;
14
- onCommitFiberUnmount: (rendererID: number, root: unknown) => void;
15
- onPostCommitFiberRoot: (rendererID: number, root: unknown) => void;
16
- inject: (renderer: unknown) => number;
13
+ onCommitFiberRoot: (rendererID: number, root: FiberRoot, priority: void | number) => void;
14
+ onCommitFiberUnmount: (rendererID: number, fiber: Fiber) => void;
15
+ onPostCommitFiberRoot: (rendererID: number, root: FiberRoot) => void;
16
+ inject: (renderer: ReactRenderer) => number;
17
17
  _instrumentationSource?: string;
18
18
  _instrumentationIsActive?: boolean;
19
19
  }
20
+ declare const version: string | undefined;
21
+ declare const BIPPY_INSTRUMENTATION_STRING: string;
20
22
  declare const ClassComponentTag = 1;
21
23
  declare const FunctionComponentTag = 0;
22
24
  declare const ContextConsumerTag = 9;
@@ -50,7 +52,15 @@ declare const Snapshot = 1024;
50
52
  declare const Visibility = 8192;
51
53
  declare const MutationMask: number;
52
54
  declare const isValidElement: (element: unknown) => element is React.ReactElement;
55
+ /**
56
+ * Host fibers are DOM nodes in react-dom, `View` in react-native, etc.
57
+ */
53
58
  declare const isHostFiber: (fiber: Fiber) => boolean;
59
+ /**
60
+ * Composite fibers are functional, class components, etc.
61
+ *
62
+ * @see https://github.com/facebook/react/blob/865d2c418d5ba6fb4546e4b58616cd9b7701af85/packages/react/src/jsx/ReactJSXElement.js#L490
63
+ */
54
64
  declare const isCompositeFiber: (fiber: Fiber) => boolean;
55
65
  declare const traverseContexts: (fiber: Fiber, selector: (prevValue: {
56
66
  context: React.Context<unknown>;
@@ -64,8 +74,11 @@ declare const traverseState: (fiber: Fiber, selector: (prevValue: {
64
74
  }, nextValue: {
65
75
  memoizedState: unknown;
66
76
  }) => boolean | void) => boolean;
67
- declare const traverseProps: (fiber: Fiber, selector: (prevValue: unknown, nextValue: unknown) => boolean | void) => boolean;
77
+ declare const traverseProps: (fiber: Fiber, selector: (propName: string, nextValue: unknown, prevValue: unknown) => boolean | void) => boolean;
68
78
  declare const didFiberRender: (fiber: Fiber) => boolean;
79
+ /**
80
+ * A commit is means reconciliation occured and the host tree is updated
81
+ */
69
82
  declare const didFiberCommit: (fiber: Fiber) => boolean;
70
83
  declare const getMutatedHostFibers: (fiber: Fiber) => Array<Fiber>;
71
84
  declare const getFiberStack: (fiber: Fiber) => any[];
@@ -81,6 +94,7 @@ declare const getType: (type: any) => any;
81
94
  declare const getDisplayName: (type: any) => string | null;
82
95
  declare const isUsingRDT: () => boolean;
83
96
  declare const detectReactBuildType: (renderer: ReactRenderer) => "development" | "production";
97
+ declare const installRDTHook: (onActive?: () => unknown) => ReactDevToolsGlobalHook;
84
98
  declare const getRDTHook: (onActive?: () => unknown) => ReactDevToolsGlobalHook;
85
99
  declare const isInstrumentationActive: () => boolean;
86
100
  type RenderHandler = <S>(fiber: Fiber, phase: 'mount' | 'update' | 'unmount', state?: S) => unknown;
@@ -88,16 +102,39 @@ declare const mountFiberRecursively: (onRender: RenderHandler, firstChild: Fiber
88
102
  declare const updateFiberRecursively: (onRender: RenderHandler, nextFiber: Fiber, prevFiber: Fiber, parentFiber: Fiber | null) => void;
89
103
  declare const unmountFiber: (onRender: RenderHandler, fiber: Fiber) => void;
90
104
  declare const unmountFiberChildrenRecursively: (onRender: RenderHandler, fiber: Fiber) => void;
105
+ /**
106
+ * Creates a fiber visitor function.
107
+ * @param options { onRender, onError }
108
+ * @example
109
+ * const visitor = createFiberVisitor({
110
+ * onRender(fiber, phase) {
111
+ * console.log(phase)
112
+ * },
113
+ * });
114
+ */
91
115
  declare const createFiberVisitor: ({ onRender: onRenderWithoutState, onError, }: {
92
116
  onRender: RenderHandler;
93
117
  onError?: (error: unknown) => unknown;
94
118
  }) => <S>(_rendererID: number, root: FiberRoot, state?: S) => void;
119
+ /**
120
+ * Instruments the DevTools hook.
121
+ * @param options { onCommitFiberRoot, onCommitFiberUnmount, onPostCommitFiberRoot, onActive, name }
122
+ * @example
123
+ * const hook = instrument({
124
+ * onActive() {
125
+ * console.log('initialized');
126
+ * },
127
+ * onCommitFiberRoot(rendererID, root) {
128
+ * console.log('fiberRoot', root.current)
129
+ * },
130
+ * });
131
+ */
95
132
  declare const instrument: ({ onCommitFiberRoot, onCommitFiberUnmount, onPostCommitFiberRoot, onActive, name, }: {
96
133
  onCommitFiberRoot?: (rendererID: number, root: FiberRoot, priority: void | number) => unknown;
97
- onCommitFiberUnmount?: (rendererID: number, root: FiberRoot) => unknown;
134
+ onCommitFiberUnmount?: (rendererID: number, fiber: Fiber) => unknown;
98
135
  onPostCommitFiberRoot?: (rendererID: number, root: FiberRoot) => unknown;
99
136
  onActive?: () => unknown;
100
137
  name?: string;
101
138
  }) => ReactDevToolsGlobalHook;
102
139
 
103
- export { CONCURRENT_MODE_NUMBER, CONCURRENT_MODE_SYMBOL_STRING, ChildDeletion, ClassComponentTag, Cloned, ContentReset, ContextConsumerTag, DEPRECATED_ASYNC_MODE_SYMBOL_STRING, DehydratedSuspenseComponent, DidCapture, ForwardRefTag, Fragment, FunctionComponentTag, HostComponentTag, HostHoistableTag, HostRoot, HostSingletonTag, HostText, Hydrating, LegacyHiddenComponent, MemoComponentTag, MutationMask, OffscreenComponent, OffscreenComponentTag, PerformedWork, Placement, type ReactDevToolsGlobalHook, type ReactRenderer, Ref, SimpleMemoComponentTag, Snapshot, SuspenseComponentTag, Update, Visibility, createFiberVisitor, detectReactBuildType, didFiberCommit, didFiberRender, getDisplayName, getFiberStack, getMutatedHostFibers, getNearestHostFiber, getRDTHook, getTimings, getType, hasMemoCache, instrument, isCompositeFiber, isHostFiber, isInstrumentationActive, isUsingRDT, isValidElement, mountFiberRecursively, shouldFilterFiber, traverseContexts, traverseFiber, traverseProps, traverseState, unmountFiber, unmountFiberChildrenRecursively, updateFiberRecursively };
140
+ export { BIPPY_INSTRUMENTATION_STRING, CONCURRENT_MODE_NUMBER, CONCURRENT_MODE_SYMBOL_STRING, ChildDeletion, ClassComponentTag, Cloned, ContentReset, ContextConsumerTag, DEPRECATED_ASYNC_MODE_SYMBOL_STRING, DehydratedSuspenseComponent, DidCapture, ForwardRefTag, Fragment, FunctionComponentTag, HostComponentTag, HostHoistableTag, HostRoot, HostSingletonTag, HostText, Hydrating, LegacyHiddenComponent, MemoComponentTag, MutationMask, OffscreenComponent, OffscreenComponentTag, PerformedWork, Placement, type ReactDevToolsGlobalHook, type ReactRenderer, Ref, SimpleMemoComponentTag, Snapshot, SuspenseComponentTag, Update, Visibility, createFiberVisitor, detectReactBuildType, didFiberCommit, didFiberRender, getDisplayName, getFiberStack, getMutatedHostFibers, getNearestHostFiber, getRDTHook, getTimings, getType, hasMemoCache, installRDTHook, instrument, isCompositeFiber, isHostFiber, isInstrumentationActive, isUsingRDT, isValidElement, mountFiberRecursively, shouldFilterFiber, traverseContexts, traverseFiber, traverseProps, traverseState, unmountFiber, unmountFiberChildrenRecursively, updateFiberRecursively, version };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { Fiber, FiberRoot } from 'react-reconciler';
2
+ import { FiberRoot, Fiber } from 'react-reconciler';
3
3
 
4
4
  interface ReactRenderer {
5
5
  version: string;
@@ -10,13 +10,15 @@ interface ReactDevToolsGlobalHook {
10
10
  supportsFiber: boolean;
11
11
  supportsFlight: boolean;
12
12
  renderers: Map<number, ReactRenderer>;
13
- onCommitFiberRoot: (rendererID: number, root: unknown, priority: void | number) => void;
14
- onCommitFiberUnmount: (rendererID: number, root: unknown) => void;
15
- onPostCommitFiberRoot: (rendererID: number, root: unknown) => void;
16
- inject: (renderer: unknown) => number;
13
+ onCommitFiberRoot: (rendererID: number, root: FiberRoot, priority: void | number) => void;
14
+ onCommitFiberUnmount: (rendererID: number, fiber: Fiber) => void;
15
+ onPostCommitFiberRoot: (rendererID: number, root: FiberRoot) => void;
16
+ inject: (renderer: ReactRenderer) => number;
17
17
  _instrumentationSource?: string;
18
18
  _instrumentationIsActive?: boolean;
19
19
  }
20
+ declare const version: string | undefined;
21
+ declare const BIPPY_INSTRUMENTATION_STRING: string;
20
22
  declare const ClassComponentTag = 1;
21
23
  declare const FunctionComponentTag = 0;
22
24
  declare const ContextConsumerTag = 9;
@@ -50,7 +52,15 @@ declare const Snapshot = 1024;
50
52
  declare const Visibility = 8192;
51
53
  declare const MutationMask: number;
52
54
  declare const isValidElement: (element: unknown) => element is React.ReactElement;
55
+ /**
56
+ * Host fibers are DOM nodes in react-dom, `View` in react-native, etc.
57
+ */
53
58
  declare const isHostFiber: (fiber: Fiber) => boolean;
59
+ /**
60
+ * Composite fibers are functional, class components, etc.
61
+ *
62
+ * @see https://github.com/facebook/react/blob/865d2c418d5ba6fb4546e4b58616cd9b7701af85/packages/react/src/jsx/ReactJSXElement.js#L490
63
+ */
54
64
  declare const isCompositeFiber: (fiber: Fiber) => boolean;
55
65
  declare const traverseContexts: (fiber: Fiber, selector: (prevValue: {
56
66
  context: React.Context<unknown>;
@@ -64,8 +74,11 @@ declare const traverseState: (fiber: Fiber, selector: (prevValue: {
64
74
  }, nextValue: {
65
75
  memoizedState: unknown;
66
76
  }) => boolean | void) => boolean;
67
- declare const traverseProps: (fiber: Fiber, selector: (prevValue: unknown, nextValue: unknown) => boolean | void) => boolean;
77
+ declare const traverseProps: (fiber: Fiber, selector: (propName: string, nextValue: unknown, prevValue: unknown) => boolean | void) => boolean;
68
78
  declare const didFiberRender: (fiber: Fiber) => boolean;
79
+ /**
80
+ * A commit is means reconciliation occured and the host tree is updated
81
+ */
69
82
  declare const didFiberCommit: (fiber: Fiber) => boolean;
70
83
  declare const getMutatedHostFibers: (fiber: Fiber) => Array<Fiber>;
71
84
  declare const getFiberStack: (fiber: Fiber) => any[];
@@ -81,6 +94,7 @@ declare const getType: (type: any) => any;
81
94
  declare const getDisplayName: (type: any) => string | null;
82
95
  declare const isUsingRDT: () => boolean;
83
96
  declare const detectReactBuildType: (renderer: ReactRenderer) => "development" | "production";
97
+ declare const installRDTHook: (onActive?: () => unknown) => ReactDevToolsGlobalHook;
84
98
  declare const getRDTHook: (onActive?: () => unknown) => ReactDevToolsGlobalHook;
85
99
  declare const isInstrumentationActive: () => boolean;
86
100
  type RenderHandler = <S>(fiber: Fiber, phase: 'mount' | 'update' | 'unmount', state?: S) => unknown;
@@ -88,16 +102,39 @@ declare const mountFiberRecursively: (onRender: RenderHandler, firstChild: Fiber
88
102
  declare const updateFiberRecursively: (onRender: RenderHandler, nextFiber: Fiber, prevFiber: Fiber, parentFiber: Fiber | null) => void;
89
103
  declare const unmountFiber: (onRender: RenderHandler, fiber: Fiber) => void;
90
104
  declare const unmountFiberChildrenRecursively: (onRender: RenderHandler, fiber: Fiber) => void;
105
+ /**
106
+ * Creates a fiber visitor function.
107
+ * @param options { onRender, onError }
108
+ * @example
109
+ * const visitor = createFiberVisitor({
110
+ * onRender(fiber, phase) {
111
+ * console.log(phase)
112
+ * },
113
+ * });
114
+ */
91
115
  declare const createFiberVisitor: ({ onRender: onRenderWithoutState, onError, }: {
92
116
  onRender: RenderHandler;
93
117
  onError?: (error: unknown) => unknown;
94
118
  }) => <S>(_rendererID: number, root: FiberRoot, state?: S) => void;
119
+ /**
120
+ * Instruments the DevTools hook.
121
+ * @param options { onCommitFiberRoot, onCommitFiberUnmount, onPostCommitFiberRoot, onActive, name }
122
+ * @example
123
+ * const hook = instrument({
124
+ * onActive() {
125
+ * console.log('initialized');
126
+ * },
127
+ * onCommitFiberRoot(rendererID, root) {
128
+ * console.log('fiberRoot', root.current)
129
+ * },
130
+ * });
131
+ */
95
132
  declare const instrument: ({ onCommitFiberRoot, onCommitFiberUnmount, onPostCommitFiberRoot, onActive, name, }: {
96
133
  onCommitFiberRoot?: (rendererID: number, root: FiberRoot, priority: void | number) => unknown;
97
- onCommitFiberUnmount?: (rendererID: number, root: FiberRoot) => unknown;
134
+ onCommitFiberUnmount?: (rendererID: number, fiber: Fiber) => unknown;
98
135
  onPostCommitFiberRoot?: (rendererID: number, root: FiberRoot) => unknown;
99
136
  onActive?: () => unknown;
100
137
  name?: string;
101
138
  }) => ReactDevToolsGlobalHook;
102
139
 
103
- export { CONCURRENT_MODE_NUMBER, CONCURRENT_MODE_SYMBOL_STRING, ChildDeletion, ClassComponentTag, Cloned, ContentReset, ContextConsumerTag, DEPRECATED_ASYNC_MODE_SYMBOL_STRING, DehydratedSuspenseComponent, DidCapture, ForwardRefTag, Fragment, FunctionComponentTag, HostComponentTag, HostHoistableTag, HostRoot, HostSingletonTag, HostText, Hydrating, LegacyHiddenComponent, MemoComponentTag, MutationMask, OffscreenComponent, OffscreenComponentTag, PerformedWork, Placement, type ReactDevToolsGlobalHook, type ReactRenderer, Ref, SimpleMemoComponentTag, Snapshot, SuspenseComponentTag, Update, Visibility, createFiberVisitor, detectReactBuildType, didFiberCommit, didFiberRender, getDisplayName, getFiberStack, getMutatedHostFibers, getNearestHostFiber, getRDTHook, getTimings, getType, hasMemoCache, instrument, isCompositeFiber, isHostFiber, isInstrumentationActive, isUsingRDT, isValidElement, mountFiberRecursively, shouldFilterFiber, traverseContexts, traverseFiber, traverseProps, traverseState, unmountFiber, unmountFiberChildrenRecursively, updateFiberRecursively };
140
+ export { BIPPY_INSTRUMENTATION_STRING, CONCURRENT_MODE_NUMBER, CONCURRENT_MODE_SYMBOL_STRING, ChildDeletion, ClassComponentTag, Cloned, ContentReset, ContextConsumerTag, DEPRECATED_ASYNC_MODE_SYMBOL_STRING, DehydratedSuspenseComponent, DidCapture, ForwardRefTag, Fragment, FunctionComponentTag, HostComponentTag, HostHoistableTag, HostRoot, HostSingletonTag, HostText, Hydrating, LegacyHiddenComponent, MemoComponentTag, MutationMask, OffscreenComponent, OffscreenComponentTag, PerformedWork, Placement, type ReactDevToolsGlobalHook, type ReactRenderer, Ref, SimpleMemoComponentTag, Snapshot, SuspenseComponentTag, Update, Visibility, createFiberVisitor, detectReactBuildType, didFiberCommit, didFiberRender, getDisplayName, getFiberStack, getMutatedHostFibers, getNearestHostFiber, getRDTHook, getTimings, getType, hasMemoCache, installRDTHook, instrument, isCompositeFiber, isHostFiber, isInstrumentationActive, isUsingRDT, isValidElement, mountFiberRecursively, shouldFilterFiber, traverseContexts, traverseFiber, traverseProps, traverseState, unmountFiber, unmountFiberChildrenRecursively, updateFiberRecursively, version };
@@ -6,5 +6,5 @@ var Bippy=(function(exports){'use strict';/**
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
- var D=1,H=0,E=9,P=13,M=22,z=11,I=14,A=15,N=5,V=26,U=27,j=18,L=6,B=7,G=23,W=22,$=3,K=60111,R="Symbol(react.concurrent_mode)",_="Symbol(react.async_mode)",Y=1,J=2,Q=128,q=4096,X=4,Z=8,ee=16,te=32,ne=512,oe=1024,re=8192,ie=13366,le=e=>typeof e=="object"&&e!=null&&"$$typeof"in e&&["Symbol(react.element)","Symbol(react.transitional.element)"].includes(String(e.$$typeof)),d=e=>e.tag===5||e.tag===26||e.tag===27,se=e=>e.tag===0||e.tag===1||e.tag===15||e.tag===14||e.tag===11,ae=(e,t)=>{try{let o=e.dependencies,n=e.alternate?.dependencies;if(!o||!n||typeof o!="object"||!("firstContext"in o)||typeof n!="object"||!("firstContext"in n))return !1;let l=o.firstContext,r=n.firstContext;for(;l&&typeof l=="object"&&"memoizedValue"in l&&r&&typeof r=="object"&&"memoizedValue"in r;){if(t(l,r)===!0)return !0;l=l.next,r=r.next;}}catch{}return !1},ce=(e,t)=>{try{let o=e.memoizedState,n=e.alternate?.memoizedState;for(;o&&n;){if(t(o,n)===!0)return !0;o=o.next,n=n.next;}}catch{}return !1},ue=(e,t)=>{try{let o=e.memoizedProps,n=e.alternate?.memoizedProps||{},l=new Set([...Object.keys(n),...Object.keys(o)]);for(let r of l){let s=n?.[r],c=o?.[r];if(t(s,c)===!0)return !0}}catch{}return !1},C=e=>{let t=e.memoizedProps,o=e.alternate?.memoizedProps||{},n=e.flags??e.effectTag??0;switch(e.tag){case 1:case 0:case 9:case 11:case 14:case 15:return (n&1)===1;default:return e.alternate?o!==t||e.alternate.memoizedState!==e.memoizedState||e.alternate.ref!==e.ref:!0}},w=e=>!!(e.flags&22||e.subtreeFlags&22),me=e=>{let t=[],o=[e];for(;o.length;){let n=o.pop();n&&(d(n)&&w(n)&&C(n)&&t.push(n),n.child&&o.push(n.child),n.sibling&&o.push(n.sibling));}return t},pe=e=>{let t=[];for(;e.return;)t.push(e),e=e.return;let o=new Array(t.length);for(let n=0;n<t.length;n++)o[n]=t[t.length-n-1];return o},g=e=>{switch(e.tag){case 18:return !0;case 6:case 7:case 23:case 22:return !0;case 3:return !1;default:{let t=typeof e.type=="object"&&e.type!==null?e.type.$$typeof:e.type;switch(typeof t=="symbol"?t.toString():t){case 60111:case R:case _:return !0;default:return !1}}}},de=e=>{let t=b(e,d);return t||(t=b(e,d,!0)),t},b=(e,t,o=!1)=>{if(!e)return null;if(t(e)===!0)return e;let n=o?e.return:e.child;for(;n;){let l=b(n,t,o);if(l)return l;n=o?null:n.sibling;}return null},be=e=>{let t=e?.actualDuration??0,o=t,n=e?.child??null;for(;t>0&&n!=null;)o-=n.actualDuration??0,n=n.sibling;return {selfTime:o,totalTime:t}},fe=e=>!!e.updateQueue?.memoCache,y=e=>typeof e=="function"?e:typeof e=="object"&&e?y(e.type||e.render):null,Ce=e=>{if(typeof e!="function"&&!(typeof e=="object"&&e))return null;let t=e.displayName||e.name||null;return t||(e=y(e),e&&(e.displayName||e.name)||null)},k=()=>globalThis.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__!=null,ge=e=>{try{if(typeof e.version=="string"&&e.bundleType>0)return "development"}catch{}return "production"},v=e=>{try{Function.prototype.toString.call(e).indexOf("^_^")>-1&&setTimeout(()=>{throw new Error("React is running in production mode, but dead code elimination has not been applied. Read how to correctly configure React for production: https://reactjs.org/link/perf-use-production-build")});}catch{}},p=()=>{},h=e=>{let t=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;if(t&&e?.(),!window.hasOwnProperty("__REACT_DEVTOOLS_GLOBAL_HOOK__")){let o=new Map,n=0;t??={checkDCE:v,supportsFiber:!0,supportsFlight:!0,renderers:o,onCommitFiberRoot:p,onCommitFiberUnmount:p,onPostCommitFiberRoot:p,inject(l){let r=++n;return o.set(r,l),t._instrumentationIsActive||(t._instrumentationIsActive=!0,e?.()),r},_instrumentationSource:"bippy",_instrumentationIsActive:!!t};try{Object.defineProperty(globalThis,"__REACT_DEVTOOLS_GLOBAL_HOOK__",{configurable:!0,value:t});}catch{}}return t},he=()=>!!h()._instrumentationIsActive||k();typeof window<"u"&&(performance.now(),h());var m=(e,t,o)=>{let n=t;for(;n!=null;){if(!g(n)&&C(n)&&e(n,"mount"),n.tag===13)if(n.memoizedState!==null){let s=n.child,c=s?s.sibling:null;if(c){let i=c.child;i!==null&&m(e,i,!1);}}else {let s=null;n.child!==null&&(s=n.child.child),s!==null&&m(e,s,!1);}else n.child!=null&&m(e,n.child,!0);n=o?n.sibling:null;}},f=(e,t,o,n)=>{if(!o)return;let l=t.tag===13,r=!g(t);r&&C(t)&&e(t,"update");let s=l&&o.memoizedState!==null,c=l&&t.memoizedState!==null;if(s&&c){let i=t.child?.sibling??null,a=o.child?.sibling??null;i!==null&&a!==null&&f(e,i,a);}else if(s&&!c){let i=t.child;i!==null&&m(e,i,!0);}else if(!s&&c){T(e,o);let i=t.child?.sibling??null;i!==null&&m(e,i,!0);}else if(t.child!==o.child){let i=t.child;for(;i;){if(i.alternate){let a=i.alternate;f(e,i,a);}else m(e,i,!1);i=i.sibling;}}},S=(e,t)=>{(t.tag===3||!g(t))&&e(t,"unmount");},T=(e,t)=>{let o=t.tag===13&&t.memoizedState!==null,n=t.child;for(o&&(n=(t.child?.sibling??null)?.child??null);n!==null;)n.return!==null&&(S(e,n),T(e,n)),n=n.sibling;},O=0,F=new WeakMap,xe=({onRender:e,onError:t})=>(o,n,l)=>{let r=n.current,s=(a,u)=>e(a,u,l),c=F.get(n);c||(c={prevFiber:null,id:O++},F.set(n,c));let{prevFiber:i}=c;try{if(i!==null){let a=i&&i.memoizedState!=null&&i.memoizedState.element!=null&&i.memoizedState.isDehydrated!==!0,u=r.memoizedState!=null&&r.memoizedState.element!=null&&r.memoizedState.isDehydrated!==!0;!a&&u?m(s,r,!1):a&&u?f(s,r,r.alternate,null):a&&!u&&S(s,r);}else m(s,r,!1);}catch(a){if(t)t(a);else throw a}c.prevFiber=r;},Fe=({onCommitFiberRoot:e,onCommitFiberUnmount:t,onPostCommitFiberRoot:o,onActive:n,name:l})=>{let r=h(n);r._instrumentationSource=l??"bippy";let s=r.onCommitFiberRoot;e&&(r.onCommitFiberRoot=(a,u,x)=>{s&&s(a,u,x),e(a,u,x);});let c=r.onCommitFiberUnmount;t&&(r.onCommitFiberUnmount=(a,u)=>{c&&c(a,u),t(a,u);});let i=r.onPostCommitFiberRoot;return o&&(r.onPostCommitFiberRoot=(a,u)=>{i&&i(a,u);}),r};
10
- exports.CONCURRENT_MODE_NUMBER=K;exports.CONCURRENT_MODE_SYMBOL_STRING=R;exports.ChildDeletion=ee;exports.ClassComponentTag=D;exports.Cloned=Z;exports.ContentReset=te;exports.ContextConsumerTag=E;exports.DEPRECATED_ASYNC_MODE_SYMBOL_STRING=_;exports.DehydratedSuspenseComponent=j;exports.DidCapture=Q;exports.ForwardRefTag=z;exports.Fragment=B;exports.FunctionComponentTag=H;exports.HostComponentTag=N;exports.HostHoistableTag=V;exports.HostRoot=$;exports.HostSingletonTag=U;exports.HostText=L;exports.Hydrating=q;exports.LegacyHiddenComponent=G;exports.MemoComponentTag=I;exports.MutationMask=ie;exports.OffscreenComponent=W;exports.OffscreenComponentTag=M;exports.PerformedWork=Y;exports.Placement=J;exports.Ref=ne;exports.SimpleMemoComponentTag=A;exports.Snapshot=oe;exports.SuspenseComponentTag=P;exports.Update=X;exports.Visibility=re;exports.createFiberVisitor=xe;exports.detectReactBuildType=ge;exports.didFiberCommit=w;exports.didFiberRender=C;exports.getDisplayName=Ce;exports.getFiberStack=pe;exports.getMutatedHostFibers=me;exports.getNearestHostFiber=de;exports.getRDTHook=h;exports.getTimings=be;exports.getType=y;exports.hasMemoCache=fe;exports.instrument=Fe;exports.isCompositeFiber=se;exports.isHostFiber=d;exports.isInstrumentationActive=he;exports.isUsingRDT=k;exports.isValidElement=le;exports.mountFiberRecursively=m;exports.shouldFilterFiber=g;exports.traverseContexts=ae;exports.traverseFiber=b;exports.traverseProps=ue;exports.traverseState=ce;exports.unmountFiber=S;exports.unmountFiberChildrenRecursively=T;exports.updateFiberRecursively=f;return exports;})({});
9
+ var A="0.0.25",v=`bippy-${A}`,k=1,w=0,M=9,C=13,V=22,O=11,D=14,E=15,j=5,B=26,L=27,U=18,G=6,$=7,K=23,W=22,H=3,Y=60111,J="Symbol(react.concurrent_mode)",Q="Symbol(react.async_mode)",T=1,p=2,le=128,q=4096,b=4,ae=8,f=16,X=32,ce=512,Z=1024,ee=8192,ue=p|b|f|X|q|ee|Z,me=e=>typeof e=="object"&&e!=null&&"$$typeof"in e&&["Symbol(react.element)","Symbol(react.transitional.element)"].includes(String(e.$$typeof)),h=e=>e.tag===j||e.tag===B||e.tag===L,de=e=>e.tag===w||e.tag===k||e.tag===E||e.tag===D||e.tag===O,pe=(e,n)=>{try{let o=e.dependencies,t=e.alternate?.dependencies;if(!o||!t||typeof o!="object"||!("firstContext"in o)||typeof t!="object"||!("firstContext"in t))return !1;let s=o.firstContext,r=t.firstContext;for(;s&&typeof s=="object"&&"memoizedValue"in s&&r&&typeof r=="object"&&"memoizedValue"in r;){if(n(s,r)===!0)return !0;s=s.next,r=r.next;}}catch{}return !1},be=(e,n)=>{try{let o=e.memoizedState,t=e.alternate?.memoizedState;for(;o&&t;){if(n(o,t)===!0)return !0;o=o.next,t=t.next;}}catch{}return !1},fe=(e,n)=>{try{let o=e.memoizedProps,t=e.alternate?.memoizedProps||{},s=new Set([...Object.keys(t),...Object.keys(o)]);for(let r of s){let l=t?.[r],c=o?.[r];if(n(r,c,l)===!0)return !0}}catch{}return !1},y=e=>{let n=e.memoizedProps,o=e.alternate?.memoizedProps||{},t=e.flags??e.effectTag??0;switch(e.tag){case k:case w:case M:case O:case D:case E:return (t&T)===T;default:return e.alternate?o!==n||e.alternate.memoizedState!==e.memoizedState||e.alternate.ref!==e.ref:!0}},te=e=>!!(e.flags&(b|p|f)||e.subtreeFlags&(b|p|f)),he=e=>{let n=[],o=[e];for(;o.length;){let t=o.pop();t&&(h(t)&&te(t)&&y(t)&&n.push(t),t.child&&o.push(t.child),t.sibling&&o.push(t.sibling));}return n},xe=e=>{let n=[];for(;e.return;)n.push(e),e=e.return;let o=new Array(n.length);for(let t=0;t<n.length;t++)o[t]=n[n.length-t-1];return o},R=e=>{switch(e.tag){case U:return !0;case G:case $:case K:case W:return !0;case H:return !1;default:{let n=typeof e.type=="object"&&e.type!==null?e.type.$$typeof:e.type;switch(typeof n=="symbol"?n.toString():n){case Y:case J:case Q:return !0;default:return !1}}}},Fe=e=>{let n=x(e,h);return n||(n=x(e,h,!0)),n},x=(e,n,o=!1)=>{if(!e)return null;if(n(e)===!0)return e;let t=o?e.return:e.child;for(;t;){let s=x(t,n,o);if(s)return s;t=o?null:t.sibling;}return null},ge=e=>{let n=e?.actualDuration??0,o=n,t=e?.child??null;for(;n>0&&t!=null;)o-=t.actualDuration??0,t=t.sibling;return {selfTime:o,totalTime:n}},Ce=e=>!!e.updateQueue?.memoCache,I=e=>typeof e=="function"?e:typeof e=="object"&&e?I(e.type||e.render):null,ye=e=>{if(typeof e!="function"&&!(typeof e=="object"&&e))return null;let n=e.displayName||e.name||null;return n||(e=I(e),e&&(e.displayName||e.name)||null)},ne=()=>globalThis.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__!=null,Re=e=>{try{if(typeof e.version=="string"&&e.bundleType>0)return "development"}catch{}return "production"},oe=e=>{try{Function.prototype.toString.call(e).indexOf("^_^")>-1&&setTimeout(()=>{throw new Error("React is running in production mode, but dead code elimination has not been applied. Read how to correctly configure React for production: https://reactjs.org/link/perf-use-production-build")});}catch{}},d=()=>{},N=e=>{let n=new Map,o=0,t={checkDCE:oe,supportsFiber:!0,supportsFlight:!0,renderers:n,onCommitFiberRoot:d,onCommitFiberUnmount:d,onPostCommitFiberRoot:d,inject(s){let r=++o;return n.set(r,s),t._instrumentationIsActive||(t._instrumentationIsActive=!0,e?.()),r},_instrumentationSource:v,_instrumentationIsActive:!1};try{Object.defineProperty(globalThis,"__REACT_DEVTOOLS_GLOBAL_HOOK__",{configurable:!0,value:t});}catch{}return t},z=e=>{let n=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;return n&&e?.(),window.hasOwnProperty("__REACT_DEVTOOLS_GLOBAL_HOOK__")||(n=N(e)),n},Se=()=>!!z()._instrumentationIsActive||ne(),m=(e,n,o)=>{let t=n;for(;t!=null;){if(!R(t)&&y(t)&&e(t,"mount"),t.tag===C)if(t.memoizedState!==null){let l=t.child,c=l?l.sibling:null;if(c){let i=c.child;i!==null&&m(e,i,!1);}}else {let l=null;t.child!==null&&(l=t.child.child),l!==null&&m(e,l,!1);}else t.child!=null&&m(e,t.child,!0);t=o?t.sibling:null;}},F=(e,n,o,t)=>{if(!o)return;let s=n.tag===C,r=!R(n);r&&y(n)&&e(n,"update");let l=s&&o.memoizedState!==null,c=s&&n.memoizedState!==null;if(l&&c){let i=n.child?.sibling??null,a=o.child?.sibling??null;i!==null&&a!==null&&F(e,i,a);}else if(l&&!c){let i=n.child;i!==null&&m(e,i,!0);}else if(!l&&c){P(e,o);let i=n.child?.sibling??null;i!==null&&m(e,i,!0);}else if(n.child!==o.child){let i=n.child;for(;i;){if(i.alternate){let a=i.alternate;F(e,i,a);}else m(e,i,!1);i=i.sibling;}}},g=(e,n)=>{(n.tag===H||!R(n))&&e(n,"unmount");},P=(e,n)=>{let o=n.tag===C&&n.memoizedState!==null,t=n.child;for(o&&(t=(n.child?.sibling??null)?.child??null);t!==null;)t.return!==null&&(g(e,t),P(e,t)),t=t.sibling;},re=0,_=new WeakMap,Te=({onRender:e,onError:n})=>(o,t,s)=>{let r=t.current,l=(a,u)=>e(a,u,s),c=_.get(t);c||(c={prevFiber:null,id:re++},_.set(t,c));let{prevFiber:i}=c;try{if(!r)g(l,t);else if(i!==null){let a=i&&i.memoizedState!=null&&i.memoizedState.element!=null&&i.memoizedState.isDehydrated!==!0,u=r.memoizedState!=null&&r.memoizedState.element!=null&&r.memoizedState.isDehydrated!==!0;!a&&u?m(l,r,!1):a&&u?F(l,r,r.alternate,null):a&&!u&&g(l,r);}else m(l,r,!1);}catch(a){if(n)n(a);else throw a}c.prevFiber=r;},_e=({onCommitFiberRoot:e,onCommitFiberUnmount:n,onPostCommitFiberRoot:o,onActive:t,name:s})=>{let r=z(t);r._instrumentationSource=s??v;let l=r.onCommitFiberRoot;e&&(r.onCommitFiberRoot=(a,u,S)=>{l&&l(a,u,S),e(a,u,S);});let c=r.onCommitFiberUnmount;n&&(r.onCommitFiberUnmount=(a,u)=>{c&&c(a,u),n(a,u);});let i=r.onPostCommitFiberRoot;return o&&(r.onPostCommitFiberRoot=(a,u)=>{i&&i(a,u);}),r},ie=typeof document<"u"&&typeof document.createElement=="function",se=typeof process<"u"&&process.versions!=null&&process.versions.node!=null;(ie||!se)&&N();
10
+ exports.BIPPY_INSTRUMENTATION_STRING=v;exports.CONCURRENT_MODE_NUMBER=Y;exports.CONCURRENT_MODE_SYMBOL_STRING=J;exports.ChildDeletion=f;exports.ClassComponentTag=k;exports.Cloned=ae;exports.ContentReset=X;exports.ContextConsumerTag=M;exports.DEPRECATED_ASYNC_MODE_SYMBOL_STRING=Q;exports.DehydratedSuspenseComponent=U;exports.DidCapture=le;exports.ForwardRefTag=O;exports.Fragment=$;exports.FunctionComponentTag=w;exports.HostComponentTag=j;exports.HostHoistableTag=B;exports.HostRoot=H;exports.HostSingletonTag=L;exports.HostText=G;exports.Hydrating=q;exports.LegacyHiddenComponent=K;exports.MemoComponentTag=D;exports.MutationMask=ue;exports.OffscreenComponent=W;exports.OffscreenComponentTag=V;exports.PerformedWork=T;exports.Placement=p;exports.Ref=ce;exports.SimpleMemoComponentTag=E;exports.Snapshot=Z;exports.SuspenseComponentTag=C;exports.Update=b;exports.Visibility=ee;exports.createFiberVisitor=Te;exports.detectReactBuildType=Re;exports.didFiberCommit=te;exports.didFiberRender=y;exports.getDisplayName=ye;exports.getFiberStack=xe;exports.getMutatedHostFibers=he;exports.getNearestHostFiber=Fe;exports.getRDTHook=z;exports.getTimings=ge;exports.getType=I;exports.hasMemoCache=Ce;exports.installRDTHook=N;exports.instrument=_e;exports.isCompositeFiber=de;exports.isHostFiber=h;exports.isInstrumentationActive=Se;exports.isUsingRDT=ne;exports.isValidElement=me;exports.mountFiberRecursively=m;exports.shouldFilterFiber=R;exports.traverseContexts=pe;exports.traverseFiber=x;exports.traverseProps=fe;exports.traverseState=be;exports.unmountFiber=g;exports.unmountFiberChildrenRecursively=P;exports.updateFiberRecursively=F;exports.version=A;return exports;})({});
package/dist/index.js CHANGED
@@ -8,6 +8,8 @@
8
8
  */
9
9
 
10
10
  // src/index.ts
11
+ var version = "0.0.25";
12
+ var BIPPY_INSTRUMENTATION_STRING = `bippy-${version}`;
11
13
  var ClassComponentTag = 1;
12
14
  var FunctionComponentTag = 0;
13
15
  var ContextConsumerTag = 9;
@@ -92,7 +94,7 @@ var traverseProps = (fiber, selector) => {
92
94
  for (const propName of allKeys) {
93
95
  const prevValue = prevProps?.[propName];
94
96
  const nextValue = nextProps?.[propName];
95
- if (selector(prevValue, nextValue) === true) return true;
97
+ if (selector(propName, nextValue, prevValue) === true) return true;
96
98
  }
97
99
  } catch {
98
100
  }
@@ -247,39 +249,43 @@ var checkDCE = (fn) => {
247
249
  };
248
250
  var NO_OP = () => {
249
251
  };
252
+ var installRDTHook = (onActive) => {
253
+ const renderers = /* @__PURE__ */ new Map();
254
+ let i = 0;
255
+ const rdtHook = {
256
+ checkDCE,
257
+ supportsFiber: true,
258
+ supportsFlight: true,
259
+ renderers,
260
+ onCommitFiberRoot: NO_OP,
261
+ onCommitFiberUnmount: NO_OP,
262
+ onPostCommitFiberRoot: NO_OP,
263
+ inject(renderer) {
264
+ const nextID = ++i;
265
+ renderers.set(nextID, renderer);
266
+ if (!rdtHook._instrumentationIsActive) {
267
+ rdtHook._instrumentationIsActive = true;
268
+ onActive?.();
269
+ }
270
+ return nextID;
271
+ },
272
+ _instrumentationSource: BIPPY_INSTRUMENTATION_STRING,
273
+ _instrumentationIsActive: false
274
+ };
275
+ try {
276
+ Object.defineProperty(globalThis, "__REACT_DEVTOOLS_GLOBAL_HOOK__", {
277
+ configurable: true,
278
+ value: rdtHook
279
+ });
280
+ } catch {
281
+ }
282
+ return rdtHook;
283
+ };
250
284
  var getRDTHook = (onActive) => {
251
285
  let rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
252
286
  if (rdtHook) onActive?.();
253
287
  if (!window.hasOwnProperty("__REACT_DEVTOOLS_GLOBAL_HOOK__")) {
254
- const renderers = /* @__PURE__ */ new Map();
255
- let i = 0;
256
- rdtHook ??= {
257
- checkDCE,
258
- supportsFiber: true,
259
- supportsFlight: true,
260
- renderers,
261
- onCommitFiberRoot: NO_OP,
262
- onCommitFiberUnmount: NO_OP,
263
- onPostCommitFiberRoot: NO_OP,
264
- inject(renderer) {
265
- const nextID = ++i;
266
- renderers.set(nextID, renderer);
267
- if (!rdtHook._instrumentationIsActive) {
268
- rdtHook._instrumentationIsActive = true;
269
- onActive?.();
270
- }
271
- return nextID;
272
- },
273
- _instrumentationSource: "bippy",
274
- _instrumentationIsActive: Boolean(rdtHook)
275
- };
276
- try {
277
- Object.defineProperty(globalThis, "__REACT_DEVTOOLS_GLOBAL_HOOK__", {
278
- configurable: true,
279
- value: rdtHook
280
- });
281
- } catch {
282
- }
288
+ rdtHook = installRDTHook(onActive);
283
289
  }
284
290
  return rdtHook;
285
291
  };
@@ -287,10 +293,6 @@ var isInstrumentationActive = () => {
287
293
  const rdtHook = getRDTHook();
288
294
  return Boolean(rdtHook._instrumentationIsActive) || isUsingRDT();
289
295
  };
290
- if (typeof window !== "undefined") {
291
- performance.now();
292
- getRDTHook();
293
- }
294
296
  var mountFiberRecursively = (onRender, firstChild, traverseSiblings) => {
295
297
  let fiber = firstChild;
296
298
  while (fiber != null) {
@@ -407,7 +409,9 @@ var createFiberVisitor = ({
407
409
  }
408
410
  const { prevFiber } = rootInstance;
409
411
  try {
410
- if (prevFiber !== null) {
412
+ if (!rootFiber) {
413
+ unmountFiber(onRender, root);
414
+ } else if (prevFiber !== null) {
411
415
  const wasMounted = prevFiber && prevFiber.memoizedState != null && prevFiber.memoizedState.element != null && // A dehydrated root is not considered mounted
412
416
  prevFiber.memoizedState.isDehydrated !== true;
413
417
  const isMounted = rootFiber.memoizedState != null && rootFiber.memoizedState.element != null && // A dehydrated root is not considered mounted
@@ -445,7 +449,7 @@ var instrument = ({
445
449
  name
446
450
  }) => {
447
451
  const devtoolsHook = getRDTHook(onActive);
448
- devtoolsHook._instrumentationSource = name ?? "bippy";
452
+ devtoolsHook._instrumentationSource = name ?? BIPPY_INSTRUMENTATION_STRING;
449
453
  const prevOnCommitFiberRoot = devtoolsHook.onCommitFiberRoot;
450
454
  if (onCommitFiberRoot) {
451
455
  devtoolsHook.onCommitFiberRoot = (rendererID, root, priority) => {
@@ -471,5 +475,10 @@ var instrument = ({
471
475
  }
472
476
  return devtoolsHook;
473
477
  };
478
+ var isBrowser = typeof document !== "undefined" && typeof document.createElement === "function";
479
+ var isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
480
+ if (isBrowser || !isNode) {
481
+ installRDTHook();
482
+ }
474
483
 
475
- export { CONCURRENT_MODE_NUMBER, CONCURRENT_MODE_SYMBOL_STRING, ChildDeletion, ClassComponentTag, Cloned, ContentReset, ContextConsumerTag, DEPRECATED_ASYNC_MODE_SYMBOL_STRING, DehydratedSuspenseComponent, DidCapture, ForwardRefTag, Fragment, FunctionComponentTag, HostComponentTag, HostHoistableTag, HostRoot, HostSingletonTag, HostText, Hydrating, LegacyHiddenComponent, MemoComponentTag, MutationMask, OffscreenComponent, OffscreenComponentTag, PerformedWork, Placement, Ref, SimpleMemoComponentTag, Snapshot, SuspenseComponentTag, Update, Visibility, createFiberVisitor, detectReactBuildType, didFiberCommit, didFiberRender, getDisplayName, getFiberStack, getMutatedHostFibers, getNearestHostFiber, getRDTHook, getTimings, getType, hasMemoCache, instrument, isCompositeFiber, isHostFiber, isInstrumentationActive, isUsingRDT, isValidElement, mountFiberRecursively, shouldFilterFiber, traverseContexts, traverseFiber, traverseProps, traverseState, unmountFiber, unmountFiberChildrenRecursively, updateFiberRecursively };
484
+ export { BIPPY_INSTRUMENTATION_STRING, CONCURRENT_MODE_NUMBER, CONCURRENT_MODE_SYMBOL_STRING, ChildDeletion, ClassComponentTag, Cloned, ContentReset, ContextConsumerTag, DEPRECATED_ASYNC_MODE_SYMBOL_STRING, DehydratedSuspenseComponent, DidCapture, ForwardRefTag, Fragment, FunctionComponentTag, HostComponentTag, HostHoistableTag, HostRoot, HostSingletonTag, HostText, Hydrating, LegacyHiddenComponent, MemoComponentTag, MutationMask, OffscreenComponent, OffscreenComponentTag, PerformedWork, Placement, Ref, SimpleMemoComponentTag, Snapshot, SuspenseComponentTag, Update, Visibility, createFiberVisitor, detectReactBuildType, didFiberCommit, didFiberRender, getDisplayName, getFiberStack, getMutatedHostFibers, getNearestHostFiber, getRDTHook, getTimings, getType, hasMemoCache, installRDTHook, instrument, isCompositeFiber, isHostFiber, isInstrumentationActive, isUsingRDT, isValidElement, mountFiberRecursively, shouldFilterFiber, traverseContexts, traverseFiber, traverseProps, traverseState, unmountFiber, unmountFiberChildrenRecursively, updateFiberRecursively, version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bippy",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "description": "a hacky way to get fibers from react",
5
5
  "keywords": [
6
6
  "react",