bippy 0.6.1-dev.d7876ea → 0.6.1-dev.f9a65c0

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 (50) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +211 -445
  3. package/dist/core.cjs +1 -1
  4. package/dist/core.d.cts +25 -75
  5. package/dist/core.d.ts +25 -75
  6. package/dist/core.js +1 -1
  7. package/dist/core2.cjs +1 -1
  8. package/dist/core2.d.cts +11 -3
  9. package/dist/core2.d.ts +11 -3
  10. package/dist/core2.js +1 -1
  11. package/dist/errors.d.cts +33 -59
  12. package/dist/errors.d.ts +33 -59
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +14 -3
  15. package/dist/index.d.ts +14 -3
  16. package/dist/index.js +1 -1
  17. package/dist/install-hook-only.cjs +1 -1
  18. package/dist/install-hook-only.d.cts +8 -0
  19. package/dist/install-hook-only.d.ts +8 -0
  20. package/dist/install-hook-only.js +1 -1
  21. package/dist/rdt-hook.cjs +1 -1
  22. package/dist/rdt-hook.js +1 -1
  23. package/dist/source.cjs +12 -13
  24. package/dist/source.d.cts +85 -77
  25. package/dist/source.d.ts +85 -77
  26. package/dist/source.js +12 -13
  27. package/package.json +6 -3
  28. package/src/core.ts +146 -579
  29. package/src/errors.ts +0 -65
  30. package/src/index.ts +1 -0
  31. package/src/install-hook-only.ts +2 -2
  32. package/src/rdt-hook.ts +39 -45
  33. package/src/{generated/react-work-tags.js → react-internals/generated/react-work-tags.ts} +85 -7
  34. package/src/{react-internals.ts → react-internals/index.ts} +6 -4
  35. package/src/{semver.ts → react-internals/semver.ts} +2 -0
  36. package/src/{types.ts → react-internals/types.ts} +1 -84
  37. package/src/react.ts +76 -0
  38. package/src/source/get-display-name-from-source.ts +44 -40
  39. package/src/source/get-source.ts +42 -26
  40. package/src/source/index.ts +2 -0
  41. package/src/source/inspect-hooks.ts +74 -96
  42. package/src/source/owner-stack.ts +66 -91
  43. package/src/source/parse-hook-names.ts +14 -53
  44. package/src/source/parse-stack.ts +14 -31
  45. package/src/source/renderer-dispatchers.ts +30 -0
  46. package/src/source/symbolication.ts +421 -108
  47. package/dist/index.iife.js +0 -9
  48. package/dist/install-hook-only.iife.js +0 -9
  49. package/src/generated/react-work-tags.d.ts +0 -261
  50. package/src/unsubscribe.ts +0 -17
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,13 +1,10 @@
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
- import type { ReactDevToolsGlobalHook, ReactRenderer } from "./types.js";
10
- import { toUnsubscribe, type Unsubscribe } from "./unsubscribe.js";
3
+ import type { ReactDevToolsGlobalHook, ReactRenderer } from "./react-internals/index.js";
4
+
5
+ export interface Unsubscribe extends Disposable {
6
+ (): void;
7
+ }
11
8
 
12
9
  interface ActiveListener {
13
10
  (): unknown;
@@ -28,20 +25,19 @@ const objectDefineProperty = Object.defineProperty;
28
25
 
29
26
  const noOp = (): void => {};
30
27
 
31
- const runHookListener = (listenerName: string, callback: () => unknown): void =>
32
- runWithBippyError(callback, (cause) => new BippyHookListenerError(listenerName, cause));
28
+ export const createUnsubscribe = (unsubscribe: () => void): Unsubscribe =>
29
+ Object.assign(unsubscribe, { [Symbol.dispose]: unsubscribe });
33
30
 
34
31
  const checkDCE = (functionToCheck: unknown): void => {
35
32
  try {
36
33
  const code = Function.prototype.toString.call(functionToCheck);
37
- if (code.includes("^_^")) {
38
- // HACK: React DevTools reports failed dead-code elimination asynchronously.
34
+ if (code.indexOf("^_^") > -1) {
39
35
  setTimeout(() => {
40
- throw new BippyReactBuildError(
36
+ throw new Error(
41
37
  "React is running in production mode, but dead code " +
42
38
  "elimination has not been applied. Read how to correctly " +
43
39
  "configure React for production: " +
44
- "https://reactjs.org/link/perf-use-production-build",
40
+ "https://react.dev/link/perf-use-production-build",
45
41
  );
46
42
  });
47
43
  }
@@ -69,19 +65,19 @@ const notifyingInjectByHook = new WeakMap<
69
65
 
70
66
  const notifyActiveListeners = (): void => {
71
67
  for (const listener of _onActiveListeners) {
72
- runHookListener("onActive", listener);
68
+ listener();
73
69
  }
74
70
  };
75
71
 
76
72
  const notifyRendererInjectListeners = (renderer: ReactRenderer): void => {
77
73
  for (const listener of rendererInjectListeners) {
78
- runHookListener("onRendererInject", () => listener(renderer));
74
+ listener(renderer);
79
75
  }
80
76
  };
81
77
 
82
78
  const notifyRDTHookReplaceListeners = (rdtHook: ReactDevToolsGlobalHook): void => {
83
79
  for (const listener of rdtHookReplaceListeners) {
84
- runHookListener("onRDTHookReplace", () => listener(rdtHook));
80
+ listener(rdtHook);
85
81
  }
86
82
  };
87
83
 
@@ -103,14 +99,14 @@ const setRendererInjectDispatcher = (rdtHook: ReactDevToolsGlobalHook): void =>
103
99
  export const onRendererInject = (listener: RendererInjectListener): Unsubscribe => {
104
100
  setRendererInjectDispatcher(getRDTHook());
105
101
  rendererInjectListeners.add(listener);
106
- return toUnsubscribe(() => {
102
+ return createUnsubscribe(() => {
107
103
  rendererInjectListeners.delete(listener);
108
104
  });
109
105
  };
110
106
 
111
107
  export const onRDTHookReplace = (listener: RDTHookReplaceListener): Unsubscribe => {
112
108
  rdtHookReplaceListeners.add(listener);
113
- return toUnsubscribe(() => {
109
+ return createUnsubscribe(() => {
114
110
  rdtHookReplaceListeners.delete(listener);
115
111
  });
116
112
  };
@@ -122,6 +118,20 @@ const getRendererMap = (rdtHook: ReactDevToolsGlobalHook): Map<number, ReactRend
122
118
  return rdtHook.renderers;
123
119
  };
124
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
+
125
135
  export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHook => {
126
136
  if (onActive) {
127
137
  _onActiveListeners.add(onActive);
@@ -135,12 +145,7 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
135
145
  hasUnsupportedRendererAttached: false,
136
146
  inject: (renderer) => {
137
147
  const nextRendererId = ++rendererIdCounter;
138
- renderers.set(nextRendererId, renderer);
139
- _renderers.add(renderer);
140
- if (!rdtHook._instrumentationIsActive) {
141
- rdtHook._instrumentationIsActive = true;
142
- notifyActiveListeners();
143
- }
148
+ trackInjectedRenderer(rdtHook, renderers, nextRendererId, renderer);
144
149
  return nextRendererId;
145
150
  },
146
151
  on: noOp,
@@ -163,12 +168,10 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
163
168
  const ourRenderers = rdtHook.renderers;
164
169
  rdtHook = newHook;
165
170
  const nextRenderers = getRendererMap(rdtHook);
166
- if (ourRenderers.size > 0) {
167
- ourRenderers.forEach((renderer, rendererId) => {
168
- _renderers.add(renderer);
169
- nextRenderers.set(rendererId, renderer);
170
- });
171
- }
171
+ ourRenderers.forEach((renderer, rendererId) => {
172
+ _renderers.add(renderer);
173
+ nextRenderers.set(rendererId, renderer);
174
+ });
172
175
  if (ourRenderers.size > 0 || rdtHookReplaceListeners.size > 0) {
173
176
  patchRDTHook(onActive);
174
177
  }
@@ -193,14 +196,14 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
193
196
  };
194
197
  objectDefineProperty(window, "hasOwnProperty", {
195
198
  configurable: true,
196
- value: (...propertyKeys: [PropertyKey]) => {
197
- if (!didRunHasOwnPropertyHack && propertyKeys[0] === "__REACT_DEVTOOLS_GLOBAL_HOOK__") {
199
+ value: (propertyKey: PropertyKey) => {
200
+ if (!didRunHasOwnPropertyHack && propertyKey === "__REACT_DEVTOOLS_GLOBAL_HOOK__") {
198
201
  didRunHasOwnPropertyHack = true;
199
202
  restoreWindowHasOwnProperty();
200
203
  globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ = undefined;
201
204
  return false;
202
205
  }
203
- return originalWindowHasOwnProperty(...propertyKeys);
206
+ return originalWindowHasOwnProperty(propertyKey);
204
207
  },
205
208
  writable: true,
206
209
  });
@@ -240,17 +243,12 @@ export const patchRDTHook = (onActive?: ActiveListener): void => {
240
243
  const previousInject = rdtHook.inject;
241
244
  rdtHook.inject = (renderer) => {
242
245
  const rendererId = previousInject.call(rdtHook, renderer);
243
- _renderers.add(renderer);
244
- renderers.set(rendererId, renderer);
245
- if (!rdtHook._instrumentationIsActive) {
246
- rdtHook._instrumentationIsActive = true;
247
- notifyActiveListeners();
248
- }
246
+ trackInjectedRenderer(rdtHook, renderers, rendererId, renderer);
249
247
  return rendererId;
250
248
  };
251
249
  }
252
250
  if (!didNotifyActiveListeners && (renderers.size || rdtHook._instrumentationIsActive)) {
253
- if (onActive) runHookListener("onActive", onActive);
251
+ onActive?.();
254
252
  }
255
253
  };
256
254
 
@@ -269,7 +267,3 @@ export const getRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHook =
269
267
  patchRDTHook(onActive);
270
268
  return globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ ?? installRDTHook(onActive);
271
269
  };
272
-
273
- export const safelyInstallRDTHook = (): void => {
274
- runWithBippyError(getRDTHook, (cause) => new BippyHookInstallationError(cause));
275
- };
@@ -1,8 +1,10 @@
1
1
  import { compareSemver } from "../semver.js";
2
+
2
3
  export const ReactBuildType = {
3
4
  Development: 1,
4
5
  Production: 0,
5
- };
6
+ } as const;
7
+
6
8
  export const ReactFiberFlags = {
7
9
  ChildDeletion: 16,
8
10
  Cloned: 8,
@@ -13,7 +15,8 @@ export const ReactFiberFlags = {
13
15
  Snapshot: 1024,
14
16
  Update: 4,
15
17
  Visibility: 8192,
16
- };
18
+ } as const;
19
+
17
20
  export const ReactSymbols = {
18
21
  CONCURRENT_MODE_NUMBER: 60111,
19
22
  CONCURRENT_MODE_SYMBOL_DESCRIPTION: "react.concurrent_mode",
@@ -22,8 +25,49 @@ export const ReactSymbols = {
22
25
  DEPRECATED_ASYNC_MODE_SYMBOL_STRING: "Symbol(react.async_mode)",
23
26
  ELEMENT_SYMBOL_STRING: "Symbol(react.transitional.element)",
24
27
  LEGACY_ELEMENT_SYMBOL_STRING: "Symbol(react.element)",
25
- };
26
- const defineReactWorkTags = (workTags) => workTags;
28
+ } as const;
29
+
30
+ export interface ReactWorkTagMap {
31
+ ActivityComponent: number;
32
+ CacheComponent: number;
33
+ ClassComponent: number;
34
+ ContextConsumer: number;
35
+ ContextProvider: number;
36
+ CoroutineComponent: number;
37
+ CoroutineHandlerPhase: number;
38
+ DehydratedSuspenseComponent: number;
39
+ ForwardRef: number;
40
+ Fragment: number;
41
+ FunctionComponent: number;
42
+ HostComponent: number;
43
+ HostHoistable: number;
44
+ HostPortal: number;
45
+ HostRoot: number;
46
+ HostSingleton: number;
47
+ HostText: number;
48
+ IncompleteClassComponent: number;
49
+ IncompleteFunctionComponent: number;
50
+ IndeterminateComponent: number;
51
+ LazyComponent: number;
52
+ LegacyHiddenComponent: number;
53
+ MemoComponent: number;
54
+ Mode: number;
55
+ OffscreenComponent: number;
56
+ Profiler: number;
57
+ ScopeComponent: number;
58
+ SimpleMemoComponent: number;
59
+ SuspenseComponent: number;
60
+ SuspenseListComponent: number;
61
+ Throw: number;
62
+ TracingMarkerComponent: number;
63
+ ViewTransitionComponent: number;
64
+ YieldComponent: number;
65
+ }
66
+
67
+ const defineReactWorkTags = <const VersionedWorkTags extends Record<string, ReactWorkTagMap>>(
68
+ workTags: VersionedWorkTags,
69
+ ): VersionedWorkTags => workTags;
70
+
27
71
  const reactWorkTagsByVersion = defineReactWorkTags({
28
72
  "16.0.0": {
29
73
  ActivityComponent: -1,
@@ -206,7 +250,37 @@ const reactWorkTagsByVersion = defineReactWorkTags({
206
250
  YieldComponent: -1,
207
251
  },
208
252
  });
209
- const reactWorkTagRanges = [
253
+
254
+ export type ReactWorkTagVersion = keyof typeof reactWorkTagsByVersion;
255
+
256
+ export interface GetReactWorkTags {
257
+ (): Readonly<ReactWorkTagMap>;
258
+ <Version extends ReactWorkTagVersion>(
259
+ reactVersion: Version,
260
+ ): Readonly<(typeof reactWorkTagsByVersion)[Version]>;
261
+ (reactVersion: string): Readonly<ReactWorkTagMap>;
262
+ }
263
+
264
+ export type ReactWorkTag = Exclude<
265
+ (typeof reactWorkTagsByVersion)[ReactWorkTagVersion][keyof ReactWorkTagMap],
266
+ -1
267
+ >;
268
+
269
+ export type HostWorkTag = Exclude<
270
+ | (typeof reactWorkTagsByVersion)[ReactWorkTagVersion]["HostComponent"]
271
+ | (typeof reactWorkTagsByVersion)[ReactWorkTagVersion]["HostHoistable"]
272
+ | (typeof reactWorkTagsByVersion)[ReactWorkTagVersion]["HostSingleton"]
273
+ | (typeof reactWorkTagsByVersion)[ReactWorkTagVersion]["HostText"],
274
+ -1
275
+ >;
276
+
277
+ interface ReactWorkTagRange {
278
+ isMinimumExcluded: boolean;
279
+ minimumVersion: string;
280
+ workTags: Readonly<ReactWorkTagMap>;
281
+ }
282
+
283
+ const reactWorkTagRanges: ReactWorkTagRange[] = [
210
284
  {
211
285
  isMinimumExcluded: true,
212
286
  minimumVersion: "17.0.1",
@@ -228,10 +302,14 @@ const reactWorkTagRanges = [
228
302
  workTags: reactWorkTagsByVersion["16.4.3-alpha"],
229
303
  },
230
304
  ];
231
- export const getReactWorkTags = (reactVersion) => {
305
+
306
+ const defaultReactWorkTags = reactWorkTagsByVersion["17.0.2"];
307
+
308
+ export const getReactWorkTags: GetReactWorkTags = (reactVersion?: string) => {
309
+ if (reactVersion === undefined) return defaultReactWorkTags;
232
310
  for (const range of reactWorkTagRanges) {
233
311
  const comparison = compareSemver(reactVersion, range.minimumVersion);
234
- if (comparison === null) return reactWorkTagsByVersion["17.0.2"];
312
+ if (comparison === null) return defaultReactWorkTags;
235
313
  if (comparison === 1 || (comparison === 0 && !range.isMinimumExcluded)) {
236
314
  return range.workTags;
237
315
  }
@@ -1,8 +1,9 @@
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
+ export { compareSemver } from "./semver.js";
6
7
  export {
7
8
  getReactWorkTags,
8
9
  ReactBuildType,
@@ -15,18 +16,19 @@ export type {
15
16
  ReactWorkTagMap,
16
17
  ReactWorkTagVersion,
17
18
  } from "./generated/react-work-tags.js";
19
+ export * from "./types.js";
18
20
 
19
- const defaultReactWorkTags = getReactWorkTags("");
21
+ const defaultReactWorkTags = getReactWorkTags();
20
22
  const fiberReactWorkTags = new WeakMap<Fiber, Readonly<ReactWorkTagMap>>();
21
23
 
22
24
  export const getReactWorkTagsForRenderer = (
23
25
  renderer?: ReactRenderer | null,
24
26
  ): Readonly<ReactWorkTagMap> => {
25
27
  const reconcilerVersion = renderer?.reconcilerVersion;
26
- if (reconcilerVersion && compareSemver(reconcilerVersion, reconcilerVersion) === 0) {
28
+ if (reconcilerVersion && isSemver(reconcilerVersion)) {
27
29
  return getReactWorkTags(reconcilerVersion);
28
30
  }
29
- return getReactWorkTags(renderer?.version ?? "");
31
+ return renderer?.version ? getReactWorkTags(renderer.version) : defaultReactWorkTags;
30
32
  };
31
33
 
32
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;
@@ -1,85 +1,13 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type ReactReconciler from "react-reconciler";
3
- import type { HostWorkTag, ReactWorkTag } from "./react-internals.js";
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 { isFiber } 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 && isFiber(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;