bippy 0.7.3-dev.ebf9a42 → 0.7.3-dev.fbafc40

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/src/core.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // React must remain a type-only import because this module loads immediately after the DevTools hook.
2
2
  import type * as React from "react";
3
- import { callListener } from "./call-listener.js";
3
+ import { callListener } from "./call-listener";
4
4
 
5
5
  import type {
6
6
  Fiber,
@@ -8,7 +8,7 @@ import type {
8
8
  HostFiber,
9
9
  ReactDevToolsGlobalHook,
10
10
  ReactRenderer,
11
- } from "./react-internals/index.js";
11
+ } from "./react-internals/index";
12
12
 
13
13
  import {
14
14
  BIPPY_INSTRUMENTATION_STRING,
@@ -20,16 +20,16 @@ import {
20
20
  isRealReactDevtools,
21
21
  onRDTHookReplace,
22
22
  removeActiveListener,
23
- } from "./rdt-hook.js";
24
- import type { ReactDevToolsTarget, Unsubscribe } from "./rdt-hook.js";
25
- import { getCurrentFiberFromRoot } from "./react-internals/current-fiber.js";
23
+ } from "./rdt-hook";
24
+ import type { ReactDevToolsTarget, Unsubscribe } from "./rdt-hook";
25
+ import { getCurrentFiberFromRoot } from "./react-internals/current-fiber";
26
26
  import {
27
27
  getReactWorkTagsForFiber,
28
28
  ReactBuildType,
29
29
  ReactFiberFlags,
30
30
  ReactSymbols,
31
31
  setReactWorkTagsForFiber,
32
- } from "./react-internals/index.js";
32
+ } from "./react-internals/index";
33
33
 
34
34
  export {
35
35
  BippyError,
@@ -37,7 +37,7 @@ export {
37
37
  BippyHookRenderError,
38
38
  BippySourceMapError,
39
39
  BippyUnsupportedHookError,
40
- } from "./errors.js";
40
+ } from "./errors";
41
41
 
42
42
  const isComponentType = (value: unknown): value is React.ComponentType<unknown> =>
43
43
  typeof value === "function";
@@ -248,8 +248,15 @@ export const traverseFiber = ((
248
248
  while (currentFiber) {
249
249
  const selectedFiber = currentFiber;
250
250
  const selection = selector(selectedFiber);
251
- if (isPromiseLike<boolean | void>(selection)) {
252
- return Promise.resolve(selection).then((didSelectFiber) =>
251
+ const then =
252
+ selection !== null && (typeof selection === "object" || typeof selection === "function")
253
+ ? selection.then
254
+ : null;
255
+ if (typeof then === "function") {
256
+ return Promise.resolve<boolean | void>({
257
+ // oxlint-disable-next-line unicorn/no-thenable -- Assimilate the captured method without reading the accessor twice.
258
+ then: (resolve, reject) => Reflect.apply(then, selection, [resolve, reject]),
259
+ }).then((didSelectFiber) =>
253
260
  didSelectFiber === true ? selectedFiber : visit(getNextFiber(selectedFiber)),
254
261
  );
255
262
  }
@@ -261,12 +268,6 @@ export const traverseFiber = ((
261
268
  return visit(fiber);
262
269
  }) as TraverseFiber;
263
270
 
264
- const isPromiseLike = <Result>(value: unknown): value is PromiseLike<Result> =>
265
- (typeof value === "object" || typeof value === "function") &&
266
- value !== null &&
267
- "then" in value &&
268
- typeof value.then === "function";
269
-
270
271
  /**
271
272
  * Returns `true` if the {@link Fiber} uses React Compiler's memo cache.
272
273
  */
@@ -330,6 +331,7 @@ export const isInstrumentationActive = (target: ReactDevToolsTarget = globalThis
330
331
  export const _fiberRoots = new Set<FiberRoot>();
331
332
  const rootRendererIds = new WeakMap<FiberRoot, number>();
332
333
  const rootHooks = new WeakMap<FiberRoot, ReactDevToolsGlobalHook>();
334
+ let currentUnmountingFiber: Fiber | null = null;
333
335
 
334
336
  /**
335
337
  * Returns the latest fiber (since it may be double-buffered).
@@ -338,7 +340,9 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
338
340
  const alternate = fiber.alternate;
339
341
  if (!alternate) return fiber;
340
342
  const currentFiber = getCurrentFiberFromRoot(fiber);
341
- if (currentFiber) return currentFiber;
343
+ const isUnmountingPair = fiber === currentUnmountingFiber || alternate === currentUnmountingFiber;
344
+ if (currentFiber && (!isUnmountingPair || currentFiber === currentUnmountingFiber))
345
+ return currentFiber;
342
346
 
343
347
  let rootFiber = fiber;
344
348
  while (rootFiber.return) {
@@ -350,6 +354,7 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
350
354
  });
351
355
  if (latestFiber) return latestFiber;
352
356
  }
357
+ if (currentFiber) return currentFiber;
353
358
 
354
359
  if (alternate.actualStartTime && fiber.actualStartTime) {
355
360
  return alternate.actualStartTime > fiber.actualStartTime ? alternate : fiber;
@@ -413,14 +418,24 @@ const fiberIdFinalizationRegistry =
413
418
  const createFiberReference = (fiber: Fiber): FiberReference =>
414
419
  typeof WeakRef === "function" ? new WeakRef(fiber) : { deref: () => fiber };
415
420
 
416
- export const setFiberId = (fiber: Fiber, fiberId: number = nextFiberId++): void => {
421
+ const getNextFiberId = (): number => {
422
+ if (!Number.isSafeInteger(nextFiberId)) throw new RangeError("Fiber ID space exhausted");
423
+ return nextFiberId++;
424
+ };
425
+
426
+ export const setFiberId = (fiber: Fiber, fiberId: number = getNextFiberId()): void => {
417
427
  const previousFiberId = fiberIdMap.get(fiber);
418
- if (previousFiberId !== undefined && previousFiberId !== fiberId) {
428
+ if (
429
+ previousFiberId !== undefined &&
430
+ previousFiberId !== fiberId &&
431
+ fiberByIdMap.get(previousFiberId)?.deref() === fiber
432
+ ) {
419
433
  fiberByIdMap.delete(previousFiberId);
420
434
  }
421
435
  fiberIdMap.set(fiber, fiberId);
422
436
  fiberByIdMap.set(fiberId, createFiberReference(fiber));
423
- fiberIdFinalizationRegistry?.register(fiber, fiberId);
437
+ if (previousFiberId !== undefined) fiberIdFinalizationRegistry?.unregister(fiber);
438
+ fiberIdFinalizationRegistry?.register(fiber, fiberId, fiber);
424
439
  if (Number.isSafeInteger(fiberId) && fiberId >= nextFiberId) {
425
440
  nextFiberId = fiberId + 1;
426
441
  }
@@ -430,10 +445,17 @@ export const getFiberId = (fiber: Fiber): number => {
430
445
  let currentFiberId = fiberIdMap.get(fiber);
431
446
  if (currentFiberId === undefined && fiber.alternate) {
432
447
  currentFiberId = fiberIdMap.get(fiber.alternate);
433
- if (currentFiberId !== undefined) setFiberId(fiber, currentFiberId);
448
+ if (currentFiberId !== undefined) {
449
+ const assignedFiber = fiberByIdMap.get(currentFiberId)?.deref();
450
+ if (assignedFiber && assignedFiber !== fiber.alternate) {
451
+ fiberIdMap.set(fiber, currentFiberId);
452
+ } else {
453
+ setFiberId(fiber, currentFiberId);
454
+ }
455
+ }
434
456
  }
435
457
  if (currentFiberId === undefined) {
436
- currentFiberId = nextFiberId++;
458
+ currentFiberId = getNextFiberId();
437
459
  setFiberId(fiber, currentFiberId);
438
460
  }
439
461
  return currentFiberId;
@@ -456,6 +478,7 @@ const releaseFiberId = (fiber: Fiber): void => {
456
478
  const fiberId = fiberIdMap.get(relatedFiber);
457
479
  if (fiberId !== undefined) fiberIds.add(fiberId);
458
480
  fiberIdMap.delete(relatedFiber);
481
+ fiberIdFinalizationRegistry?.unregister(relatedFiber);
459
482
  }
460
483
 
461
484
  for (const fiberId of fiberIds) {
@@ -532,10 +555,10 @@ const updateFiberTree = (
532
555
  if (wasTimedOut && isTimedOut) {
533
556
  const nextFallback = fiber.child?.sibling;
534
557
  const previousFallback = previousFiber.child?.sibling;
535
- if (nextFallback && previousFallback) {
558
+ if (nextFallback) {
536
559
  pendingUpdates.push({
537
560
  fiber: nextFallback,
538
- previousFiber: previousFallback,
561
+ previousFiber: previousFallback ?? null,
539
562
  traverseSiblings: false,
540
563
  });
541
564
  }
@@ -606,21 +629,23 @@ const isRootFiberMounted = (fiber: Fiber): boolean => {
606
629
  */
607
630
  export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: RenderHandler): void => {
608
631
  const fiber = "current" in root ? root.current : root;
632
+ const rootKey = "current" in root || !isFiberRoot(root.stateNode) ? root : root.stateNode;
609
633
 
610
- let rootInstance = rootInstanceMap.get(root);
634
+ let rootInstance = rootInstanceMap.get(rootKey);
611
635
 
612
636
  if (!rootInstance) {
613
637
  rootInstance = { prevFiber: null };
614
- rootInstanceMap.set(root, rootInstance);
638
+ rootInstanceMap.set(rootKey, rootInstance);
615
639
  }
616
640
 
617
641
  const { prevFiber } = rootInstance;
642
+ rootInstance.prevFiber = fiber;
618
643
  if (!fiber) {
619
644
  if (prevFiber) {
620
645
  unmountFiber(onRender, prevFiber);
621
646
  }
622
647
  } else if (prevFiber !== null) {
623
- const wasMounted = isRootFiberMounted(prevFiber);
648
+ const wasMounted = isRootFiberMounted(fiber.alternate ?? prevFiber);
624
649
  const isMounted = isRootFiberMounted(fiber);
625
650
 
626
651
  if (!wasMounted && isMounted) {
@@ -633,8 +658,6 @@ export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: Render
633
658
  } else {
634
659
  mountFiberTree(onRender, fiber, true);
635
660
  }
636
-
637
- rootInstance.prevFiber = fiber;
638
661
  };
639
662
 
640
663
  export interface InstrumentationOptions {
@@ -690,10 +713,12 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
690
713
  priority,
691
714
  didError,
692
715
  ) => {
716
+ const isCurrentDispatcher =
717
+ hookDispatchers.get(rdtHook)?.onCommitFiberRoot === dispatchCommitFiberRoot;
693
718
  if (prevOnCommitFiberRoot) {
694
719
  callListener(prevOnCommitFiberRoot, rdtHook, rendererID, root, priority, didError);
695
720
  }
696
- if (hookDispatchers.get(rdtHook)?.onCommitFiberRoot !== dispatchCommitFiberRoot) return;
721
+ if (!isCurrentDispatcher) return;
697
722
  setReactWorkTagsForFiber(root.current, rdtHook.renderers.get(rendererID));
698
723
  // Custom renderers and test harnesses commit roots without a memoizedState;
699
724
  // those must stay tracked, so only explicit unmount evidence removes a root.
@@ -706,9 +731,14 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
706
731
  rootRendererIds.set(root, rendererID);
707
732
  rootHooks.set(root, rdtHook);
708
733
  }
709
- for (const { options, target } of instrumentationSubscriptions) {
710
- if (target === hookTargets.get(rdtHook) && options.onCommitFiberRoot) {
711
- callListener(options.onCommitFiberRoot, options, rendererID, root, priority, didError);
734
+ const subscriptionSnapshot = [...instrumentationSubscriptions];
735
+ for (const subscription of subscriptionSnapshot) {
736
+ const { options, target } = subscription;
737
+ if (instrumentationSubscriptions.has(subscription) && target === hookTargets.get(rdtHook)) {
738
+ callListener(
739
+ () => options.onCommitFiberRoot?.(rendererID, root, priority, didError),
740
+ undefined,
741
+ );
712
742
  }
713
743
  }
714
744
  };
@@ -725,21 +755,29 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
725
755
  rendererID,
726
756
  fiber,
727
757
  ) => {
758
+ const isCurrentDispatcher =
759
+ hookDispatchers.get(rdtHook)?.onCommitFiberUnmount === dispatchCommitFiberUnmount;
728
760
  setReactWorkTagsForFiber(fiber, rdtHook.renderers.get(rendererID));
729
- if (prevOnCommitFiberUnmount) {
730
- callListener(prevOnCommitFiberUnmount, rdtHook, rendererID, fiber);
731
- }
732
- if (hookDispatchers.get(rdtHook)?.onCommitFiberUnmount !== dispatchCommitFiberUnmount) {
733
- return;
734
- }
761
+ const previousUnmountingFiber = currentUnmountingFiber;
762
+ currentUnmountingFiber = fiber;
735
763
  try {
736
- for (const { options, target } of instrumentationSubscriptions) {
737
- if (target === hookTargets.get(rdtHook) && options.onCommitFiberUnmount) {
738
- callListener(options.onCommitFiberUnmount, options, rendererID, fiber);
764
+ if (prevOnCommitFiberUnmount) {
765
+ callListener(prevOnCommitFiberUnmount, rdtHook, rendererID, fiber);
766
+ }
767
+ if (!isCurrentDispatcher) return;
768
+ const subscriptionSnapshot = [...instrumentationSubscriptions];
769
+ for (const subscription of subscriptionSnapshot) {
770
+ const { options, target } = subscription;
771
+ if (
772
+ instrumentationSubscriptions.has(subscription) &&
773
+ target === hookTargets.get(rdtHook)
774
+ ) {
775
+ callListener(() => options.onCommitFiberUnmount?.(rendererID, fiber), undefined);
739
776
  }
740
777
  }
741
778
  } finally {
742
- releaseFiberId(fiber);
779
+ currentUnmountingFiber = previousUnmountingFiber;
780
+ if (isCurrentDispatcher) releaseFiberId(fiber);
743
781
  }
744
782
  };
745
783
  dispatchers.onCommitFiberUnmount = dispatchCommitFiberUnmount;
@@ -755,15 +793,17 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
755
793
  rendererID,
756
794
  root,
757
795
  ) => {
796
+ const isCurrentDispatcher =
797
+ hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot === dispatchPostCommitFiberRoot;
758
798
  if (prevOnPostCommitFiberRoot) {
759
799
  callListener(prevOnPostCommitFiberRoot, rdtHook, rendererID, root);
760
800
  }
761
- if (hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot !== dispatchPostCommitFiberRoot) {
762
- return;
763
- }
764
- for (const { options, target } of instrumentationSubscriptions) {
765
- if (target === hookTargets.get(rdtHook) && options.onPostCommitFiberRoot) {
766
- callListener(options.onPostCommitFiberRoot, options, rendererID, root);
801
+ if (!isCurrentDispatcher) return;
802
+ const subscriptionSnapshot = [...instrumentationSubscriptions];
803
+ for (const subscription of subscriptionSnapshot) {
804
+ const { options, target } = subscription;
805
+ if (instrumentationSubscriptions.has(subscription) && target === hookTargets.get(rdtHook)) {
806
+ callListener(() => options.onPostCommitFiberRoot?.(rendererID, root), undefined);
767
807
  }
768
808
  }
769
809
  };
@@ -781,13 +821,17 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
781
821
  root,
782
822
  children,
783
823
  ) => {
824
+ const isCurrentDispatcher =
825
+ hookDispatchers.get(rdtHook)?.onScheduleFiberRoot === dispatchScheduleFiberRoot;
784
826
  if (prevOnScheduleFiberRoot) {
785
827
  callListener(prevOnScheduleFiberRoot, rdtHook, rendererID, root, children);
786
828
  }
787
- if (hookDispatchers.get(rdtHook)?.onScheduleFiberRoot !== dispatchScheduleFiberRoot) return;
788
- for (const { options, target } of instrumentationSubscriptions) {
789
- if (target === hookTargets.get(rdtHook) && options.onScheduleFiberRoot) {
790
- callListener(options.onScheduleFiberRoot, options, rendererID, root, children);
829
+ if (!isCurrentDispatcher) return;
830
+ const subscriptionSnapshot = [...instrumentationSubscriptions];
831
+ for (const subscription of subscriptionSnapshot) {
832
+ const { options, target } = subscription;
833
+ if (instrumentationSubscriptions.has(subscription) && target === hookTargets.get(rdtHook)) {
834
+ callListener(() => options.onScheduleFiberRoot?.(rendererID, root, children), undefined);
791
835
  }
792
836
  }
793
837
  };
@@ -843,15 +887,20 @@ try {
843
887
  */
844
888
  export const instrument = (options: InstrumentationOptions): Unsubscribe => {
845
889
  const target = options.target ?? globalThis;
846
- const rdtHook = getRDTHook(options.onActive, target);
890
+ const activeListener = options.onActive;
891
+ const onActive = activeListener ? () => activeListener() : undefined;
892
+ const existingHook = target.__REACT_DEVTOOLS_GLOBAL_HOOK__;
893
+ if (existingHook) wireHookEventDispatchers(existingHook, target);
894
+ const rdtHook = getRDTHook(undefined, target);
847
895
  rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
848
896
 
849
897
  wireHookEventDispatchers(rdtHook, target);
850
898
  const subscription: InstrumentationSubscription = { options, target };
851
899
  instrumentationSubscriptions.add(subscription);
900
+ if (onActive) getRDTHook(onActive, target);
852
901
 
853
902
  return createUnsubscribe(() => {
854
- if (options.onActive) removeActiveListener(options.onActive, target);
903
+ if (onActive) removeActiveListener(onActive, target);
855
904
  instrumentationSubscriptions.delete(subscription);
856
905
  });
857
906
  };
@@ -974,6 +1023,6 @@ export {
974
1023
  onRendererInject,
975
1024
  patchRDTHook,
976
1025
  version,
977
- } from "./rdt-hook.js";
978
- export type { ReactDevToolsTarget, Unsubscribe } from "./rdt-hook.js";
979
- export * from "./react-internals/index.js";
1026
+ } from "./rdt-hook";
1027
+ export type { ReactDevToolsTarget, Unsubscribe } from "./rdt-hook";
1028
+ export * from "./react-internals/index";
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import "./install-hook-only.js";
1
+ import "./install-hook-only";
2
2
 
3
- export * from "./core.js";
4
- export * from "./react.js";
3
+ export * from "./core";
4
+ export * from "./react";
@@ -1,4 +1,4 @@
1
- import { getRDTHook } from "./rdt-hook.js";
1
+ import { getRDTHook } from "./rdt-hook";
2
2
 
3
3
  // Importing bippy must never crash module evaluation, even when a foreign
4
4
  // hook is frozen or otherwise rejects patching.
package/src/rdt-hook.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // This module must load before React so renderers can inject into the hook.
2
2
 
3
- import { callListener } from "./call-listener.js";
4
- import type { FiberRoot, ReactDevToolsGlobalHook, ReactRenderer } from "./react-internals/index.js";
3
+ import { callListener } from "./call-listener";
4
+ import type { FiberRoot, ReactDevToolsGlobalHook, ReactRenderer } from "./react-internals/index";
5
5
 
6
6
  export interface Unsubscribe extends Disposable {
7
7
  (): void;
@@ -24,6 +24,10 @@ interface RDTHookReplaceListener {
24
24
  (rdtHook: ReactDevToolsGlobalHook, target: ReactDevToolsTarget): void;
25
25
  }
26
26
 
27
+ interface RDTHookReplaceSubscription {
28
+ listener: RDTHookReplaceListener;
29
+ }
30
+
27
31
  export interface ReactDevToolsTarget {
28
32
  __REACT_DEVTOOLS_GLOBAL_HOOK__?: ReactDevToolsGlobalHook;
29
33
  }
@@ -94,7 +98,7 @@ export const _renderers = new Set<ReactRenderer>();
94
98
 
95
99
  const activeListenerTargets = new WeakMap<ActiveListener, Set<ReactDevToolsTarget>>();
96
100
  const rendererInjectSubscriptions = new Set<RendererInjectSubscription>();
97
- const rdtHookReplaceListeners = new Set<RDTHookReplaceListener>();
101
+ const rdtHookReplaceSubscriptions = new Set<RDTHookReplaceSubscription>();
98
102
  const notifiedRenderersByTarget = new WeakMap<ReactDevToolsTarget, WeakSet<ReactRenderer>>();
99
103
 
100
104
  const addActiveListener = (listener: ActiveListener, target: ReactDevToolsTarget): void => {
@@ -116,8 +120,11 @@ export const removeActiveListener = (
116
120
  }
117
121
  };
118
122
 
119
- const notifyActiveListeners = (target: ReactDevToolsTarget): void => {
120
- for (const listener of _onActiveListeners) {
123
+ const notifyActiveListeners = (
124
+ target: ReactDevToolsTarget,
125
+ pendingListeners = [..._onActiveListeners],
126
+ ): void => {
127
+ for (const listener of pendingListeners) {
121
128
  if (activeListenerTargets.get(listener)?.has(target)) callListener(listener, undefined);
122
129
  }
123
130
  };
@@ -126,8 +133,11 @@ const notifyRendererInjectListeners = (
126
133
  target: ReactDevToolsTarget,
127
134
  renderer: ReactRenderer,
128
135
  ): void => {
129
- for (const subscription of rendererInjectSubscriptions) {
130
- if (subscription.target === target) callListener(subscription.listener, subscription, renderer);
136
+ const subscriptionSnapshot = [...rendererInjectSubscriptions];
137
+ for (const subscription of subscriptionSnapshot) {
138
+ if (rendererInjectSubscriptions.has(subscription) && subscription.target === target) {
139
+ callListener(subscription.listener, subscription, renderer);
140
+ }
131
141
  }
132
142
  };
133
143
 
@@ -135,8 +145,11 @@ const notifyRDTHookReplaceListeners = (
135
145
  rdtHook: ReactDevToolsGlobalHook,
136
146
  target: ReactDevToolsTarget,
137
147
  ): void => {
138
- for (const listener of rdtHookReplaceListeners) {
139
- callListener(listener, undefined, rdtHook, target);
148
+ const subscriptionSnapshot = [...rdtHookReplaceSubscriptions];
149
+ for (const subscription of subscriptionSnapshot) {
150
+ if (rdtHookReplaceSubscriptions.has(subscription)) {
151
+ callListener(subscription.listener, undefined, rdtHook, target);
152
+ }
140
153
  }
141
154
  };
142
155
 
@@ -153,9 +166,10 @@ export const onRendererInject = (
153
166
  };
154
167
 
155
168
  export const onRDTHookReplace = (listener: RDTHookReplaceListener): Unsubscribe => {
156
- rdtHookReplaceListeners.add(listener);
169
+ const subscription = { listener };
170
+ rdtHookReplaceSubscriptions.add(subscription);
157
171
  return createUnsubscribe(() => {
158
- rdtHookReplaceListeners.delete(listener);
172
+ rdtHookReplaceSubscriptions.delete(subscription);
159
173
  });
160
174
  };
161
175
 
@@ -243,10 +257,11 @@ export const installRDTHook = (
243
257
  _renderers.add(renderer);
244
258
  nextRenderers.set(rendererId, renderer);
245
259
  });
246
- if (ourRenderers.size > 0 || rdtHookReplaceListeners.size > 0) {
247
- patchRDTHook(onActive, target);
260
+ if (ourRenderers.size > 0 || rdtHookReplaceSubscriptions.size > 0) {
261
+ patchRDTHook(undefined, target, () => notifyRDTHookReplaceListeners(newHook, target));
262
+ } else {
263
+ notifyRDTHookReplaceListeners(newHook, target);
248
264
  }
249
- notifyRDTHookReplaceListeners(rdtHook, target);
250
265
  }
251
266
  },
252
267
  });
@@ -288,9 +303,10 @@ export const installRDTHook = (
288
303
  export const patchRDTHook = (
289
304
  onActive?: ActiveListener,
290
305
  target: ReactDevToolsTarget = globalThis,
306
+ onReady?: () => void,
291
307
  ): void => {
292
308
  if (onActive) addActiveListener(onActive, target);
293
- let didNotifyActiveListeners = false;
309
+ let shouldNotifyActiveListeners = false;
294
310
  const rdtHook = getTargetHook(target);
295
311
  if (!rdtHook) return;
296
312
  const renderers = getRendererMap(rdtHook);
@@ -306,28 +322,25 @@ export const patchRDTHook = (
306
322
  if (!isReactDevtools) {
307
323
  rdtHook.on = noOp;
308
324
  }
309
- if (renderers.size) {
310
- renderers.forEach((renderer) => _renderers.add(renderer));
311
- rdtHook._instrumentationIsActive = true;
312
- notifyActiveListeners(target);
313
- didNotifyActiveListeners = true;
314
- } else if (!isReactDevtools && isReactRefresh(rdtHook)) {
315
- // HACK: react-refresh's stub inject never records renderers, so a React app
316
- // that injected before bippy loaded is undetectable through the renderers map.
317
- // A react-refresh hook implies a dev renderer, so activate immediately.
318
- rdtHook._instrumentationIsActive = true;
319
- notifyActiveListeners(target);
320
- didNotifyActiveListeners = true;
321
- }
325
+ const shouldActivate = renderers.size > 0 || (!isReactDevtools && isReactRefresh(rdtHook));
322
326
  const previousInject = rdtHook.inject;
323
327
  rdtHook.inject = (renderer) => {
324
328
  const rendererId = previousInject.call(rdtHook, renderer);
325
329
  trackInjectedRenderer(rdtHook, target, renderers, rendererId, renderer);
326
330
  return rendererId;
327
331
  };
332
+ if (shouldActivate) {
333
+ renderers.forEach((renderer) => _renderers.add(renderer));
334
+ rdtHook._instrumentationIsActive = true;
335
+ shouldNotifyActiveListeners = true;
336
+ }
328
337
  }
329
- if (!didNotifyActiveListeners && (renderers.size || rdtHook._instrumentationIsActive)) {
330
- if (onActive) callListener(onActive, undefined);
338
+ const pendingListeners = shouldNotifyActiveListeners ? [..._onActiveListeners] : [];
339
+ onReady?.();
340
+ if (shouldNotifyActiveListeners) {
341
+ notifyActiveListeners(target, pendingListeners);
342
+ } else if (onActive && (renderers.size || rdtHook._instrumentationIsActive)) {
343
+ callListener(onActive, undefined);
331
344
  }
332
345
  };
333
346
 
@@ -1,6 +1,6 @@
1
1
  // Adapted from ReactFiberTreeReflection.js. Copyright (c) Meta Platforms, Inc. and affiliates. MIT licensed.
2
- import type { Fiber } from "./types.js";
3
- import { getReactWorkTagsForFiber } from "./index.js";
2
+ import type { Fiber } from "./types";
3
+ import { getReactWorkTagsForFiber } from "./index";
4
4
 
5
5
  export const getCurrentFiberFromRoot = (fiber: Fiber): Fiber | null => {
6
6
  const alternate = fiber.alternate;
@@ -1,4 +1,4 @@
1
- import { compareSemver } from "../semver.js";
1
+ import { compareSemver } from "../semver";
2
2
 
3
3
  export const ReactBuildType = {
4
4
  Development: 1,
@@ -1,22 +1,22 @@
1
- import { getReactWorkTags, ReactFiberFlags } from "./generated/react-work-tags.js";
2
- import type { ReactWorkTagMap } from "./generated/react-work-tags.js";
3
- import { isSemver } from "./semver.js";
4
- import type { Fiber, ReactRenderer } from "./types.js";
1
+ import { getReactWorkTags, ReactFiberFlags } from "./generated/react-work-tags";
2
+ import type { ReactWorkTagMap } from "./generated/react-work-tags";
3
+ import { isSemver } from "./semver";
4
+ import type { Fiber, ReactRenderer } from "./types";
5
5
 
6
- export { compareSemver } from "./semver.js";
6
+ export { compareSemver } from "./semver";
7
7
  export {
8
8
  getReactWorkTags,
9
9
  ReactBuildType,
10
10
  ReactFiberFlags,
11
11
  ReactSymbols,
12
- } from "./generated/react-work-tags.js";
12
+ } from "./generated/react-work-tags";
13
13
  export type {
14
14
  HostWorkTag,
15
15
  ReactWorkTag,
16
16
  ReactWorkTagMap,
17
17
  ReactWorkTagVersion,
18
- } from "./generated/react-work-tags.js";
19
- export * from "./types.js";
18
+ } from "./generated/react-work-tags";
19
+ export * from "./types";
20
20
 
21
21
  interface InheritedWorkTags {
22
22
  workTags: Readonly<ReactWorkTagMap>;
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type ReactReconciler from "react-reconciler";
3
- import type { HostWorkTag, ReactWorkTag } from "./generated/react-work-tags.js";
3
+ import type { HostWorkTag, ReactWorkTag } from "./generated/react-work-tags";
4
4
 
5
5
  export type WorkTag = ReactReconciler.WorkTag | ReactWorkTag;
6
6
 
package/src/react.ts CHANGED
@@ -1,11 +1,11 @@
1
- import "./install-hook-only.js";
1
+ import "./install-hook-only";
2
2
  import React from "react";
3
- import { isFiber, traverseFiber } from "./core.js";
4
- import { _renderers } from "./rdt-hook.js";
5
- import { getCurrentFiberFromRoot } from "./react-internals/current-fiber.js";
6
- import type { Fiber } from "./react-internals/index.js";
3
+ import { isFiber, traverseFiber } from "./core";
4
+ import { _renderers } from "./rdt-hook";
5
+ import { getCurrentFiberFromRoot } from "./react-internals/current-fiber";
6
+ import type { Fiber } from "./react-internals/index";
7
7
 
8
- export type { Fiber } from "./react-internals/index.js";
8
+ export type { Fiber } from "./react-internals/index";
9
9
 
10
10
  interface RenderMarker {
11
11
  (state: undefined): undefined;
@@ -1,15 +1,19 @@
1
1
  export const SCHEME_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
2
2
 
3
3
  export const INTERNAL_SCHEME_PREFIXES = [
4
- "rsc://",
5
- "file:///",
4
+ "rspack-internal://",
6
5
  "webpack-internal://",
7
- "webpack://",
8
- "node:",
9
6
  "turbopack://",
7
+ "webpack://",
8
+ "rspack://",
9
+ "parcel://",
10
10
  "metro://",
11
- "/app-pages-browser/",
11
+ "file:///",
12
+ "rsc://",
13
+ "node:",
14
+ "bun:",
12
15
  "/(app-pages-browser)/",
16
+ "/app-pages-browser/",
13
17
  ] as const;
14
18
 
15
19
  export const ABOUT_REACT_PREFIX = "about://React/";
@@ -21,7 +25,7 @@ export const ANONYMOUS_FILE_PATTERNS = ["<anonymous>", "eval", ""] as const;
21
25
  export const SOURCE_FILE_EXTENSION_REGEX = /\.(cjs|cts|js|jsx|mdx|mjs|mts|ts|tsx)$/;
22
26
 
23
27
  export const BUNDLED_FILE_PATTERN_REGEX =
24
- /(\.min|bundle|chunk|vendor|vendors|runtime|polyfill|polyfills)\.(js|mjs|cjs)$|(chunk|bundle|vendor|vendors|runtime|polyfill|polyfills|framework|app|main|index)[-_.][A-Za-z0-9_-]{4,}\.(js|mjs|cjs)$|[\da-f]{8,}\.(js|mjs|cjs)$|[-_.][\da-f]{20,}\.(js|mjs|cjs)$|\/dist\/|\/build\/|\/.next\/|\/out\/|\/node_modules\/|\.webpack\.|\.vite\.|\.turbopack\./i;
28
+ /(\.min|bundle|chunk|vendor|vendors|runtime|polyfill|polyfills)\.(js|mjs|cjs)$|(chunk|bundle|vendor|vendors|runtime|polyfill|polyfills|framework|app|main|index)[-_.][A-Za-z0-9_-]{4,}\.(js|mjs|cjs)$|[\da-f]{8,}\.(js|mjs|cjs)$|[-_.][\da-f]{20,}\.(js|mjs|cjs)$|\/dist\/|\/build\/|\/.next\/|\/out\/|\/node_modules\/|\/_next\/|\/_expo\/|\/_nuxt\/|\/@vite\/|\/@react-refresh|\/\.parcel-cache\/|\/\.expo\/|\/\.rsbuild\/|\/\.vite\/|\/\.svelte-kit\/|\/\.astro\/|\/\.nuxt\/|\/\.angular\/|\.webpack\.|\.vite\.|\.turbopack\.|\[root-of-the-server\]/i;
25
29
 
26
30
  export const QUERY_PARAMETER_PATTERN_REGEX = /^\?[\w~.-]+(?:=[^&#]*)?(?:&[\w~.-]+(?:=[^&#]*)?)*$/;
27
31
 
@@ -1,14 +1,14 @@
1
- import type { Fiber } from "../react-internals/index.js";
2
- import { getDisplayName } from "../core.js";
3
- import { getDefinitionFrameFromOwnedChild, getRawParentStack } from "./owner-stack.js";
1
+ import type { Fiber } from "../react-internals/index";
2
+ import { getDisplayName } from "../core";
3
+ import { getDefinitionFrameFromOwnedChild, getRawParentStack } from "./owner-stack";
4
4
  import {
5
5
  getSourceContentFromSourceMap,
6
6
  getSourceFromSourceMap,
7
7
  getSourceMap,
8
8
  type SourceFetch,
9
9
  type SourceMapRequestOptions,
10
- } from "./symbolication.js";
11
- import type { StackFrame } from "./parse-stack.js";
10
+ } from "./symbolication";
11
+ import type { StackFrame } from "./parse-stack";
12
12
 
13
13
  const COMPONENT_DECLARATION_PATTERNS = [
14
14
  /(?:^|export\s+)(?:const|let|var)\s+(\w+)\s*=/,