bippy 0.6.1-dev.b88fcb4 → 0.6.1-dev.f9e6c65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +159 -487
  2. package/dist/core.cjs +1 -1
  3. package/dist/core.d.cts +8 -54
  4. package/dist/core.d.ts +8 -54
  5. package/dist/core.js +1 -1
  6. package/dist/core2.cjs +1 -1
  7. package/dist/core2.d.cts +3 -3
  8. package/dist/core2.d.ts +3 -3
  9. package/dist/core2.js +1 -1
  10. package/dist/errors.d.cts +2 -57
  11. package/dist/errors.d.ts +2 -57
  12. package/dist/index.cjs +1 -1
  13. package/dist/index.d.cts +6 -3
  14. package/dist/index.d.ts +6 -3
  15. package/dist/index.js +1 -1
  16. package/dist/install-hook-only.cjs +1 -1
  17. package/dist/install-hook-only.js +1 -1
  18. package/dist/rdt-hook.cjs +1 -1
  19. package/dist/rdt-hook.js +1 -1
  20. package/dist/source.cjs +12 -13
  21. package/dist/source.d.cts +77 -78
  22. package/dist/source.d.ts +77 -78
  23. package/dist/source.js +12 -13
  24. package/package.json +6 -3
  25. package/src/core.ts +101 -530
  26. package/src/errors.ts +0 -65
  27. package/src/index.ts +1 -0
  28. package/src/install-hook-only.ts +2 -2
  29. package/src/rdt-hook.ts +30 -43
  30. package/src/react-internals/generated/react-work-tags.ts +6 -2
  31. package/src/react-internals/index.ts +4 -4
  32. package/src/react-internals/semver.ts +2 -0
  33. package/src/react-internals/types.ts +0 -83
  34. package/src/react.ts +76 -0
  35. package/src/source/get-display-name-from-source.ts +44 -40
  36. package/src/source/get-source.ts +40 -24
  37. package/src/source/index.ts +2 -0
  38. package/src/source/inspect-hooks.ts +70 -91
  39. package/src/source/owner-stack.ts +60 -89
  40. package/src/source/parse-hook-names.ts +14 -53
  41. package/src/source/parse-stack.ts +14 -31
  42. package/src/source/renderer-dispatchers.ts +30 -0
  43. package/src/source/symbolication.ts +312 -111
  44. package/dist/index.iife.js +0 -9
  45. package/dist/install-hook-only.iife.js +0 -9
package/src/errors.ts CHANGED
@@ -5,55 +5,6 @@ export class BippyError extends Error {
5
5
  }
6
6
  }
7
7
 
8
- export class BippyInstrumentationError extends BippyError {
9
- readonly callbackName: string;
10
- readonly instrumentationName: string;
11
-
12
- constructor(instrumentationName: string, callbackName: string, cause: unknown) {
13
- super(
14
- `Bippy instrumentation "${instrumentationName}" callback "${callbackName}" failed`,
15
- cause,
16
- );
17
- this.name = "BippyInstrumentationError";
18
- this.callbackName = callbackName;
19
- this.instrumentationName = instrumentationName;
20
- }
21
- }
22
-
23
- export class BippyReactDevToolsError extends BippyError {
24
- readonly callbackName: string;
25
-
26
- constructor(callbackName: string, cause: unknown) {
27
- super(`React DevTools callback "${callbackName}" failed`, cause);
28
- this.name = "BippyReactDevToolsError";
29
- this.callbackName = callbackName;
30
- }
31
- }
32
-
33
- export class BippyHookListenerError extends BippyError {
34
- readonly listenerName: string;
35
-
36
- constructor(listenerName: string, cause: unknown) {
37
- super(`Bippy hook listener "${listenerName}" failed`, cause);
38
- this.name = "BippyHookListenerError";
39
- this.listenerName = listenerName;
40
- }
41
- }
42
-
43
- export class BippyHookInstallationError extends BippyError {
44
- constructor(cause: unknown) {
45
- super("Bippy could not install the React DevTools hook", cause);
46
- this.name = "BippyHookInstallationError";
47
- }
48
- }
49
-
50
- export class BippyReactBuildError extends BippyError {
51
- constructor(message: string, cause?: unknown) {
52
- super(message, cause);
53
- this.name = "BippyReactBuildError";
54
- }
55
- }
56
-
57
8
  export class BippyHookInspectionError extends BippyError {
58
9
  constructor(message: string, cause?: unknown) {
59
10
  super(message, cause);
@@ -81,19 +32,3 @@ export class BippySourceMapError extends BippyError {
81
32
  this.name = "BippySourceMapError";
82
33
  }
83
34
  }
84
-
85
- interface BippyErrorFactory {
86
- (cause: unknown): BippyError;
87
- }
88
-
89
- export const runWithBippyError = <Result>(
90
- callback: () => Result,
91
- createError: BippyErrorFactory,
92
- ): Result => {
93
- try {
94
- return callback();
95
- } catch (cause) {
96
- if (cause instanceof BippyError) throw cause;
97
- throw createError(cause);
98
- }
99
- };
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";
@@ -1,3 +1,3 @@
1
- import { safelyInstallRDTHook } from "./rdt-hook.js";
1
+ import { getRDTHook } from "./rdt-hook.js";
2
2
 
3
- safelyInstallRDTHook();
3
+ getRDTHook();
package/src/rdt-hook.ts CHANGED
@@ -1,11 +1,5 @@
1
1
  // This module must load before React so renderers can inject into the hook.
2
2
 
3
- import {
4
- BippyHookInstallationError,
5
- BippyHookListenerError,
6
- BippyReactBuildError,
7
- runWithBippyError,
8
- } from "./errors.js";
9
3
  import type { ReactDevToolsGlobalHook, ReactRenderer } from "./react-internals/index.js";
10
4
 
11
5
  export interface Unsubscribe extends Disposable {
@@ -34,21 +28,16 @@ const noOp = (): void => {};
34
28
  export const createUnsubscribe = (unsubscribe: () => void): Unsubscribe =>
35
29
  Object.assign(unsubscribe, { [Symbol.dispose]: unsubscribe });
36
30
 
37
- const runHookListener = (listenerName: string, callback: () => unknown): void => {
38
- runWithBippyError(callback, (cause) => new BippyHookListenerError(listenerName, cause));
39
- };
40
-
41
31
  const checkDCE = (functionToCheck: unknown): void => {
42
32
  try {
43
33
  const code = Function.prototype.toString.call(functionToCheck);
44
- if (code.includes("^_^")) {
45
- // HACK: React DevTools reports failed dead-code elimination asynchronously.
34
+ if (code.indexOf("^_^") > -1) {
46
35
  setTimeout(() => {
47
- throw new BippyReactBuildError(
36
+ throw new Error(
48
37
  "React is running in production mode, but dead code " +
49
38
  "elimination has not been applied. Read how to correctly " +
50
39
  "configure React for production: " +
51
- "https://reactjs.org/link/perf-use-production-build",
40
+ "https://react.dev/link/perf-use-production-build",
52
41
  );
53
42
  });
54
43
  }
@@ -76,19 +65,19 @@ const notifyingInjectByHook = new WeakMap<
76
65
 
77
66
  const notifyActiveListeners = (): void => {
78
67
  for (const listener of _onActiveListeners) {
79
- runHookListener("onActive", listener);
68
+ listener();
80
69
  }
81
70
  };
82
71
 
83
72
  const notifyRendererInjectListeners = (renderer: ReactRenderer): void => {
84
73
  for (const listener of rendererInjectListeners) {
85
- runHookListener("onRendererInject", () => listener(renderer));
74
+ listener(renderer);
86
75
  }
87
76
  };
88
77
 
89
78
  const notifyRDTHookReplaceListeners = (rdtHook: ReactDevToolsGlobalHook): void => {
90
79
  for (const listener of rdtHookReplaceListeners) {
91
- runHookListener("onRDTHookReplace", () => listener(rdtHook));
80
+ listener(rdtHook);
92
81
  }
93
82
  };
94
83
 
@@ -129,6 +118,20 @@ const getRendererMap = (rdtHook: ReactDevToolsGlobalHook): Map<number, ReactRend
129
118
  return rdtHook.renderers;
130
119
  };
131
120
 
121
+ const trackInjectedRenderer = (
122
+ rdtHook: ReactDevToolsGlobalHook,
123
+ renderers: Map<number, ReactRenderer>,
124
+ rendererId: number,
125
+ renderer: ReactRenderer,
126
+ ): void => {
127
+ renderers.set(rendererId, renderer);
128
+ _renderers.add(renderer);
129
+ if (!rdtHook._instrumentationIsActive) {
130
+ rdtHook._instrumentationIsActive = true;
131
+ notifyActiveListeners();
132
+ }
133
+ };
134
+
132
135
  export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHook => {
133
136
  if (onActive) {
134
137
  _onActiveListeners.add(onActive);
@@ -142,12 +145,7 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
142
145
  hasUnsupportedRendererAttached: false,
143
146
  inject: (renderer) => {
144
147
  const nextRendererId = ++rendererIdCounter;
145
- renderers.set(nextRendererId, renderer);
146
- _renderers.add(renderer);
147
- if (!rdtHook._instrumentationIsActive) {
148
- rdtHook._instrumentationIsActive = true;
149
- notifyActiveListeners();
150
- }
148
+ trackInjectedRenderer(rdtHook, renderers, nextRendererId, renderer);
151
149
  return nextRendererId;
152
150
  },
153
151
  on: noOp,
@@ -170,12 +168,10 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
170
168
  const ourRenderers = rdtHook.renderers;
171
169
  rdtHook = newHook;
172
170
  const nextRenderers = getRendererMap(rdtHook);
173
- if (ourRenderers.size > 0) {
174
- ourRenderers.forEach((renderer, rendererId) => {
175
- _renderers.add(renderer);
176
- nextRenderers.set(rendererId, renderer);
177
- });
178
- }
171
+ ourRenderers.forEach((renderer, rendererId) => {
172
+ _renderers.add(renderer);
173
+ nextRenderers.set(rendererId, renderer);
174
+ });
179
175
  if (ourRenderers.size > 0 || rdtHookReplaceListeners.size > 0) {
180
176
  patchRDTHook(onActive);
181
177
  }
@@ -200,14 +196,14 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
200
196
  };
201
197
  objectDefineProperty(window, "hasOwnProperty", {
202
198
  configurable: true,
203
- value: (...propertyKeys: [PropertyKey]) => {
204
- if (!didRunHasOwnPropertyHack && propertyKeys[0] === "__REACT_DEVTOOLS_GLOBAL_HOOK__") {
199
+ value: (propertyKey: PropertyKey) => {
200
+ if (!didRunHasOwnPropertyHack && propertyKey === "__REACT_DEVTOOLS_GLOBAL_HOOK__") {
205
201
  didRunHasOwnPropertyHack = true;
206
202
  restoreWindowHasOwnProperty();
207
203
  globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ = undefined;
208
204
  return false;
209
205
  }
210
- return originalWindowHasOwnProperty(...propertyKeys);
206
+ return originalWindowHasOwnProperty(propertyKey);
211
207
  },
212
208
  writable: true,
213
209
  });
@@ -247,17 +243,12 @@ export const patchRDTHook = (onActive?: ActiveListener): void => {
247
243
  const previousInject = rdtHook.inject;
248
244
  rdtHook.inject = (renderer) => {
249
245
  const rendererId = previousInject.call(rdtHook, renderer);
250
- _renderers.add(renderer);
251
- renderers.set(rendererId, renderer);
252
- if (!rdtHook._instrumentationIsActive) {
253
- rdtHook._instrumentationIsActive = true;
254
- notifyActiveListeners();
255
- }
246
+ trackInjectedRenderer(rdtHook, renderers, rendererId, renderer);
256
247
  return rendererId;
257
248
  };
258
249
  }
259
250
  if (!didNotifyActiveListeners && (renderers.size || rdtHook._instrumentationIsActive)) {
260
- if (onActive) runHookListener("onActive", onActive);
251
+ onActive?.();
261
252
  }
262
253
  };
263
254
 
@@ -276,7 +267,3 @@ export const getRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHook =
276
267
  patchRDTHook(onActive);
277
268
  return globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ ?? installRDTHook(onActive);
278
269
  };
279
-
280
- export const safelyInstallRDTHook = (): void => {
281
- runWithBippyError(getRDTHook, (cause) => new BippyHookInstallationError(cause));
282
- };
@@ -254,6 +254,7 @@ const reactWorkTagsByVersion = defineReactWorkTags({
254
254
  export type ReactWorkTagVersion = keyof typeof reactWorkTagsByVersion;
255
255
 
256
256
  export interface GetReactWorkTags {
257
+ (): Readonly<ReactWorkTagMap>;
257
258
  <Version extends ReactWorkTagVersion>(
258
259
  reactVersion: Version,
259
260
  ): Readonly<(typeof reactWorkTagsByVersion)[Version]>;
@@ -302,10 +303,13 @@ const reactWorkTagRanges: ReactWorkTagRange[] = [
302
303
  },
303
304
  ];
304
305
 
305
- export const getReactWorkTags: GetReactWorkTags = (reactVersion: string) => {
306
+ const defaultReactWorkTags = reactWorkTagsByVersion["17.0.2"];
307
+
308
+ export const getReactWorkTags: GetReactWorkTags = (reactVersion?: string) => {
309
+ if (reactVersion === undefined) return defaultReactWorkTags;
306
310
  for (const range of reactWorkTagRanges) {
307
311
  const comparison = compareSemver(reactVersion, range.minimumVersion);
308
- if (comparison === null) return reactWorkTagsByVersion["17.0.2"];
312
+ if (comparison === null) return defaultReactWorkTags;
309
313
  if (comparison === 1 || (comparison === 0 && !range.isMinimumExcluded)) {
310
314
  return range.workTags;
311
315
  }
@@ -1,6 +1,6 @@
1
1
  import { getReactWorkTags, ReactFiberFlags } from "./generated/react-work-tags.js";
2
2
  import type { ReactWorkTagMap } from "./generated/react-work-tags.js";
3
- import { compareSemver } from "./semver.js";
3
+ import { isSemver } from "./semver.js";
4
4
  import type { Fiber, ReactRenderer } from "./types.js";
5
5
 
6
6
  export { compareSemver } from "./semver.js";
@@ -18,17 +18,17 @@ export type {
18
18
  } from "./generated/react-work-tags.js";
19
19
  export * from "./types.js";
20
20
 
21
- const defaultReactWorkTags = getReactWorkTags("");
21
+ const defaultReactWorkTags = getReactWorkTags();
22
22
  const fiberReactWorkTags = new WeakMap<Fiber, Readonly<ReactWorkTagMap>>();
23
23
 
24
24
  export const getReactWorkTagsForRenderer = (
25
25
  renderer?: ReactRenderer | null,
26
26
  ): Readonly<ReactWorkTagMap> => {
27
27
  const reconcilerVersion = renderer?.reconcilerVersion;
28
- if (reconcilerVersion && compareSemver(reconcilerVersion, reconcilerVersion) === 0) {
28
+ if (reconcilerVersion && isSemver(reconcilerVersion)) {
29
29
  return getReactWorkTags(reconcilerVersion);
30
30
  }
31
- return getReactWorkTags(renderer?.version ?? "");
31
+ return renderer?.version ? getReactWorkTags(renderer.version) : defaultReactWorkTags;
32
32
  };
33
33
 
34
34
  export const setReactWorkTagsForFiber = (fiber: Fiber, renderer?: ReactRenderer): void => {
@@ -9,6 +9,8 @@ const SEMVER_PATTERN =
9
9
  /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
10
10
  const NUMERIC_IDENTIFIER_PATTERN = /^\d+$/;
11
11
 
12
+ export const isSemver = (version: string): boolean => parseSemver(version) !== null;
13
+
12
14
  const parseSemver = (version: string): SemanticVersion | null => {
13
15
  const match = SEMVER_PATTERN.exec(version);
14
16
  if (!match) return null;
@@ -2,84 +2,12 @@ import type { ReactNode } from "react";
2
2
  import type ReactReconciler from "react-reconciler";
3
3
  import type { HostWorkTag, ReactWorkTag } from "./generated/react-work-tags.js";
4
4
 
5
- export type BundleType = ReactReconciler.BundleType;
6
- export type Flags = ReactReconciler.Flags;
7
- export type HookType = ReactReconciler.HookType;
8
- export type LanePriority = ReactReconciler.LanePriority;
9
- export type Lanes = ReactReconciler.Lanes;
10
- export type MutableSource = ReactReconciler.MutableSource;
11
- export type OpaqueHandle = ReactReconciler.OpaqueHandle;
12
- export type OpaqueRoot = ReactReconciler.OpaqueRoot;
13
- export type React$AbstractComponent<
14
- Config,
15
- Instance = unknown,
16
- > = ReactReconciler.React$AbstractComponent<Config, Instance>;
17
- export type RootTag = ReactReconciler.RootTag;
18
- export type TypeOfMode = ReactReconciler.TypeOfMode;
19
5
  export type WorkTag = ReactReconciler.WorkTag | ReactWorkTag;
20
6
 
21
- export interface HostConfig<
22
- Type = unknown,
23
- HostProps = unknown,
24
- Container = unknown,
25
- Instance = unknown,
26
- TextInstance = unknown,
27
- SuspenseInstance = unknown,
28
- HydratableInstance = unknown,
29
- FormInstance = unknown,
30
- PublicInstance = unknown,
31
- HostContext = unknown,
32
- ChildSet = unknown,
33
- TimeoutHandle = unknown,
34
- NoTimeout = unknown,
35
- TransitionStatus = unknown,
36
- > extends ReactReconciler.HostConfig<
37
- Type,
38
- HostProps,
39
- Container,
40
- Instance,
41
- TextInstance,
42
- SuspenseInstance,
43
- HydratableInstance,
44
- FormInstance,
45
- PublicInstance,
46
- HostContext,
47
- ChildSet,
48
- TimeoutHandle,
49
- NoTimeout,
50
- TransitionStatus
51
- > {}
52
-
53
7
  export interface Source extends ReactReconciler.Source {}
54
- export interface RefObject extends ReactReconciler.RefObject {}
55
- export interface Thenable<T> extends ReactReconciler.Thenable<T> {}
56
8
 
57
9
  export interface ReactContext<T> extends ReactReconciler.ReactContext<T> {}
58
10
 
59
- export interface ReactProviderType<T> extends ReactReconciler.ReactProviderType<T> {}
60
- export interface ReactProvider<T> extends ReactReconciler.ReactProvider<T> {}
61
- export interface ReactConsumer<T> extends ReactReconciler.ReactConsumer<T> {}
62
- export interface ReactPortal extends ReactReconciler.ReactPortal {}
63
-
64
- export interface ComponentSelector extends ReactReconciler.ComponentSelector {}
65
- export interface HasPseudoClassSelector extends ReactReconciler.HasPseudoClassSelector {}
66
- export interface RoleSelector extends ReactReconciler.RoleSelector {}
67
- export interface TextSelector extends ReactReconciler.TextSelector {}
68
- export interface TestNameSelector extends ReactReconciler.TestNameSelector {}
69
- export type Selector = ReactReconciler.Selector;
70
-
71
- export interface DevToolsConfig<
72
- Instance = unknown,
73
- TextInstance = unknown,
74
- RendererInspectionConfig = unknown,
75
- > extends ReactReconciler.DevToolsConfig<Instance, TextInstance, RendererInspectionConfig> {}
76
-
77
- export interface SuspenseHydrationCallbacks<
78
- SuspenseInstance = unknown,
79
- > extends ReactReconciler.SuspenseHydrationCallbacks<SuspenseInstance> {}
80
-
81
- export interface TransitionTracingCallbacks extends ReactReconciler.TransitionTracingCallbacks {}
82
-
83
11
  // HACK: React 17 exposes observedBits while React 18+ exposes memoizedValue on context dependencies.
84
12
  export interface ContextDependency<T> extends Omit<
85
13
  ReactReconciler.ContextDependency<T>,
@@ -98,15 +26,6 @@ export interface Dependencies extends Omit<ReactReconciler.Dependencies, "firstC
98
26
  firstContext: ContextDependency<unknown> | null;
99
27
  }
100
28
 
101
- export interface Effect {
102
- [key: string]: unknown;
103
- create: (...args: unknown[]) => unknown;
104
- deps: null | unknown[];
105
- destroy: ((...args: unknown[]) => unknown) | null;
106
- next: Effect | null;
107
- tag: number;
108
- }
109
-
110
29
  export interface ServerComponentInfo {
111
30
  name?: string;
112
31
  env?: string;
@@ -131,8 +50,6 @@ export interface FiberDebugSource extends Source {
131
50
 
132
51
  export interface FiberUpdateQueue {
133
52
  [key: string]: unknown;
134
- dispatch?: unknown;
135
- lastEffect: Effect | null;
136
53
  memoCache?: ReactMemoCache;
137
54
  }
138
55
 
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,8 +1,29 @@
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 { getParentStack } from "./owner-stack.js";
4
- import { getSourceFromSourceMap, getSourceMap } from "./symbolication.js";
5
- import { StackFrame } from "./parse-stack.js";
3
+ import { getDefinitionFrameFromOwnedChild, getRawParentStack } from "./owner-stack.js";
4
+ import {
5
+ getSourceContentFromSourceMap,
6
+ getSourceFromSourceMap,
7
+ getSourceMap,
8
+ type SourceFetch,
9
+ } from "./symbolication.js";
10
+ import type { StackFrame } from "./parse-stack.js";
11
+
12
+ const COMPONENT_DECLARATION_PATTERNS = [
13
+ /(?:^|export\s+)(?:const|let|var)\s+(\w+)\s*=/,
14
+ /(?:^|export\s+)function\s+(\w+)/,
15
+ /(?:^|export\s+)class\s+(\w+)/,
16
+ ];
17
+
18
+ const findComponentDeclarationOnLine = (line: string): string | null => {
19
+ for (const pattern of COMPONENT_DECLARATION_PATTERNS) {
20
+ const match = line.match(pattern);
21
+ if (match?.[1]) return match[1];
22
+ }
23
+ return null;
24
+ };
25
+
26
+ const MAX_DECLARATION_LINE_DISTANCE = 5;
6
27
 
7
28
  const extractComponentNameFromSource = (
8
29
  sourceContent: string,
@@ -15,27 +36,16 @@ const extractComponentNameFromSource = (
15
36
  return null;
16
37
  }
17
38
 
18
- const startLine = Math.max(0, targetLineIndex - 5);
19
- const endLine = Math.min(lines.length, targetLineIndex + 5);
20
- const contextLines = lines.slice(startLine, endLine).join("\n");
21
-
22
- const arrowFunctionPattern = /(?:^|export\s+)(?:const|let|var)\s+(\w+)\s*=/m;
23
- const functionPattern = /(?:^|export\s+)function\s+(\w+)/m;
24
- const classPattern = /(?:^|export\s+)class\s+(\w+)/m;
25
-
26
- const arrowMatch = contextLines.match(arrowFunctionPattern);
27
- if (arrowMatch?.[1]) {
28
- return arrowMatch[1];
29
- }
30
-
31
- const functionMatch = contextLines.match(functionPattern);
32
- if (functionMatch?.[1]) {
33
- return functionMatch[1];
34
- }
35
-
36
- const classMatch = contextLines.match(classPattern);
37
- if (classMatch?.[1]) {
38
- return classMatch[1];
39
+ for (let lineDistance = 0; lineDistance <= MAX_DECLARATION_LINE_DISTANCE; lineDistance++) {
40
+ const lineIndexes =
41
+ lineDistance === 0
42
+ ? [targetLineIndex]
43
+ : [targetLineIndex - lineDistance, targetLineIndex + lineDistance];
44
+ for (const lineIndex of lineIndexes) {
45
+ if (lineIndex < 0 || lineIndex >= lines.length) continue;
46
+ const declarationName = findComponentDeclarationOnLine(lines[lineIndex]);
47
+ if (declarationName) return declarationName;
48
+ }
39
49
  }
40
50
 
41
51
  return null;
@@ -44,10 +54,11 @@ const extractComponentNameFromSource = (
44
54
  export const getDisplayNameFromSource = async (
45
55
  fiber: Fiber,
46
56
  cache = true,
47
- fetchFn?: (url: string) => Promise<Response>,
57
+ fetchFn?: SourceFetch,
48
58
  ): Promise<string | null> => {
49
- const parentStackFrames = await getParentStack(fiber, cache, fetchFn);
50
- const stackFrame = parentStackFrames.filter((innerFrame) => innerFrame.fileName)[0];
59
+ const stackFrame =
60
+ getDefinitionFrameFromOwnedChild(fiber) ??
61
+ getRawParentStack(fiber).find((innerFrame) => innerFrame.fileName);
51
62
 
52
63
  if (!stackFrame?.fileName) {
53
64
  return getDisplayName(fiber.type);
@@ -73,21 +84,14 @@ export const getDisplayNameFromSource = async (
73
84
  return getDisplayName(fiber.type);
74
85
  }
75
86
 
76
- if (!bundleSourceMap.sourcesContent) {
77
- return getDisplayName(fiber.type);
78
- }
79
-
80
- const sourceIndex = bundleSourceMap.sources.indexOf(source.fileName);
81
- if (sourceIndex === -1 || !bundleSourceMap.sourcesContent[sourceIndex]) {
82
- return getDisplayName(fiber.type);
83
- }
84
-
85
- const sourceContent = bundleSourceMap.sourcesContent[sourceIndex];
86
- const extractedName = extractComponentNameFromSource(sourceContent, source.lineNumber);
87
+ const sourceContent = getSourceContentFromSourceMap(bundleSourceMap, source.fileName);
88
+ const extractedName = sourceContent
89
+ ? extractComponentNameFromSource(sourceContent, source.lineNumber)
90
+ : null;
87
91
 
88
92
  if (extractedName) {
89
93
  return extractedName;
90
94
  }
91
95
 
92
- return getDisplayName(fiber.type);
96
+ return source.functionName ?? getDisplayName(fiber.type);
93
97
  };