bippy 0.7.2 → 0.7.3-dev.1d4f35b

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/rdt-hook.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // This module must load before React so renderers can inject into the hook.
2
2
 
3
- import type { ReactDevToolsGlobalHook, ReactRenderer } from "./react-internals/index.js";
3
+ import type { FiberRoot, ReactDevToolsGlobalHook, ReactRenderer } from "./react-internals/index.js";
4
4
 
5
5
  export interface Unsubscribe extends Disposable {
6
6
  (): void;
@@ -14,8 +14,17 @@ interface RendererInjectListener {
14
14
  (renderer: ReactRenderer): void;
15
15
  }
16
16
 
17
+ interface RendererInjectSubscription {
18
+ listener: RendererInjectListener;
19
+ target: ReactDevToolsTarget;
20
+ }
21
+
17
22
  interface RDTHookReplaceListener {
18
- (rdtHook: ReactDevToolsGlobalHook): void;
23
+ (rdtHook: ReactDevToolsGlobalHook, target: ReactDevToolsTarget): void;
24
+ }
25
+
26
+ export interface ReactDevToolsTarget {
27
+ __REACT_DEVTOOLS_GLOBAL_HOOK__?: ReactDevToolsGlobalHook;
19
28
  }
20
29
 
21
30
  export const version = process.env.VERSION;
@@ -25,9 +34,20 @@ const objectDefineProperty = Object.defineProperty;
25
34
 
26
35
  const noOp = (): void => {};
27
36
 
37
+ const getTargetHook = (target: ReactDevToolsTarget): ReactDevToolsGlobalHook | undefined =>
38
+ target.__REACT_DEVTOOLS_GLOBAL_HOOK__;
39
+
28
40
  export const createUnsubscribe = (unsubscribe: () => void): Unsubscribe =>
29
41
  Object.assign(unsubscribe, { [Symbol.dispose]: unsubscribe });
30
42
 
43
+ export const isFiberRootUnmounted = (fiberRoot: FiberRoot): boolean => {
44
+ const rootState = fiberRoot.current.memoizedState;
45
+ return (
46
+ rootState === null ||
47
+ (rootState !== undefined && (rootState.element === null || rootState.element === undefined))
48
+ );
49
+ };
50
+
31
51
  const checkDCE = (functionToCheck: unknown): void => {
32
52
  try {
33
53
  const code = Function.prototype.toString.call(functionToCheck);
@@ -47,7 +67,7 @@ const checkDCE = (functionToCheck: unknown): void => {
47
67
  export const isRealReactDevtools = (
48
68
  rdtHook: ReactDevToolsGlobalHook | undefined | null = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__,
49
69
  ): boolean => {
50
- return Boolean(rdtHook && "getFiberRoots" in rdtHook);
70
+ return Boolean(rdtHook && "getFiberRoots" in rdtHook && rdtHook._isBippyHook !== true);
51
71
  };
52
72
 
53
73
  // HACK: react-refresh's inject wrapper is the only stock hook whose parameter is named
@@ -69,53 +89,63 @@ export const _onActiveListeners = new Set<ActiveListener>();
69
89
 
70
90
  export const _renderers = new Set<ReactRenderer>();
71
91
 
72
- const rendererInjectListeners = new Set<RendererInjectListener>();
92
+ const activeListenerTargets = new WeakMap<ActiveListener, Set<ReactDevToolsTarget>>();
93
+ const rendererInjectSubscriptions = new Set<RendererInjectSubscription>();
73
94
  const rdtHookReplaceListeners = new Set<RDTHookReplaceListener>();
74
- // HACK: Hook replacement can leave an old inject wrapper in the call chain, so notifications must be deduplicated.
75
- const notifiedRenderers = new WeakSet<ReactRenderer>();
76
- const notifyingInjectByHook = new WeakMap<
77
- ReactDevToolsGlobalHook,
78
- ReactDevToolsGlobalHook["inject"]
79
- >();
80
-
81
- const notifyActiveListeners = (): void => {
82
- for (const listener of _onActiveListeners) {
83
- listener();
95
+ const notifiedRenderersByTarget = new WeakMap<ReactDevToolsTarget, WeakSet<ReactRenderer>>();
96
+
97
+ const addActiveListener = (listener: ActiveListener, target: ReactDevToolsTarget): void => {
98
+ _onActiveListeners.add(listener);
99
+ const targets = activeListenerTargets.get(listener) ?? new Set<ReactDevToolsTarget>();
100
+ targets.add(target);
101
+ activeListenerTargets.set(listener, targets);
102
+ };
103
+
104
+ export const removeActiveListener = (
105
+ listener: ActiveListener,
106
+ target: ReactDevToolsTarget,
107
+ ): void => {
108
+ const targets = activeListenerTargets.get(listener);
109
+ targets?.delete(target);
110
+ if (!targets || targets.size === 0) {
111
+ activeListenerTargets.delete(listener);
112
+ _onActiveListeners.delete(listener);
84
113
  }
85
114
  };
86
115
 
87
- const notifyRendererInjectListeners = (renderer: ReactRenderer): void => {
88
- for (const listener of rendererInjectListeners) {
89
- listener(renderer);
116
+ const notifyActiveListeners = (target: ReactDevToolsTarget): void => {
117
+ for (const listener of _onActiveListeners) {
118
+ if (activeListenerTargets.get(listener)?.has(target)) listener();
90
119
  }
91
120
  };
92
121
 
93
- const notifyRDTHookReplaceListeners = (rdtHook: ReactDevToolsGlobalHook): void => {
94
- for (const listener of rdtHookReplaceListeners) {
95
- listener(rdtHook);
122
+ const notifyRendererInjectListeners = (
123
+ target: ReactDevToolsTarget,
124
+ renderer: ReactRenderer,
125
+ ): void => {
126
+ for (const subscription of rendererInjectSubscriptions) {
127
+ if (subscription.target === target) subscription.listener(renderer);
96
128
  }
97
129
  };
98
130
 
99
- const setRendererInjectDispatcher = (rdtHook: ReactDevToolsGlobalHook): void => {
100
- if (rdtHook.inject === notifyingInjectByHook.get(rdtHook)) return;
101
- const previousInject = rdtHook.inject;
102
- const nextInject = (renderer: ReactRenderer) => {
103
- const rendererId = previousInject.call(rdtHook, renderer);
104
- if (!notifiedRenderers.has(renderer)) {
105
- notifiedRenderers.add(renderer);
106
- notifyRendererInjectListeners(renderer);
107
- }
108
- return rendererId;
109
- };
110
- rdtHook.inject = nextInject;
111
- notifyingInjectByHook.set(rdtHook, nextInject);
131
+ const notifyRDTHookReplaceListeners = (
132
+ rdtHook: ReactDevToolsGlobalHook,
133
+ target: ReactDevToolsTarget,
134
+ ): void => {
135
+ for (const listener of rdtHookReplaceListeners) {
136
+ listener(rdtHook, target);
137
+ }
112
138
  };
113
139
 
114
- export const onRendererInject = (listener: RendererInjectListener): Unsubscribe => {
115
- setRendererInjectDispatcher(getRDTHook());
116
- rendererInjectListeners.add(listener);
140
+ export const onRendererInject = (
141
+ listener: RendererInjectListener,
142
+ target: ReactDevToolsTarget = globalThis,
143
+ ): Unsubscribe => {
144
+ getRDTHook(undefined, target);
145
+ const subscription = { listener, target };
146
+ rendererInjectSubscriptions.add(subscription);
117
147
  return createUnsubscribe(() => {
118
- rendererInjectListeners.delete(listener);
148
+ rendererInjectSubscriptions.delete(subscription);
119
149
  });
120
150
  };
121
151
 
@@ -126,8 +156,13 @@ export const onRDTHookReplace = (listener: RDTHookReplaceListener): Unsubscribe
126
156
  });
127
157
  };
128
158
 
159
+ // A Map built in another realm (an iframe target) fails instanceof, so replacing it
160
+ // would discard that realm's renderer registry. Object.prototype.toString is realm-agnostic.
161
+ export const isRendererMap = (value: unknown): value is Map<number, ReactRenderer> =>
162
+ Object.prototype.toString.call(value) === "[object Map]";
163
+
129
164
  const getRendererMap = (rdtHook: ReactDevToolsGlobalHook): Map<number, ReactRenderer> => {
130
- if (!(rdtHook.renderers instanceof Map)) {
165
+ if (!isRendererMap(rdtHook.renderers)) {
131
166
  rdtHook.renderers = new Map();
132
167
  }
133
168
  return rdtHook.renderers;
@@ -135,6 +170,7 @@ const getRendererMap = (rdtHook: ReactDevToolsGlobalHook): Map<number, ReactRend
135
170
 
136
171
  const trackInjectedRenderer = (
137
172
  rdtHook: ReactDevToolsGlobalHook,
173
+ target: ReactDevToolsTarget,
138
174
  renderers: Map<number, ReactRenderer>,
139
175
  rendererId: number,
140
176
  renderer: ReactRenderer,
@@ -143,28 +179,47 @@ const trackInjectedRenderer = (
143
179
  _renderers.add(renderer);
144
180
  if (!rdtHook._instrumentationIsActive) {
145
181
  rdtHook._instrumentationIsActive = true;
146
- notifyActiveListeners();
182
+ notifyActiveListeners(target);
147
183
  }
184
+ const notifiedRenderers = notifiedRenderersByTarget.get(target) ?? new WeakSet<ReactRenderer>();
185
+ notifiedRenderersByTarget.set(target, notifiedRenderers);
186
+ if (notifiedRenderers.has(renderer)) return;
187
+ notifiedRenderers.add(renderer);
188
+ notifyRendererInjectListeners(target, renderer);
148
189
  };
149
190
 
150
- export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHook => {
151
- if (onActive) {
152
- _onActiveListeners.add(onActive);
153
- }
191
+ export const installRDTHook = (
192
+ onActive?: ActiveListener,
193
+ target: ReactDevToolsTarget = globalThis,
194
+ ): ReactDevToolsGlobalHook => {
195
+ if (onActive) addActiveListener(onActive, target);
154
196
  const renderers = new Map<number, ReactRenderer>();
197
+ const fiberRoots = new Map<number, Set<FiberRoot>>();
198
+ // Callers register roots by mutating the returned set, so it must be memoized.
199
+ const getRendererFiberRoots = (rendererId: number): Set<FiberRoot> => {
200
+ const rendererRoots = fiberRoots.get(rendererId) ?? new Set<FiberRoot>();
201
+ fiberRoots.set(rendererId, rendererRoots);
202
+ return rendererRoots;
203
+ };
155
204
  let rendererIdCounter = 0;
156
205
  let rdtHook: ReactDevToolsGlobalHook = {
157
206
  _instrumentationIsActive: false,
158
207
  _instrumentationSource: BIPPY_INSTRUMENTATION_STRING,
208
+ _isBippyHook: true,
159
209
  checkDCE,
210
+ getFiberRoots: getRendererFiberRoots,
160
211
  hasUnsupportedRendererAttached: false,
161
212
  inject: (renderer) => {
162
213
  const nextRendererId = ++rendererIdCounter;
163
- trackInjectedRenderer(rdtHook, renderers, nextRendererId, renderer);
214
+ trackInjectedRenderer(rdtHook, target, renderers, nextRendererId, renderer);
164
215
  return nextRendererId;
165
216
  },
166
217
  on: noOp,
167
- onCommitFiberRoot: noOp,
218
+ onCommitFiberRoot: (rendererId, fiberRoot) => {
219
+ const rendererRoots = getRendererFiberRoots(rendererId);
220
+ if (isFiberRootUnmounted(fiberRoot)) rendererRoots.delete(fiberRoot);
221
+ else rendererRoots.add(fiberRoot);
222
+ },
168
223
  onCommitFiberUnmount: noOp,
169
224
  onPostCommitFiberRoot: noOp,
170
225
  renderers,
@@ -172,13 +227,11 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
172
227
  supportsFlight: true,
173
228
  };
174
229
  try {
175
- objectDefineProperty(globalThis, "__REACT_DEVTOOLS_GLOBAL_HOOK__", {
230
+ objectDefineProperty(target, "__REACT_DEVTOOLS_GLOBAL_HOOK__", {
176
231
  configurable: true,
177
232
  enumerable: true,
178
- get() {
179
- return rdtHook;
180
- },
181
- set(newHook) {
233
+ get: () => rdtHook,
234
+ set: (newHook: ReactDevToolsGlobalHook | undefined) => {
182
235
  if (newHook && typeof newHook === "object") {
183
236
  const ourRenderers = rdtHook.renderers;
184
237
  rdtHook = newHook;
@@ -188,13 +241,13 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
188
241
  nextRenderers.set(rendererId, renderer);
189
242
  });
190
243
  if (ourRenderers.size > 0 || rdtHookReplaceListeners.size > 0) {
191
- patchRDTHook(onActive);
244
+ patchRDTHook(onActive, target);
192
245
  }
193
- notifyRDTHookReplaceListeners(rdtHook);
246
+ notifyRDTHookReplaceListeners(rdtHook, target);
194
247
  }
195
248
  },
196
249
  });
197
- if (typeof window !== "undefined") {
250
+ if (target === globalThis && typeof window !== "undefined") {
198
251
  // HACK: The DevTools extension uses hasOwnProperty to decide whether it may replace an earlier hook.
199
252
  const originalWindowHasOwnProperty = window.hasOwnProperty.bind(window);
200
253
  const originalHasOwnPropertyDescriptor = Object.getOwnPropertyDescriptor(
@@ -224,17 +277,18 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
224
277
  });
225
278
  }
226
279
  } catch {
227
- patchRDTHook(onActive);
280
+ patchRDTHook(onActive, target);
228
281
  }
229
282
  return rdtHook;
230
283
  };
231
284
 
232
- export const patchRDTHook = (onActive?: ActiveListener): void => {
233
- if (onActive) {
234
- _onActiveListeners.add(onActive);
235
- }
285
+ export const patchRDTHook = (
286
+ onActive?: ActiveListener,
287
+ target: ReactDevToolsTarget = globalThis,
288
+ ): void => {
289
+ if (onActive) addActiveListener(onActive, target);
236
290
  let didNotifyActiveListeners = false;
237
- const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
291
+ const rdtHook = getTargetHook(target);
238
292
  if (!rdtHook) return;
239
293
  const renderers = getRendererMap(rdtHook);
240
294
  if (!rdtHook._instrumentationSource) {
@@ -252,20 +306,20 @@ export const patchRDTHook = (onActive?: ActiveListener): void => {
252
306
  if (renderers.size) {
253
307
  renderers.forEach((renderer) => _renderers.add(renderer));
254
308
  rdtHook._instrumentationIsActive = true;
255
- notifyActiveListeners();
309
+ notifyActiveListeners(target);
256
310
  didNotifyActiveListeners = true;
257
311
  } else if (!isReactDevtools && isReactRefresh(rdtHook)) {
258
312
  // HACK: react-refresh's stub inject never records renderers, so a React app
259
313
  // that injected before bippy loaded is undetectable through the renderers map.
260
314
  // A react-refresh hook implies a dev renderer, so activate immediately.
261
315
  rdtHook._instrumentationIsActive = true;
262
- notifyActiveListeners();
316
+ notifyActiveListeners(target);
263
317
  didNotifyActiveListeners = true;
264
318
  }
265
319
  const previousInject = rdtHook.inject;
266
320
  rdtHook.inject = (renderer) => {
267
321
  const rendererId = previousInject.call(rdtHook, renderer);
268
- trackInjectedRenderer(rdtHook, renderers, rendererId, renderer);
322
+ trackInjectedRenderer(rdtHook, target, renderers, rendererId, renderer);
269
323
  return rendererId;
270
324
  };
271
325
  }
@@ -274,18 +328,20 @@ export const patchRDTHook = (onActive?: ActiveListener): void => {
274
328
  }
275
329
  };
276
330
 
277
- export const hasRDTHook = (): boolean => {
278
- return Object.hasOwn(globalThis, "__REACT_DEVTOOLS_GLOBAL_HOOK__");
279
- };
331
+ export const hasRDTHook = (target: ReactDevToolsTarget = globalThis): boolean =>
332
+ Object.hasOwn(target, "__REACT_DEVTOOLS_GLOBAL_HOOK__");
280
333
 
281
334
  /**
282
335
  * Returns the current React DevTools global hook.
283
336
  */
284
- export const getRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHook => {
285
- if (!hasRDTHook()) {
286
- return installRDTHook(onActive);
337
+ export const getRDTHook = (
338
+ onActive?: ActiveListener,
339
+ target: ReactDevToolsTarget = globalThis,
340
+ ): ReactDevToolsGlobalHook => {
341
+ if (!hasRDTHook(target)) {
342
+ return installRDTHook(onActive, target);
287
343
  }
288
344
 
289
- patchRDTHook(onActive);
290
- return globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ ?? installRDTHook(onActive);
345
+ patchRDTHook(onActive, target);
346
+ return getTargetHook(target) ?? installRDTHook(onActive, target);
291
347
  };
@@ -31,12 +31,30 @@ export interface ServerComponentInfo {
31
31
  env?: string;
32
32
  owner?: Fiber | ServerComponentInfo | null;
33
33
  debugStack?: Error | null;
34
+ debugLocation?: Error | null;
35
+ }
36
+
37
+ export interface ReactIOInfo {
38
+ name: string;
39
+ start: number;
40
+ end: number;
41
+ byteSize?: number;
42
+ value?: Promise<unknown> | null;
43
+ env?: string;
44
+ owner?: ServerComponentInfo | null;
45
+ stack?: unknown[] | null;
46
+ debugStack?: Error | null;
34
47
  }
35
48
 
36
49
  export interface ReactDebugInfo {
37
- debugLocation?: unknown;
50
+ awaited?: ReactIOInfo;
51
+ debugLocation?: Error | null;
38
52
  env?: string;
39
53
  name?: string;
54
+ owner?: ServerComponentInfo | null;
55
+ stack?: unknown[] | null;
56
+ debugStack?: Error | null;
57
+ time?: number;
40
58
  }
41
59
 
42
60
  export interface ReactMemoCache {
@@ -71,10 +89,10 @@ export interface Fiber<T = unknown> extends Omit<
71
89
  | "tag"
72
90
  | "updateQueue"
73
91
  > {
74
- _debugInfo?: ReactDebugInfo[];
92
+ _debugInfo?: ReactDebugInfo[] | null;
75
93
  _debugOwner?: Fiber | ServerComponentInfo | null;
76
94
  _debugSource?: FiberDebugSource | null;
77
- _debugStack?: Error & { stack: string };
95
+ _debugStack?: Error | null;
78
96
  alternate: Fiber | null;
79
97
  child: Fiber | null;
80
98
  deletions: Fiber[] | null;
@@ -96,6 +114,8 @@ export interface HostFiber<T = unknown> extends Fiber<T> {
96
114
 
97
115
  export interface FiberRoot {
98
116
  current: Fiber;
117
+ effectDuration?: number;
118
+ passiveEffectDuration?: number;
99
119
  }
100
120
 
101
121
  export interface MemoizedState {
@@ -115,6 +135,7 @@ export interface ReactDevToolsEventHandler {
115
135
  export interface ReactDevToolsGlobalHook {
116
136
  _instrumentationIsActive?: boolean;
117
137
  _instrumentationSource?: string;
138
+ _isBippyHook?: boolean;
118
139
  checkDCE: (fn: unknown) => void;
119
140
  emit?: (event: string, data?: unknown) => void;
120
141
  getFiberRoots?: (rendererID: number) => Set<FiberRoot>;
@@ -149,14 +170,20 @@ export type RendererDispatcherRef = CurrentDispatcherRef | LegacyDispatcherRef;
149
170
 
150
171
  export interface ReactRenderer extends Omit<
151
172
  ReactReconciler.DevToolsConfig<unknown, unknown, unknown>,
152
- "findFiberByHostInstance"
173
+ "bundleType" | "findFiberByHostInstance"
153
174
  > {
175
+ bundleType: number;
154
176
  currentDispatcherRef?: RendererDispatcherRef | null;
155
177
  findFiberByHostInstance?: (hostInstance: unknown) => Fiber | null;
156
178
  getCurrentFiber?: () => Fiber | null;
157
179
  overrideContext?: (fiber: Fiber, contextType: unknown, path: string[], value: unknown) => void;
158
180
 
159
- overrideHookState?: (fiber: Fiber, id: number, path: string[], value: unknown) => void;
181
+ overrideHookState?: (
182
+ fiber: Fiber,
183
+ id: number,
184
+ path: Array<number | string>,
185
+ value: unknown,
186
+ ) => void;
160
187
  overrideHookStateDeletePath?: (fiber: Fiber, id: number, path: Array<number | string>) => void;
161
188
  overrideHookStateRenamePath?: (
162
189
  fiber: Fiber,
@@ -164,7 +191,7 @@ export interface ReactRenderer extends Omit<
164
191
  oldPath: Array<number | string>,
165
192
  newPath: Array<number | string>,
166
193
  ) => void;
167
- overrideProps?: (fiber: Fiber, path: string[], value: unknown) => void;
194
+ overrideProps?: (fiber: Fiber, path: Array<number | string>, value: unknown) => void;
168
195
  overridePropsDeletePath?: (fiber: Fiber, path: Array<number | string>) => void;
169
196
  overridePropsRenamePath?: (
170
197
  fiber: Fiber,
package/src/react.ts CHANGED
@@ -38,6 +38,7 @@ const captureFiberFromHook = (useCaptureHook: () => void): Fiber | null => {
38
38
  };
39
39
 
40
40
  const useExternalStoreCapture = (): void => {
41
+ if (typeof useSyncExternalStore !== "function") return;
41
42
  Reflect.apply(useSyncExternalStore, React, [
42
43
  subscribeToEmptyStore,
43
44
  readEmptySnapshot,
@@ -25,9 +25,7 @@ export const BUNDLED_FILE_PATTERN_REGEX =
25
25
 
26
26
  export const QUERY_PARAMETER_PATTERN_REGEX = /^\?[\w~.-]+(?:=[^&#]*)?(?:&[\w~.-]+(?:=[^&#]*)?)*$/;
27
27
 
28
- export const SERVER_FRAME_MARKER = "(at Server)";
29
-
30
- export const SERVER_ENV_PATTERN = /\(at [^)]+\)$/;
28
+ export const SERVER_ENV_PATTERN = /\[[^\]]+\]$/;
31
29
 
32
30
  export const REACT_STACK_BOTTOM_FRAME_PATTERNS = [
33
31
  "react_stack_bottom_frame",
@@ -6,6 +6,7 @@ import {
6
6
  getSourceFromSourceMap,
7
7
  getSourceMap,
8
8
  type SourceFetch,
9
+ type SourceMapRequestOptions,
9
10
  } from "./symbolication.js";
10
11
  import type { StackFrame } from "./parse-stack.js";
11
12
 
@@ -53,8 +54,9 @@ const extractComponentNameFromSource = (
53
54
 
54
55
  export const getDisplayNameFromSource = async (
55
56
  fiber: Fiber,
56
- cache = true,
57
- fetchFn?: SourceFetch,
57
+ shouldUseCache = true,
58
+ sourceFetch?: SourceFetch,
59
+ requestOptions: SourceMapRequestOptions = {},
58
60
  ): Promise<string | null> => {
59
61
  const stackFrame =
60
62
  getDefinitionFrameFromOwnedChild(fiber) ??
@@ -64,7 +66,12 @@ export const getDisplayNameFromSource = async (
64
66
  return getDisplayName(fiber.type);
65
67
  }
66
68
 
67
- const bundleSourceMap = await getSourceMap(stackFrame.fileName, cache, fetchFn);
69
+ const bundleSourceMap = await getSourceMap(
70
+ stackFrame.fileName,
71
+ shouldUseCache,
72
+ sourceFetch,
73
+ requestOptions,
74
+ );
68
75
 
69
76
  if (!bundleSourceMap) {
70
77
  return getDisplayName(fiber.type);
@@ -19,6 +19,7 @@ import {
19
19
  getSourceMap,
20
20
  symbolicateStack,
21
21
  type SourceFetch,
22
+ type SourceMapRequestOptions,
22
23
  } from "./symbolication.js";
23
24
 
24
25
  export const hasDebugSource = (
@@ -67,10 +68,17 @@ const getUsageFrameFromDebugStack = (fiber: Fiber): StackFrame | null => {
67
68
  return null;
68
69
  };
69
70
 
71
+ export const getRawSource = (fiber: Fiber): FiberSource | null => {
72
+ if (hasDebugSource(fiber)) return fiber._debugSource;
73
+ const stackFrame = getUsageFrameFromDebugStack(fiber) ?? getDefinitionFrameFromOwnedChild(fiber);
74
+ return stackFrame ? toFiberSource(stackFrame) : null;
75
+ };
76
+
70
77
  const getSourceByComponentName = async (
71
78
  fiber: Fiber,
72
- cache: boolean,
73
- fetchFn: SourceFetch | undefined,
79
+ shouldUseCache: boolean,
80
+ sourceFetch: SourceFetch | undefined,
81
+ requestOptions: SourceMapRequestOptions,
74
82
  ): Promise<FiberSource | null> => {
75
83
  const functionName = getDisplayName(fiber.type);
76
84
  if (!functionName) return null;
@@ -83,7 +91,7 @@ const getSourceByComponentName = async (
83
91
  continue;
84
92
  }
85
93
  visitedFileNames.add(fileName);
86
- const sourceMap = await getSourceMap(fileName, cache, fetchFn);
94
+ const sourceMap = await getSourceMap(fileName, shouldUseCache, sourceFetch, requestOptions);
87
95
  if (!sourceMap) continue;
88
96
  const source = getSourceFromSourceMapByFunctionName(sourceMap, functionName);
89
97
  if (source && !source.isIgnoreListed) return toFiberSource(source);
@@ -119,30 +127,42 @@ const getSourceByComponentName = async (
119
127
  */
120
128
  export const getSource = async (
121
129
  fiber: Fiber,
122
- cache = true,
123
- fetchFn?: SourceFetch,
130
+ shouldUseCache = true,
131
+ sourceFetch?: SourceFetch,
132
+ requestOptions: SourceMapRequestOptions = {},
124
133
  ): Promise<FiberSource | null> => {
125
- if (hasDebugSource(fiber)) {
126
- return fiber._debugSource;
127
- }
128
-
129
- const debugStackFrame =
130
- getUsageFrameFromDebugStack(fiber) ?? getDefinitionFrameFromOwnedChild(fiber);
131
- if (debugStackFrame) {
132
- const [symbolicatedFrame] = await symbolicateStack([debugStackFrame], cache, fetchFn);
133
- const debugStackSource = toFiberSource(symbolicatedFrame);
134
- if (debugStackSource) {
135
- return debugStackSource;
136
- }
134
+ if (hasDebugSource(fiber)) return fiber._debugSource;
135
+ const rawSource = getRawSource(fiber);
136
+ if (rawSource) {
137
+ const [symbolicatedFrame] = await symbolicateStack(
138
+ [
139
+ {
140
+ columnNumber: rawSource.columnNumber,
141
+ fileName: rawSource.fileName,
142
+ functionName: rawSource.functionName,
143
+ lineNumber: rawSource.lineNumber,
144
+ },
145
+ ],
146
+ shouldUseCache,
147
+ sourceFetch,
148
+ requestOptions,
149
+ );
150
+ const symbolicatedSource = toFiberSource(symbolicatedFrame);
151
+ if (symbolicatedSource) return symbolicatedSource;
137
152
  }
138
153
 
139
- const parentStackFrames = await getParentStack(fiber, cache, fetchFn);
154
+ const parentStackFrames = await getParentStack(
155
+ fiber,
156
+ shouldUseCache,
157
+ sourceFetch,
158
+ requestOptions,
159
+ );
140
160
  for (const stackFrame of parentStackFrames) {
141
161
  if (stackFrame.fileName) {
142
162
  return toFiberSource(stackFrame);
143
163
  }
144
164
  }
145
- return getSourceByComponentName(fiber, cache, fetchFn);
165
+ return getSourceByComponentName(fiber, shouldUseCache, sourceFetch, requestOptions);
146
166
  };
147
167
 
148
168
  const getPathSegmentCount = (path: string): number => path.split("/").filter(Boolean).length;
@@ -1,5 +1,13 @@
1
- export { formatOwnerStack, getOwnerStack, getParentStack, hasDebugStack } from "./owner-stack.js";
2
- export { getSource, isSourceFile, normalizeFileName } from "./get-source.js";
1
+ export {
2
+ formatOwnerStack,
3
+ getDefinitionFrameFromOwnedChild,
4
+ getFallbackParentStack,
5
+ getOwnerStack,
6
+ getParentStack,
7
+ getRawOwnerStack,
8
+ hasDebugStack,
9
+ } from "./owner-stack.js";
10
+ export { getRawSource, getSource, isSourceFile, normalizeFileName } from "./get-source.js";
3
11
  export {
4
12
  getSourceContentFromSourceMap,
5
13
  getSourceFromSourceMap,
@@ -23,5 +31,11 @@ export {
23
31
  BippySourceMapError,
24
32
  BippyUnsupportedHookError,
25
33
  } from "../errors.js";
26
- export { getFiberHooks, type HookSource, type HooksNode, type HooksTree } from "./inspect-hooks.js";
34
+ export {
35
+ getFiberHooks,
36
+ inspectHooks,
37
+ type HookSource,
38
+ type HooksNode,
39
+ type HooksTree,
40
+ } from "./inspect-hooks.js";
27
41
  export { parseHookNames, type HookNames } from "./parse-hook-names.js";