bippy 0.7.3-dev.049a43d → 0.7.3-dev.0c7e504
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/LICENSE +1 -0
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +6 -3
- package/dist/index.d.ts +10 -7
- package/dist/index.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +12 -12
- package/dist/source.js +13 -13
- package/package.json +3 -31
- package/src/core.ts +66 -64
- package/src/rdt-hook.ts +2 -0
- package/src/react-internals/current-fiber.ts +84 -0
- package/src/react-internals/index.ts +6 -1
- package/src/react.ts +131 -56
- package/src/source/inspect-hooks.ts +24 -18
package/src/react.ts
CHANGED
|
@@ -1,77 +1,152 @@
|
|
|
1
1
|
import "./install-hook-only.js";
|
|
2
|
-
import
|
|
3
|
-
import { isFiber } from "./core.js";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { isFiber, traverseFiber } from "./core.js";
|
|
4
|
+
import { _renderers } from "./rdt-hook.js";
|
|
4
5
|
import type { Fiber } from "./react-internals/index.js";
|
|
5
6
|
|
|
6
7
|
export type { Fiber } from "./react-internals/index.js";
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const subscribeToEmptyStore = (): (() => void) => unsubscribeFromEmptyStore;
|
|
12
|
-
const useSyncExternalStore: unknown = Reflect.get(React, "useSyncExternalStore");
|
|
9
|
+
interface RenderMarker {
|
|
10
|
+
(state: undefined): undefined;
|
|
11
|
+
}
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
interface ReducerQueue {
|
|
14
|
+
lastRenderedReducer: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface FiberCapture {
|
|
18
|
+
fiber: Fiber;
|
|
19
|
+
queue: ReducerQueue | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const isReducerQueue = (value: unknown): value is ReducerQueue =>
|
|
23
|
+
typeof value === "object" && value !== null && "lastRenderedReducer" in value;
|
|
24
|
+
|
|
25
|
+
const hasRenderMarker = (fiber: Fiber, renderMarker: RenderMarker): boolean => {
|
|
26
|
+
for (let hook = fiber.memoizedState; hook; hook = hook.next) {
|
|
27
|
+
if (Array.isArray(hook.memoizedState) && hook.memoizedState[0] === renderMarker) return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getReducerQueue = (fiber: Fiber, renderMarker: RenderMarker): ReducerQueue | null => {
|
|
33
|
+
for (let hook = fiber.memoizedState; hook; hook = hook.next) {
|
|
34
|
+
if (isReducerQueue(hook.queue) && hook.queue.lastRenderedReducer === renderMarker)
|
|
35
|
+
return hook.queue;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const getDevToolsFiber = (renderMarker: RenderMarker): Fiber | null => {
|
|
41
|
+
for (const renderer of _renderers) {
|
|
42
|
+
try {
|
|
43
|
+
const fiber = renderer.getCurrentFiber?.();
|
|
44
|
+
if (fiber && isFiber(fiber) && hasRenderMarker(fiber, renderMarker)) return fiber;
|
|
45
|
+
} catch {}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const captureReducerFiber = (
|
|
51
|
+
renderMarker: RenderMarker,
|
|
52
|
+
shouldCapture: boolean,
|
|
53
|
+
): FiberCapture | null => {
|
|
54
|
+
let originalBind: typeof Function.prototype.bind | undefined;
|
|
55
|
+
if (shouldCapture) {
|
|
56
|
+
try {
|
|
57
|
+
originalBind = Function.prototype.bind;
|
|
58
|
+
} catch {}
|
|
59
|
+
}
|
|
60
|
+
if (!originalBind) {
|
|
61
|
+
React.useReducer(renderMarker, undefined);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let capture: FiberCapture | null = null;
|
|
66
|
+
let isCapturing = true;
|
|
67
|
+
// HACK: Production React binds a new reducer's Fiber and queue; validate the private reducer marker, not argument positions.
|
|
18
68
|
const bindProxy = new Proxy(originalBind, {
|
|
19
|
-
apply: (bind,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
69
|
+
apply: (bind, callback, boundArguments) => {
|
|
70
|
+
if (isCapturing && !capture) {
|
|
71
|
+
try {
|
|
72
|
+
const queue = boundArguments.find(
|
|
73
|
+
(argument): argument is ReducerQueue =>
|
|
74
|
+
isReducerQueue(argument) && argument.lastRenderedReducer === renderMarker,
|
|
75
|
+
);
|
|
76
|
+
if (queue) {
|
|
77
|
+
const fiber = boundArguments.find(
|
|
78
|
+
(argument): argument is Fiber => isFiber(argument) && isFiber(argument.return),
|
|
79
|
+
);
|
|
80
|
+
if (fiber) capture = { fiber, queue };
|
|
81
|
+
}
|
|
82
|
+
} catch {}
|
|
23
83
|
}
|
|
24
|
-
return Reflect.apply(bind,
|
|
84
|
+
return Reflect.apply(bind, callback, boundArguments);
|
|
25
85
|
},
|
|
26
86
|
});
|
|
27
|
-
Reflect.set(Function.prototype, "bind", bindProxy);
|
|
28
87
|
|
|
29
88
|
try {
|
|
30
|
-
|
|
89
|
+
try {
|
|
90
|
+
Reflect.set(Function.prototype, "bind", bindProxy);
|
|
91
|
+
} catch {}
|
|
92
|
+
React.useReducer(renderMarker, undefined);
|
|
93
|
+
return capture;
|
|
31
94
|
} finally {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
95
|
+
isCapturing = false;
|
|
96
|
+
capture = null;
|
|
97
|
+
try {
|
|
98
|
+
if (Function.prototype.bind === bindProxy) {
|
|
99
|
+
Reflect.set(Function.prototype, "bind", originalBind);
|
|
100
|
+
}
|
|
101
|
+
} catch {}
|
|
35
102
|
}
|
|
36
|
-
|
|
37
|
-
return capturedFiber;
|
|
38
103
|
};
|
|
39
104
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
105
|
+
// HACK: React <16.13 attaches hooks after render; the shared reducer queue validates the root-walk fallback.
|
|
106
|
+
const getRenderingFiberFromRoot = (
|
|
107
|
+
{ fiber, queue }: FiberCapture,
|
|
108
|
+
renderMarker: RenderMarker,
|
|
109
|
+
): Fiber | null => {
|
|
110
|
+
if (queue?.lastRenderedReducer !== renderMarker) return null;
|
|
111
|
+
let rootFiber = fiber;
|
|
112
|
+
while (rootFiber.return) rootFiber = rootFiber.return;
|
|
113
|
+
const root = rootFiber.stateNode;
|
|
114
|
+
if (typeof root !== "object" || root === null || !("current" in root) || !isFiber(root.current)) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const renderingRoot = root.current.alternate;
|
|
118
|
+
return traverseFiber(renderingRoot, (candidate) => {
|
|
119
|
+
if (candidate !== fiber && candidate !== fiber.alternate) return false;
|
|
120
|
+
let parent = candidate;
|
|
121
|
+
while (parent.return) parent = parent.return;
|
|
122
|
+
return parent === renderingRoot;
|
|
123
|
+
});
|
|
51
124
|
};
|
|
52
125
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const
|
|
126
|
+
export const useFiber = (): Fiber | undefined => {
|
|
127
|
+
"use no memo";
|
|
128
|
+
const fiberRef = React.useRef<FiberCapture | null>(null);
|
|
129
|
+
const renderMarker: RenderMarker = (state) => state;
|
|
130
|
+
React.useMemo(() => renderMarker, [renderMarker]);
|
|
131
|
+
const devToolsFiber = fiberRef.current ? null : getDevToolsFiber(renderMarker);
|
|
132
|
+
const capture = captureReducerFiber(renderMarker, !fiberRef.current && !devToolsFiber);
|
|
133
|
+
if (devToolsFiber) {
|
|
134
|
+
fiberRef.current = {
|
|
135
|
+
fiber: devToolsFiber,
|
|
136
|
+
queue: getReducerQueue(devToolsFiber, renderMarker),
|
|
137
|
+
};
|
|
138
|
+
return devToolsFiber;
|
|
139
|
+
}
|
|
140
|
+
const knownCapture = capture ?? fiberRef.current;
|
|
141
|
+
if (!knownCapture) return undefined;
|
|
142
|
+
const knownFiber = knownCapture.fiber;
|
|
60
143
|
const fiber =
|
|
61
|
-
|
|
62
|
-
(
|
|
63
|
-
?
|
|
64
|
-
:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
React.useEffect(() => {
|
|
70
|
-
committedFiberRef.current = fiber;
|
|
71
|
-
}, [fiber]);
|
|
72
|
-
|
|
144
|
+
capture?.fiber ??
|
|
145
|
+
(hasRenderMarker(knownFiber, renderMarker)
|
|
146
|
+
? knownFiber
|
|
147
|
+
: knownFiber.alternate && hasRenderMarker(knownFiber.alternate, renderMarker)
|
|
148
|
+
? knownFiber.alternate
|
|
149
|
+
: getRenderingFiberFromRoot(knownCapture, renderMarker));
|
|
150
|
+
if (fiber) fiberRef.current = { fiber, queue: knownCapture.queue };
|
|
73
151
|
return fiber ?? undefined;
|
|
74
152
|
};
|
|
75
|
-
|
|
76
|
-
export const useFiber =
|
|
77
|
-
typeof useSyncExternalStore === "function" ? useFiberWithExternalStore : useFiberWithReducer;
|
|
@@ -87,6 +87,16 @@ let currentHook: MemoizedState | null = null;
|
|
|
87
87
|
let currentContextDependency: ContextDependency<unknown> | null = null;
|
|
88
88
|
let currentThenableIndex = 0;
|
|
89
89
|
let currentThenableState: unknown[] | null = null;
|
|
90
|
+
let currentMemoCacheIndex = 0;
|
|
91
|
+
let isInspectingHooks = false;
|
|
92
|
+
|
|
93
|
+
const assertNotInspectingHooks = (): void => {
|
|
94
|
+
if (isInspectingHooks) {
|
|
95
|
+
throw new BippyHookInspectionError(
|
|
96
|
+
"Hook inspection cannot be called during another inspection.",
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
90
100
|
|
|
91
101
|
const SuspenseException: unknown = new Error(
|
|
92
102
|
"Suspense interrupted this render. This error is an internal implementation detail of `use`.",
|
|
@@ -323,16 +333,8 @@ const dispatcherUseMemoCache = (size: number): unknown[] => {
|
|
|
323
333
|
const memoCache = fiber.updateQueue?.memoCache;
|
|
324
334
|
if (memoCache === null || memoCache === undefined) return [];
|
|
325
335
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
memoCacheSlots = memoCache.data[memoCache.index] = Array.from(
|
|
329
|
-
{ length: size },
|
|
330
|
-
() => REACT_MEMO_CACHE_SENTINEL,
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
memoCache.index++;
|
|
335
|
-
return memoCacheSlots;
|
|
336
|
+
const memoCacheSlots = memoCache.data[currentMemoCacheIndex++];
|
|
337
|
+
return memoCacheSlots?.slice() ?? Array.from({ length: size }, () => REACT_MEMO_CACHE_SENTINEL);
|
|
336
338
|
};
|
|
337
339
|
|
|
338
340
|
const dispatcherUseOptimistic = (passthrough: unknown): [unknown, () => void] => {
|
|
@@ -834,6 +836,7 @@ const performDispatcherInspection = <TArgs extends unknown[]>(
|
|
|
834
836
|
): HooksTree => {
|
|
835
837
|
const previousDispatcher = readDispatcher(dispatcherRef);
|
|
836
838
|
writeDispatcher(dispatcherRef, dispatcherProxy);
|
|
839
|
+
isInspectingHooks = true;
|
|
837
840
|
|
|
838
841
|
let capturedHookLog: HookLogEntry[] = [];
|
|
839
842
|
let ancestorStackError: Error | undefined;
|
|
@@ -846,6 +849,7 @@ const performDispatcherInspection = <TArgs extends unknown[]>(
|
|
|
846
849
|
} finally {
|
|
847
850
|
capturedHookLog = hookLog;
|
|
848
851
|
hookLog = [];
|
|
852
|
+
isInspectingHooks = false;
|
|
849
853
|
writeDispatcher(dispatcherRef, previousDispatcher);
|
|
850
854
|
}
|
|
851
855
|
|
|
@@ -938,6 +942,7 @@ export const inspectHooks = (
|
|
|
938
942
|
props: Record<string, unknown>,
|
|
939
943
|
target: ReactDevToolsTarget = globalThis,
|
|
940
944
|
): HooksTree => {
|
|
945
|
+
assertNotInspectingHooks();
|
|
941
946
|
const dispatcherRef = requireDispatcherRef(undefined, target);
|
|
942
947
|
getPrimitiveStackCache();
|
|
943
948
|
currentHook = null;
|
|
@@ -961,6 +966,7 @@ export const inspectHooks = (
|
|
|
961
966
|
};
|
|
962
967
|
|
|
963
968
|
export const getFiberHooks = (fiber: Fiber): HooksTree => {
|
|
969
|
+
assertNotInspectingHooks();
|
|
964
970
|
const dispatcherRef = requireDispatcherRef(fiber);
|
|
965
971
|
const workTags = getReactWorkTagsForFiber(fiber);
|
|
966
972
|
|
|
@@ -976,6 +982,7 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
|
|
|
976
982
|
|
|
977
983
|
currentHook = fiber.memoizedState;
|
|
978
984
|
currentFiber = fiber;
|
|
985
|
+
currentMemoCacheIndex = 0;
|
|
979
986
|
|
|
980
987
|
const debugThenableState = fiber.dependencies?._debugThenableState;
|
|
981
988
|
const usedThenables = Array.isArray(debugThenableState)
|
|
@@ -984,18 +991,16 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
|
|
|
984
991
|
currentThenableState = Array.isArray(usedThenables) ? usedThenables : null;
|
|
985
992
|
currentThenableIndex = 0;
|
|
986
993
|
|
|
987
|
-
resolveContextDependency(fiber);
|
|
988
|
-
|
|
989
|
-
const type = fiber.type;
|
|
990
|
-
let props = fiber.memoizedProps;
|
|
991
|
-
if (type !== fiber.elementType) {
|
|
992
|
-
props = resolveDefaultProps(type, props);
|
|
993
|
-
}
|
|
994
|
-
|
|
995
994
|
const originalConsoleMethods = suppressConsole();
|
|
996
995
|
const contextMap = new Map<ReactContext<unknown>, unknown>();
|
|
997
996
|
|
|
998
997
|
try {
|
|
998
|
+
resolveContextDependency(fiber);
|
|
999
|
+
const type = fiber.type;
|
|
1000
|
+
const props =
|
|
1001
|
+
type !== fiber.elementType
|
|
1002
|
+
? resolveDefaultProps(type, fiber.memoizedProps)
|
|
1003
|
+
: fiber.memoizedProps;
|
|
999
1004
|
if (
|
|
1000
1005
|
currentContextDependency !== null &&
|
|
1001
1006
|
!Object.hasOwn(currentContextDependency, "memoizedValue")
|
|
@@ -1030,6 +1035,7 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
|
|
|
1030
1035
|
currentContextDependency = null;
|
|
1031
1036
|
currentThenableState = null;
|
|
1032
1037
|
currentThenableIndex = 0;
|
|
1038
|
+
currentMemoCacheIndex = 0;
|
|
1033
1039
|
restoreContexts(contextMap);
|
|
1034
1040
|
restoreConsole(originalConsoleMethods);
|
|
1035
1041
|
}
|