bippy 0.6.1-dev.3702582 → 0.6.1-dev.4ed5147

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
@@ -3,11 +3,9 @@
3
3
  import type * as React from "react";
4
4
 
5
5
  import type {
6
- ContextDependency,
7
6
  Fiber,
8
7
  FiberRoot,
9
8
  HostFiber,
10
- MemoizedState,
11
9
  ReactDevToolsGlobalHook,
12
10
  ReactRenderer,
13
11
  } from "./react-internals/index.js";
@@ -20,7 +18,6 @@ import {
20
18
  hasRDTHook,
21
19
  isRealReactDevtools,
22
20
  onRDTHookReplace,
23
- onRendererInject,
24
21
  } from "./rdt-hook.js";
25
22
  import type { Unsubscribe } from "./rdt-hook.js";
26
23
  import {
@@ -32,8 +29,6 @@ import {
32
29
  setReactWorkTagsForFiber,
33
30
  } from "./react-internals/index.js";
34
31
 
35
- export { getReactWorkTags, ReactSymbols } from "./react-internals/index.js";
36
- export type { ReactWorkTagMap, ReactWorkTagVersion } from "./react-internals/index.js";
37
32
  export {
38
33
  BippyError,
39
34
  BippyHookInspectionError,
@@ -56,20 +51,10 @@ interface FiberSelector {
56
51
  (node: Fiber): boolean | Promise<boolean | void> | void;
57
52
  }
58
53
 
59
- export interface FiberTimings {
60
- selfTime: number;
61
- totalTime: number;
62
- }
63
-
64
54
  export interface RenderHandler {
65
55
  (fiber: Fiber, phase: RenderPhase): unknown;
66
56
  }
67
57
 
68
- interface ValueWrite {
69
- path: string[];
70
- value: unknown;
71
- }
72
-
73
58
  /**
74
59
  * Returns `true` if object is a React Element.
75
60
  *
@@ -148,90 +133,6 @@ export const isFiber = (maybeFiber: unknown): maybeFiber is Fiber => {
148
133
  return "pendingProps" in maybeFiber && !("containerInfo" in maybeFiber);
149
134
  };
150
135
 
151
- /**
152
- * Traverses up or down a {@link Fiber}'s contexts, return `true` to stop and select the current and previous context value.
153
- */
154
- export const traverseContexts = (
155
- fiber: Fiber,
156
- selector: (
157
- nextValue: ContextDependency<unknown> | null | undefined,
158
- prevValue: ContextDependency<unknown> | null | undefined,
159
- ) => boolean | void,
160
- ): boolean => {
161
- try {
162
- const nextDependencies = fiber.dependencies;
163
- const prevDependencies = fiber.alternate?.dependencies;
164
-
165
- if (!nextDependencies || !prevDependencies) return false;
166
- if (
167
- typeof nextDependencies !== "object" ||
168
- !("firstContext" in nextDependencies) ||
169
- typeof prevDependencies !== "object" ||
170
- !("firstContext" in prevDependencies)
171
- ) {
172
- return false;
173
- }
174
- let nextContext: ContextDependency<unknown> | null | undefined = nextDependencies.firstContext;
175
- let prevContext: ContextDependency<unknown> | null | undefined = prevDependencies.firstContext;
176
- while (
177
- (nextContext && typeof nextContext === "object" && "memoizedValue" in nextContext) ||
178
- (prevContext && typeof prevContext === "object" && "memoizedValue" in prevContext)
179
- ) {
180
- if (selector(nextContext, prevContext) === true) return true;
181
-
182
- nextContext = nextContext?.next;
183
- prevContext = prevContext?.next;
184
- }
185
- } catch {}
186
- return false;
187
- };
188
-
189
- /**
190
- * Traverses up or down a {@link Fiber}'s states, return `true` to stop and select the current and previous state value. This stores both state values and effects.
191
- */
192
- export const traverseState = (
193
- fiber: Fiber,
194
- selector: (
195
- nextValue: MemoizedState | null | undefined,
196
- prevValue: MemoizedState | null | undefined,
197
- ) => boolean | void,
198
- ): boolean => {
199
- try {
200
- let nextState: MemoizedState | null | undefined = fiber.memoizedState;
201
- let prevState: MemoizedState | null | undefined = fiber.alternate?.memoizedState;
202
-
203
- while (nextState || prevState) {
204
- if (selector(nextState, prevState) === true) return true;
205
-
206
- nextState = nextState?.next;
207
- prevState = prevState?.next;
208
- }
209
- } catch {}
210
- return false;
211
- };
212
-
213
- /**
214
- * Traverses up or down a {@link Fiber}'s props, return `true` to stop and select the current and previous props value.
215
- */
216
- export const traverseProps = (
217
- fiber: Fiber,
218
- selector: (propName: string, nextValue: unknown, prevValue: unknown) => boolean | void,
219
- ): boolean => {
220
- try {
221
- const nextProps = fiber.memoizedProps;
222
- const prevProps = fiber.alternate?.memoizedProps || {};
223
-
224
- for (const propName of Object.keys(nextProps)) {
225
- if (selector(propName, nextProps[propName], prevProps[propName]) === true) return true;
226
- }
227
- for (const propName of Object.keys(prevProps)) {
228
- if (propName in nextProps) continue;
229
- if (selector(propName, nextProps[propName], prevProps[propName]) === true) return true;
230
- }
231
- } catch {}
232
- return false;
233
- };
234
-
235
136
  /**
236
137
  * Returns `true` if the {@link Fiber} has rendered. Note that this does not mean the fiber has rendered in the current commit, just that it has rendered in the past.
237
138
  */
@@ -271,46 +172,6 @@ export const didFiberCommit = (fiber: Fiber): boolean => {
271
172
  );
272
173
  };
273
174
 
274
- /**
275
- * Returns all host {@link Fiber}s that have committed and rendered.
276
- */
277
- export const getMutatedHostFibers = (fiber: Fiber): Fiber[] => {
278
- const mutations: Fiber[] = [];
279
- const stack: Fiber[] = [fiber];
280
-
281
- while (stack.length) {
282
- const node = stack.pop();
283
- if (!node) continue;
284
-
285
- if (isHostFiber(node) && didFiberCommit(node) && didFiberRender(node)) {
286
- mutations.push(node);
287
- }
288
-
289
- if (node.child) stack.push(node.child);
290
- if (node.sibling) stack.push(node.sibling);
291
- }
292
-
293
- return mutations;
294
- };
295
-
296
- /**
297
- * Returns the stack of {@link Fiber}s from the current fiber to the root fiber.
298
- *
299
- * @example
300
- * ```ts
301
- * [fiber, fiber.return, fiber.return.return, ...]
302
- * ```
303
- */
304
- export const getFiberStack = (fiber: Fiber): Fiber[] => {
305
- const stack: Fiber[] = [];
306
- let currentFiber = fiber;
307
- while (currentFiber.return) {
308
- stack.push(currentFiber);
309
- currentFiber = currentFiber.return;
310
- }
311
- return stack;
312
- };
313
-
314
175
  /**
315
176
  * Returns `true` if the {@link Fiber} should be filtered out during reconciliation.
316
177
  */
@@ -359,47 +220,6 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
359
220
  }
360
221
  };
361
222
 
362
- /**
363
- * Returns the nearest host {@link Fiber} to the current {@link Fiber}.
364
- */
365
- export const getNearestHostFiber = (fiber: Fiber, ascending = false): Fiber | null => {
366
- let hostFiber = traverseFiber(fiber, isHostFiber, ascending);
367
- if (!hostFiber) {
368
- hostFiber = traverseFiber(fiber, isHostFiber, !ascending);
369
- }
370
- return hostFiber;
371
- };
372
-
373
- /**
374
- * Returns all host {@link Fiber}s in the tree that are associated with the current {@link Fiber}.
375
- */
376
- export const getNearestHostFibers = (fiber: Fiber): Fiber[] => {
377
- const hostFibers: Fiber[] = [];
378
- const stack: Fiber[] = [];
379
-
380
- if (isHostFiber(fiber)) {
381
- hostFibers.push(fiber);
382
- } else if (fiber.child) {
383
- stack.push(fiber.child);
384
- }
385
-
386
- while (stack.length) {
387
- const currentNode = stack.pop();
388
- if (!currentNode) break;
389
- if (isHostFiber(currentNode)) {
390
- hostFibers.push(currentNode);
391
- } else if (currentNode.child) {
392
- stack.push(currentNode.child);
393
- }
394
-
395
- if (currentNode.sibling) {
396
- stack.push(currentNode.sibling);
397
- }
398
- }
399
-
400
- return hostFibers;
401
- };
402
-
403
223
  /**
404
224
  * Traverses up or down a {@link Fiber}, return `true` to stop and select a node.
405
225
  */
@@ -468,27 +288,6 @@ const traverseFiberSiblings = (
468
288
  return match ?? traverseFiberSiblings(nextSibling, selector, ascending);
469
289
  };
470
290
 
471
- /**
472
- * Returns the timings of the {@link Fiber}.
473
- *
474
- * @example
475
- * ```ts
476
- * const { selfTime, totalTime } = getTimings(fiber);
477
- * console.log(selfTime, totalTime);
478
- * ```
479
- */
480
- export const getTimings = (fiber?: Fiber | null): FiberTimings => {
481
- const totalTime = fiber?.actualDuration ?? 0;
482
- let selfTime = totalTime;
483
- // TODO: calculate a DOM time, which is just host component summed up
484
- let child = fiber?.child ?? null;
485
- while (totalTime > 0 && child !== null) {
486
- selfTime -= child.actualDuration ?? 0;
487
- child = child.sibling;
488
- }
489
- return { selfTime, totalTime };
490
- };
491
-
492
291
  /**
493
292
  * Returns `true` if the {@link Fiber} uses React Compiler's memo cache.
494
293
  */
@@ -542,6 +341,7 @@ export const isInstrumentationActive = (): boolean => {
542
341
  };
543
342
 
544
343
  export const _fiberRoots = new Set<FiberRoot>();
344
+ const rootRendererIds = new WeakMap<FiberRoot, number>();
545
345
 
546
346
  /**
547
347
  * Returns the latest fiber (since it may be double-buffered).
@@ -561,6 +361,25 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
561
361
  return fiber;
562
362
  };
563
363
 
364
+ /**
365
+ * Returns the renderer that owns the {@link Fiber}.
366
+ */
367
+ export const getRenderer = (fiber: Fiber): ReactRenderer | null => {
368
+ if (!hasRDTHook()) return null;
369
+
370
+ let rootFiber = fiber;
371
+ while (rootFiber.return) {
372
+ rootFiber = rootFiber.return;
373
+ }
374
+
375
+ const fiberRoot = rootFiber.stateNode;
376
+ if (!isFiberRoot(fiberRoot)) return null;
377
+
378
+ const rendererID = rootRendererIds.get(fiberRoot);
379
+ if (rendererID === undefined) return null;
380
+ return getRDTHook().renderers.get(rendererID) ?? null;
381
+ };
382
+
564
383
  export type RenderPhase = "mount" | "unmount" | "update";
565
384
 
566
385
  let nextFiberId = 0;
@@ -806,168 +625,6 @@ export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: Render
806
625
  rootInstance.prevFiber = fiber;
807
626
  };
808
627
 
809
- const overrideRenderers = new Set<ReactRenderer>();
810
- let areOverrideRenderersWired = false;
811
-
812
- const wireOverrideRenderers = (): void => {
813
- if (!hasRDTHook()) return;
814
- const rdtHook = getRDTHook();
815
-
816
- setHookEventDispatchers(rdtHook);
817
- for (const renderer of rdtHook.renderers.values()) {
818
- overrideRenderers.add(renderer);
819
- }
820
- if (areOverrideRenderersWired) return;
821
- areOverrideRenderersWired = true;
822
- onRendererInject((renderer) => {
823
- overrideRenderers.add(renderer);
824
- });
825
- };
826
-
827
- const getRootRenderer = (fiber: Fiber): ReactRenderer | null => {
828
- if (!hasRDTHook()) return null;
829
- let hostRootFiber = fiber;
830
- while (hostRootFiber.return) {
831
- hostRootFiber = hostRootFiber.return;
832
- }
833
- const fiberRoot = hostRootFiber.stateNode;
834
- if (!isFiberRoot(fiberRoot)) return null;
835
- const rendererId = rootRendererIds.get(fiberRoot);
836
- if (rendererId === undefined) return null;
837
- return getRDTHook().renderers.get(rendererId) ?? null;
838
- };
839
-
840
- const resolveOverrideRenderers = (fiber: Fiber): ReactRenderer[] => {
841
- wireOverrideRenderers();
842
- const rootRenderer = getRootRenderer(fiber);
843
- if (rootRenderer) return [rootRenderer];
844
- return Array.from(overrideRenderers);
845
- };
846
-
847
- const applyPropsOverride = (
848
- renderers: ReactRenderer[],
849
- fiber: Fiber,
850
- path: string[],
851
- value: unknown,
852
- ): void => {
853
- for (const renderer of renderers) {
854
- try {
855
- renderer.overrideProps?.(fiber, path, value);
856
- } catch {}
857
- }
858
- };
859
-
860
- const getHookStateDispatch = (
861
- fiber: Fiber,
862
- hookIndex: number,
863
- ): ((value: unknown) => void) | null => {
864
- let hookState = fiber.memoizedState;
865
- for (let currentHookIndex = 0; currentHookIndex < hookIndex; currentHookIndex++) {
866
- if (!hookState?.next) return null;
867
- hookState = hookState.next;
868
- }
869
- const queue = hookState?.queue;
870
- if (!isPOJO(queue)) return null;
871
- const dispatch = queue.dispatch;
872
- return typeof dispatch === "function" ? (value) => dispatch(value) : null;
873
- };
874
-
875
- const findContextProviderFiber = (fiber: Fiber, contextType: unknown): Fiber | null => {
876
- let currentFiber: Fiber | null = fiber;
877
- while (currentFiber) {
878
- const fiberType = currentFiber.type;
879
- if (fiberType === contextType || fiberType?.Provider === contextType) {
880
- return currentFiber;
881
- }
882
- currentFiber = currentFiber.return;
883
- }
884
- return null;
885
- };
886
-
887
- const isPOJO = (maybePOJO: unknown): maybePOJO is Record<string, unknown> => {
888
- return (
889
- Object.prototype.toString.call(maybePOJO) === "[object Object]" &&
890
- (Object.getPrototypeOf(maybePOJO) === Object.prototype ||
891
- Object.getPrototypeOf(maybePOJO) === null)
892
- );
893
- };
894
-
895
- const buildPathsFromValue = (
896
- maybePOJO: Record<string, unknown>,
897
- basePath: string[] = [],
898
- ancestors = new WeakSet<object>(),
899
- ): ValueWrite[] => {
900
- if (ancestors.has(maybePOJO)) {
901
- return [{ path: basePath, value: maybePOJO }];
902
- }
903
-
904
- ancestors.add(maybePOJO);
905
- const paths: ValueWrite[] = [];
906
-
907
- for (const [key, value] of Object.entries(maybePOJO)) {
908
- const path = basePath.concat(key);
909
-
910
- if (isPOJO(value)) {
911
- paths.push(...buildPathsFromValue(value, path, ancestors));
912
- } else {
913
- paths.push({ path, value });
914
- }
915
- }
916
-
917
- ancestors.delete(maybePOJO);
918
- return paths;
919
- };
920
-
921
- const buildValueWrites = (partialValue: unknown): ValueWrite[] =>
922
- isPOJO(partialValue) ? buildPathsFromValue(partialValue) : [{ path: [], value: partialValue }];
923
-
924
- export const overrideProps = (fiber: Fiber, partialValue: Record<string, unknown>) => {
925
- const renderers = resolveOverrideRenderers(fiber);
926
- for (const { path, value } of buildValueWrites(partialValue)) {
927
- applyPropsOverride(renderers, fiber, path, value);
928
- }
929
- };
930
-
931
- export const overrideHookState = (fiber: Fiber, id: number, partialValue: unknown) => {
932
- const renderers = resolveOverrideRenderers(fiber).filter((renderer) =>
933
- Boolean(renderer.overrideHookState),
934
- );
935
-
936
- if (renderers.length > 0) {
937
- const writes = buildValueWrites(partialValue);
938
- for (const renderer of renderers) {
939
- for (const { path, value } of writes) {
940
- try {
941
- renderer.overrideHookState?.(fiber, id, path, value);
942
- } catch {}
943
- }
944
- }
945
- return;
946
- }
947
-
948
- // production renderers don't expose overrideHookState; dispatching through
949
- // the hook's own queue still works there, but only for whole-value writes
950
- // (a path write through dispatch would replace the entire hook state)
951
- if (isPOJO(partialValue)) return;
952
- const dispatch = getHookStateDispatch(fiber, id);
953
- if (!dispatch) return;
954
- try {
955
- dispatch(partialValue);
956
- } catch {}
957
- };
958
-
959
- export const overrideContext = (fiber: Fiber, contextType: unknown, partialValue: unknown) => {
960
- const providerFiber = findContextProviderFiber(fiber, contextType);
961
- if (!providerFiber) return;
962
- const renderers = resolveOverrideRenderers(providerFiber);
963
- for (const { path, value } of buildValueWrites(partialValue)) {
964
- applyPropsOverride(renderers, providerFiber, ["value", ...path], value);
965
- if (providerFiber.alternate) {
966
- applyPropsOverride(renderers, providerFiber.alternate, ["value", ...path], value);
967
- }
968
- }
969
- };
970
-
971
628
  export interface InstrumentationOptions {
972
629
  name?: string;
973
630
  onActive?: () => unknown;
@@ -998,8 +655,6 @@ interface HookDispatchers {
998
655
  const hookDispatchers = new WeakMap<ReactDevToolsGlobalHook, Partial<HookDispatchers>>();
999
656
  let didSubscribeToHookReplacements = false;
1000
657
 
1001
- const rootRendererIds = new WeakMap<FiberRoot, number>();
1002
-
1003
658
  // each hook event is dispatched from a single re-installable wrapper. If
1004
659
  // something overwrites the hook method (devtools, direct assignment), the
1005
660
  // next instrument() call installs a fresh wrapper over it; a superseded
@@ -1123,6 +778,18 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1123
778
  }
1124
779
  };
1125
780
 
781
+ const wireHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
782
+ if (!didSubscribeToHookReplacements) {
783
+ onRDTHookReplace(setHookEventDispatchers);
784
+ didSubscribeToHookReplacements = true;
785
+ }
786
+ setHookEventDispatchers(rdtHook);
787
+ };
788
+
789
+ if (hasRDTHook()) {
790
+ wireHookEventDispatchers(getRDTHook());
791
+ }
792
+
1126
793
  /**
1127
794
  * Instruments the DevTools hook. Each hook event is patched once and
1128
795
  * dispatches to a set of listeners, so multiple `instrument` calls compose
@@ -1142,15 +809,9 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1142
809
  */
1143
810
  export const instrument = (options: InstrumentationOptions): Unsubscribe => {
1144
811
  const rdtHook = getRDTHook(options.onActive);
1145
-
1146
- if (!didSubscribeToHookReplacements) {
1147
- onRDTHookReplace(setHookEventDispatchers);
1148
- didSubscribeToHookReplacements = true;
1149
- }
1150
-
1151
812
  rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
1152
813
 
1153
- setHookEventDispatchers(rdtHook);
814
+ wireHookEventDispatchers(rdtHook);
1154
815
  const subscription: InstrumentationSubscription = { options };
1155
816
  instrumentationSubscriptions.add(subscription);
1156
817
 
@@ -1199,7 +860,7 @@ const getPublicHostInstance = (stateNode: unknown): unknown => {
1199
860
  return typeof nativeTag === "number" ? nativeTag : stateNode;
1200
861
  };
1201
862
 
1202
- export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
863
+ export const getFiber = <T>(hostInstance: T): Fiber | null => {
1203
864
  const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
1204
865
  if (rdtHook?.renderers) {
1205
866
  for (const renderer of rdtHook.renderers.values()) {
@@ -1249,6 +910,8 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1249
910
  return null;
1250
911
  };
1251
912
 
913
+ export const getFiberFromHostInstance = getFiber;
914
+
1252
915
  export {
1253
916
  BIPPY_INSTRUMENTATION_STRING,
1254
917
  _onActiveListeners,
@@ -1262,4 +925,4 @@ export {
1262
925
  version,
1263
926
  } from "./rdt-hook.js";
1264
927
  export type { Unsubscribe } from "./rdt-hook.js";
1265
- export type * from "./react-internals/index.js";
928
+ export * from "./react-internals/index.js";
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import "./install-hook-only.js";
2
2
 
3
3
  export * from "./core.js";
4
+ export * from "./react.js";
package/src/react.ts ADDED
@@ -0,0 +1,76 @@
1
+ import "./install-hook-only.js";
2
+ import * as React from "react";
3
+ import { isValidFiber } from "./core.js";
4
+ import type { Fiber } from "./react-internals/index.js";
5
+
6
+ export type { Fiber } from "./react-internals/index.js";
7
+
8
+ const preserveState = (state: undefined): undefined => state;
9
+ const readEmptySnapshot = (): undefined => undefined;
10
+ const unsubscribeFromEmptyStore = (): void => {};
11
+ const subscribeToEmptyStore = (): (() => void) => unsubscribeFromEmptyStore;
12
+ const useSyncExternalStore: unknown = Reflect.get(React, "useSyncExternalStore");
13
+
14
+ const captureFiberFromHook = (useCaptureHook: () => void): Fiber | null => {
15
+ const originalBind = Function.prototype.bind;
16
+ let capturedFiber: Fiber | null = null;
17
+ // HACK: React binds hook callbacks to the rendering Fiber in production but exposes no public API for it.
18
+ const bindProxy = new Proxy(originalBind, {
19
+ apply: (bind, functionToBind, boundArguments) => {
20
+ const fiber = boundArguments[1];
21
+ if (!capturedFiber && isValidFiber(fiber)) {
22
+ capturedFiber = fiber;
23
+ }
24
+ return Reflect.apply(bind, functionToBind, boundArguments);
25
+ },
26
+ });
27
+ Reflect.set(Function.prototype, "bind", bindProxy);
28
+
29
+ try {
30
+ useCaptureHook();
31
+ } finally {
32
+ if (Function.prototype.bind === bindProxy) {
33
+ Reflect.set(Function.prototype, "bind", originalBind);
34
+ }
35
+ }
36
+
37
+ return capturedFiber;
38
+ };
39
+
40
+ const useExternalStoreCapture = (): void => {
41
+ Reflect.apply(useSyncExternalStore, React, [
42
+ subscribeToEmptyStore,
43
+ readEmptySnapshot,
44
+ readEmptySnapshot,
45
+ ]);
46
+ };
47
+
48
+ const useReducerCapture = (): void => {
49
+ React.useReducer(preserveState, undefined);
50
+ };
51
+
52
+ const useFiberWithExternalStore = (): Fiber | undefined =>
53
+ captureFiberFromHook(useExternalStoreCapture) ?? undefined;
54
+
55
+ const useFiberWithReducer = (): Fiber | undefined => {
56
+ const committedFiberRef = React.useRef<Fiber | null>(null);
57
+ const renderedFiberRef = React.useRef<Fiber | null>(null);
58
+ const hookFiber = captureFiberFromHook(useReducerCapture);
59
+ const fiber =
60
+ hookFiber ??
61
+ (renderedFiberRef.current !== committedFiberRef.current
62
+ ? renderedFiberRef.current
63
+ : committedFiberRef.current?.alternate) ??
64
+ committedFiberRef.current;
65
+
66
+ renderedFiberRef.current = fiber;
67
+
68
+ React.useEffect(() => {
69
+ committedFiberRef.current = fiber;
70
+ }, [fiber]);
71
+
72
+ return fiber ?? undefined;
73
+ };
74
+
75
+ export const useFiber =
76
+ typeof useSyncExternalStore === "function" ? useFiberWithExternalStore : useFiberWithReducer;
@@ -1,13 +1,13 @@
1
- import { Fiber } from "../react-internals/index.js";
1
+ import type { Fiber } from "../react-internals/index.js";
2
2
  import { getDisplayName } from "../core.js";
3
- import { getRawParentStack } from "./owner-stack.js";
3
+ import { getDefinitionFrameFromOwnedChild, getRawParentStack } from "./owner-stack.js";
4
4
  import {
5
5
  getSourceContentFromSourceMap,
6
6
  getSourceFromSourceMap,
7
7
  getSourceMap,
8
8
  type SourceFetch,
9
9
  } from "./symbolication.js";
10
- import { StackFrame } from "./parse-stack.js";
10
+ import type { StackFrame } from "./parse-stack.js";
11
11
 
12
12
  const COMPONENT_DECLARATION_PATTERNS = [
13
13
  /(?:^|export\s+)(?:const|let|var)\s+(\w+)\s*=/,
@@ -25,9 +25,6 @@ const findComponentDeclarationOnLine = (line: string): string | null => {
25
25
 
26
26
  const MAX_DECLARATION_LINE_DISTANCE = 5;
27
27
 
28
- // the frame points inside the component, so the nearest declaration wins;
29
- // searching by pattern over the whole window instead would let an adjacent
30
- // component's declaration shadow the right one
31
28
  const extractComponentNameFromSource = (
32
29
  sourceContent: string,
33
30
  lineNumber: number,
@@ -59,7 +56,9 @@ export const getDisplayNameFromSource = async (
59
56
  cache = true,
60
57
  fetchFn?: SourceFetch,
61
58
  ): Promise<string | null> => {
62
- const stackFrame = getRawParentStack(fiber).find((innerFrame) => innerFrame.fileName);
59
+ const stackFrame =
60
+ getDefinitionFrameFromOwnedChild(fiber) ??
61
+ getRawParentStack(fiber).find((innerFrame) => innerFrame.fileName);
63
62
 
64
63
  if (!stackFrame?.fileName) {
65
64
  return getDisplayName(fiber.type);
@@ -86,11 +85,13 @@ export const getDisplayNameFromSource = async (
86
85
  }
87
86
 
88
87
  const sourceContent = getSourceContentFromSourceMap(bundleSourceMap, source.fileName);
89
- if (!sourceContent) {
90
- return getDisplayName(fiber.type);
88
+ const extractedName = sourceContent
89
+ ? extractComponentNameFromSource(sourceContent, source.lineNumber)
90
+ : null;
91
+
92
+ if (extractedName) {
93
+ return extractedName;
91
94
  }
92
95
 
93
- return (
94
- extractComponentNameFromSource(sourceContent, source.lineNumber) ?? getDisplayName(fiber.type)
95
- );
96
+ return source.functionName ?? getDisplayName(fiber.type);
96
97
  };