bippy 0.6.1-dev.93556ef → 0.6.1-dev.94e5797

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
  *
@@ -85,7 +70,7 @@ export const isValidElement = (element: unknown): element is React.ReactElement
85
70
  /**
86
71
  * Returns `true` if object is a React Fiber.
87
72
  */
88
- export const isValidFiber = (fiber: unknown): fiber is Fiber =>
73
+ export const isFiber = (fiber: unknown): fiber is Fiber =>
89
74
  typeof fiber === "object" &&
90
75
  fiber !== null &&
91
76
  "tag" in fiber &&
@@ -99,7 +84,7 @@ const isFiberRoot = (fiberRoot: unknown): fiberRoot is FiberRoot =>
99
84
  typeof fiberRoot === "object" &&
100
85
  fiberRoot !== null &&
101
86
  "current" in fiberRoot &&
102
- isValidFiber(fiberRoot.current);
87
+ isFiber(fiberRoot.current);
103
88
 
104
89
  /**
105
90
  * Returns `true` if fiber is a host fiber. Host fibers are DOM nodes in react-dom, `View` in react-native, etc.
@@ -138,100 +123,6 @@ export const isCompositeFiber = (fiber: Fiber): boolean => {
138
123
  }
139
124
  };
140
125
 
141
- /**
142
- * Returns `true` if the object is a {@link Fiber}
143
- */
144
- export const isFiber = (maybeFiber: unknown): maybeFiber is Fiber => {
145
- if (!maybeFiber || typeof maybeFiber !== "object") return false;
146
- // this is a fast check. pendingProps will ALWAYS exist in fiber
147
- // `containerInfo` is in FiberRootNode, not FiberNode
148
- return "pendingProps" in maybeFiber && !("containerInfo" in maybeFiber);
149
- };
150
-
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
126
  /**
236
127
  * 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
128
  */
@@ -271,46 +162,6 @@ export const didFiberCommit = (fiber: Fiber): boolean => {
271
162
  );
272
163
  };
273
164
 
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
165
  /**
315
166
  * Returns `true` if the {@link Fiber} should be filtered out during reconciliation.
316
167
  */
@@ -359,47 +210,6 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
359
210
  }
360
211
  };
361
212
 
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
213
  /**
404
214
  * Traverses up or down a {@link Fiber}, return `true` to stop and select a node.
405
215
  */
@@ -468,27 +278,6 @@ const traverseFiberSiblings = (
468
278
  return match ?? traverseFiberSiblings(nextSibling, selector, ascending);
469
279
  };
470
280
 
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
281
  /**
493
282
  * Returns `true` if the {@link Fiber} uses React Compiler's memo cache.
494
283
  */
@@ -542,6 +331,7 @@ export const isInstrumentationActive = (): boolean => {
542
331
  };
543
332
 
544
333
  export const _fiberRoots = new Set<FiberRoot>();
334
+ const rootRendererIds = new WeakMap<FiberRoot, number>();
545
335
 
546
336
  /**
547
337
  * Returns the latest fiber (since it may be double-buffered).
@@ -561,6 +351,25 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
561
351
  return fiber;
562
352
  };
563
353
 
354
+ /**
355
+ * Returns the renderer that owns the {@link Fiber}.
356
+ */
357
+ export const getRenderer = (fiber: Fiber): ReactRenderer | null => {
358
+ if (!hasRDTHook()) return null;
359
+
360
+ let rootFiber = fiber;
361
+ while (rootFiber.return) {
362
+ rootFiber = rootFiber.return;
363
+ }
364
+
365
+ const fiberRoot = rootFiber.stateNode;
366
+ if (!isFiberRoot(fiberRoot)) return null;
367
+
368
+ const rendererID = rootRendererIds.get(fiberRoot);
369
+ if (rendererID === undefined) return null;
370
+ return getRDTHook().renderers.get(rendererID) ?? null;
371
+ };
372
+
564
373
  export type RenderPhase = "mount" | "unmount" | "update";
565
374
 
566
375
  let nextFiberId = 0;
@@ -806,168 +615,6 @@ export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: Render
806
615
  rootInstance.prevFiber = fiber;
807
616
  };
808
617
 
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
618
  export interface InstrumentationOptions {
972
619
  name?: string;
973
620
  onActive?: () => unknown;
@@ -998,8 +645,6 @@ interface HookDispatchers {
998
645
  const hookDispatchers = new WeakMap<ReactDevToolsGlobalHook, Partial<HookDispatchers>>();
999
646
  let didSubscribeToHookReplacements = false;
1000
647
 
1001
- const rootRendererIds = new WeakMap<FiberRoot, number>();
1002
-
1003
648
  // each hook event is dispatched from a single re-installable wrapper. If
1004
649
  // something overwrites the hook method (devtools, direct assignment), the
1005
650
  // next instrument() call installs a fresh wrapper over it; a superseded
@@ -1123,6 +768,18 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1123
768
  }
1124
769
  };
1125
770
 
771
+ const wireHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
772
+ if (!didSubscribeToHookReplacements) {
773
+ onRDTHookReplace(setHookEventDispatchers);
774
+ didSubscribeToHookReplacements = true;
775
+ }
776
+ setHookEventDispatchers(rdtHook);
777
+ };
778
+
779
+ if (hasRDTHook()) {
780
+ wireHookEventDispatchers(getRDTHook());
781
+ }
782
+
1126
783
  /**
1127
784
  * Instruments the DevTools hook. Each hook event is patched once and
1128
785
  * dispatches to a set of listeners, so multiple `instrument` calls compose
@@ -1142,15 +799,9 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1142
799
  */
1143
800
  export const instrument = (options: InstrumentationOptions): Unsubscribe => {
1144
801
  const rdtHook = getRDTHook(options.onActive);
1145
-
1146
- if (!didSubscribeToHookReplacements) {
1147
- onRDTHookReplace(setHookEventDispatchers);
1148
- didSubscribeToHookReplacements = true;
1149
- }
1150
-
1151
802
  rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
1152
803
 
1153
- setHookEventDispatchers(rdtHook);
804
+ wireHookEventDispatchers(rdtHook);
1154
805
  const subscription: InstrumentationSubscription = { options };
1155
806
  instrumentationSubscriptions.add(subscription);
1156
807
 
@@ -1176,14 +827,14 @@ const getLegacyRootFiber = (hostInstance: object): Fiber | null => {
1176
827
  const current = Reflect.get(internalRoot, "current");
1177
828
  if (typeof current !== "object" || current === null) return null;
1178
829
  const child = Reflect.get(current, "child");
1179
- return isValidFiber(child) ? child : null;
830
+ return isFiber(child) ? child : null;
1180
831
  };
1181
832
 
1182
833
  const getInternalInstanceHandle = (hostInstance: object): Fiber | null => {
1183
834
  const internalInstanceHandle =
1184
835
  Reflect.get(hostInstance, "__internalInstanceHandle") ??
1185
836
  Reflect.get(hostInstance, "_internalInstanceHandle");
1186
- return isValidFiber(internalInstanceHandle) ? internalInstanceHandle : null;
837
+ return isFiber(internalInstanceHandle) ? internalInstanceHandle : null;
1187
838
  };
1188
839
 
1189
840
  const getPublicHostInstance = (stateNode: unknown): unknown => {
@@ -1199,7 +850,7 @@ const getPublicHostInstance = (stateNode: unknown): unknown => {
1199
850
  return typeof nativeTag === "number" ? nativeTag : stateNode;
1200
851
  };
1201
852
 
1202
- export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
853
+ export const getFiber = <T>(hostInstance: T): Fiber | null => {
1203
854
  const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
1204
855
  if (rdtHook?.renderers) {
1205
856
  for (const renderer of rdtHook.renderers.values()) {
@@ -1219,14 +870,14 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1219
870
 
1220
871
  for (const knownKey of knownFiberPropertyKeys) {
1221
872
  const fiber = Reflect.get(hostInstance, knownKey);
1222
- if (isValidFiber(fiber)) return fiber;
873
+ if (isFiber(fiber)) return fiber;
1223
874
  }
1224
875
 
1225
876
  for (const key of Object.keys(hostInstance)) {
1226
877
  if (isFiberPropertyKey(key)) {
1227
878
  knownFiberPropertyKeys.add(key);
1228
879
  const fiber = Reflect.get(hostInstance, key);
1229
- if (isValidFiber(fiber)) return fiber;
880
+ if (isFiber(fiber)) return fiber;
1230
881
  }
1231
882
  }
1232
883
  }
@@ -1249,6 +900,8 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1249
900
  return null;
1250
901
  };
1251
902
 
903
+ export const getFiberFromHostInstance = getFiber;
904
+
1252
905
  export {
1253
906
  BIPPY_INSTRUMENTATION_STRING,
1254
907
  _onActiveListeners,
@@ -1262,4 +915,4 @@ export {
1262
915
  version,
1263
916
  } from "./rdt-hook.js";
1264
917
  export type { Unsubscribe } from "./rdt-hook.js";
1265
- export type * from "./react-internals/index.js";
918
+ 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 { 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;