bippy 0.7.2 → 0.7.3-dev.0ac832d
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/README.md +9 -76
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +56 -9
- package/dist/errors.d.ts +56 -9
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +14 -38
- package/dist/index.d.ts +18 -42
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +11 -11
- package/dist/source.d.cts +36 -9
- package/dist/source.d.ts +36 -9
- package/dist/source.js +11 -11
- package/package.json +5 -33
- package/src/call-listener.ts +13 -0
- package/src/core.ts +287 -258
- package/src/rdt-hook.ts +130 -71
- package/src/react-internals/current-fiber.ts +84 -0
- package/src/react-internals/index.ts +38 -11
- package/src/react-internals/types.ts +33 -6
- package/src/react.ts +141 -55
- package/src/source/constants.ts +1 -3
- package/src/source/get-display-name-from-source.ts +10 -3
- package/src/source/get-source.ts +39 -19
- package/src/source/index.ts +17 -3
- package/src/source/inspect-hooks.ts +198 -49
- package/src/source/owner-stack.ts +43 -35
- package/src/source/parse-debug-stack.ts +1 -1
- package/src/source/parse-hook-names.ts +15 -8
- package/src/source/parse-stack.ts +114 -64
- package/src/source/renderer-dispatchers.ts +9 -4
- package/src/source/symbolication.ts +68 -36
package/src/react.ts
CHANGED
|
@@ -1,76 +1,162 @@
|
|
|
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";
|
|
5
|
+
import { getCurrentFiberFromRoot } from "./react-internals/current-fiber.js";
|
|
4
6
|
import type { Fiber } from "./react-internals/index.js";
|
|
5
7
|
|
|
6
8
|
export type { Fiber } from "./react-internals/index.js";
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const subscribeToEmptyStore = (): (() => void) => unsubscribeFromEmptyStore;
|
|
12
|
-
const useSyncExternalStore: unknown = Reflect.get(React, "useSyncExternalStore");
|
|
10
|
+
interface RenderMarker {
|
|
11
|
+
(state: undefined): undefined;
|
|
12
|
+
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
interface ReducerQueue {
|
|
15
|
+
lastRenderedReducer: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface FiberCapture {
|
|
19
|
+
fiber: Fiber;
|
|
20
|
+
queue: ReducerQueue | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const isReducerQueue = (value: unknown): value is ReducerQueue =>
|
|
24
|
+
typeof value === "object" && value !== null && "lastRenderedReducer" in value;
|
|
25
|
+
|
|
26
|
+
const hasRenderMarker = (fiber: Fiber, renderMarker: RenderMarker): boolean => {
|
|
27
|
+
for (let hook = fiber.memoizedState; hook; hook = hook.next) {
|
|
28
|
+
if (Array.isArray(hook.memoizedState) && hook.memoizedState[0] === renderMarker) return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const getReducerQueue = (fiber: Fiber, renderMarker: RenderMarker): ReducerQueue | null => {
|
|
34
|
+
for (let hook = fiber.memoizedState; hook; hook = hook.next) {
|
|
35
|
+
if (isReducerQueue(hook.queue) && hook.queue.lastRenderedReducer === renderMarker)
|
|
36
|
+
return hook.queue;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const getDevToolsFiber = (renderMarker: RenderMarker): Fiber | null => {
|
|
42
|
+
for (const renderer of _renderers) {
|
|
43
|
+
try {
|
|
44
|
+
const fiber = renderer.getCurrentFiber?.();
|
|
45
|
+
if (fiber && isFiber(fiber) && hasRenderMarker(fiber, renderMarker)) return fiber;
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const captureReducerFiber = (
|
|
52
|
+
renderMarker: RenderMarker,
|
|
53
|
+
shouldCapture: boolean,
|
|
54
|
+
): FiberCapture | null => {
|
|
55
|
+
let originalBind: typeof Function.prototype.bind | undefined;
|
|
56
|
+
if (shouldCapture) {
|
|
57
|
+
try {
|
|
58
|
+
originalBind = Function.prototype.bind;
|
|
59
|
+
} catch {}
|
|
60
|
+
}
|
|
61
|
+
if (!originalBind) {
|
|
62
|
+
React.useReducer(renderMarker, undefined);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let capture: FiberCapture | null = null;
|
|
67
|
+
let isCapturing = true;
|
|
68
|
+
// HACK: Production React binds a new reducer's Fiber and queue; validate the private reducer marker, not argument positions.
|
|
18
69
|
const bindProxy = new Proxy(originalBind, {
|
|
19
|
-
apply: (bind,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
70
|
+
apply: (bind, callback, boundArguments) => {
|
|
71
|
+
if (isCapturing && !capture) {
|
|
72
|
+
try {
|
|
73
|
+
const queue = boundArguments.find(
|
|
74
|
+
(argument): argument is ReducerQueue =>
|
|
75
|
+
isReducerQueue(argument) && argument.lastRenderedReducer === renderMarker,
|
|
76
|
+
);
|
|
77
|
+
if (queue) {
|
|
78
|
+
const fiber = boundArguments.find(
|
|
79
|
+
(argument): argument is Fiber => isFiber(argument) && isFiber(argument.return),
|
|
80
|
+
);
|
|
81
|
+
if (fiber) capture = { fiber, queue };
|
|
82
|
+
}
|
|
83
|
+
} catch {}
|
|
23
84
|
}
|
|
24
|
-
return Reflect.apply(bind,
|
|
85
|
+
return Reflect.apply(bind, callback, boundArguments);
|
|
25
86
|
},
|
|
26
87
|
});
|
|
27
|
-
Reflect.set(Function.prototype, "bind", bindProxy);
|
|
28
88
|
|
|
29
89
|
try {
|
|
30
|
-
|
|
90
|
+
try {
|
|
91
|
+
Reflect.set(Function.prototype, "bind", bindProxy);
|
|
92
|
+
} catch {}
|
|
93
|
+
React.useReducer(renderMarker, undefined);
|
|
94
|
+
return capture;
|
|
31
95
|
} finally {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
96
|
+
isCapturing = false;
|
|
97
|
+
capture = null;
|
|
98
|
+
try {
|
|
99
|
+
if (Function.prototype.bind === bindProxy) {
|
|
100
|
+
Reflect.set(Function.prototype, "bind", originalBind);
|
|
101
|
+
}
|
|
102
|
+
} catch {}
|
|
35
103
|
}
|
|
36
|
-
|
|
37
|
-
return capturedFiber;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const useExternalStoreCapture = (): void => {
|
|
41
|
-
Reflect.apply(useSyncExternalStore, React, [
|
|
42
|
-
subscribeToEmptyStore,
|
|
43
|
-
readEmptySnapshot,
|
|
44
|
-
readEmptySnapshot,
|
|
45
|
-
]);
|
|
46
104
|
};
|
|
47
105
|
|
|
48
|
-
|
|
49
|
-
|
|
106
|
+
// HACK: React <16.13 attaches hooks after render; the shared reducer queue validates the root-walk fallback.
|
|
107
|
+
const getRenderingFiberFromRoot = (
|
|
108
|
+
{ fiber, queue }: FiberCapture,
|
|
109
|
+
renderMarker: RenderMarker,
|
|
110
|
+
): Fiber | null => {
|
|
111
|
+
if (queue?.lastRenderedReducer !== renderMarker) return null;
|
|
112
|
+
let rootFiber = fiber;
|
|
113
|
+
while (rootFiber.return) rootFiber = rootFiber.return;
|
|
114
|
+
const root = rootFiber.stateNode;
|
|
115
|
+
if (typeof root !== "object" || root === null || !("current" in root) || !isFiber(root.current)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const renderingRoot = root.current.alternate;
|
|
119
|
+
const renderingFiber = getCurrentFiberFromRoot(fiber)?.alternate;
|
|
120
|
+
if (renderingFiber) {
|
|
121
|
+
let ancestor = renderingFiber;
|
|
122
|
+
while (ancestor.return) ancestor = ancestor.return;
|
|
123
|
+
if (ancestor === renderingRoot) return renderingFiber;
|
|
124
|
+
}
|
|
125
|
+
return traverseFiber(renderingRoot, (candidate) => {
|
|
126
|
+
if (candidate !== fiber && candidate !== fiber.alternate) return false;
|
|
127
|
+
let parent = candidate;
|
|
128
|
+
while (parent.return) parent = parent.return;
|
|
129
|
+
return parent === renderingRoot;
|
|
130
|
+
});
|
|
50
131
|
};
|
|
51
132
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
const
|
|
133
|
+
export const useFiber = (): Fiber | undefined => {
|
|
134
|
+
"use no memo";
|
|
135
|
+
const fiberRef = React.useRef<FiberCapture | null>(null);
|
|
136
|
+
const renderMarker: RenderMarker = (state) => state;
|
|
137
|
+
React.useMemo(() => renderMarker, [renderMarker]);
|
|
138
|
+
const devToolsFiber = fiberRef.current ? null : getDevToolsFiber(renderMarker);
|
|
139
|
+
const capture = captureReducerFiber(renderMarker, !fiberRef.current && !devToolsFiber);
|
|
140
|
+
if (devToolsFiber) {
|
|
141
|
+
fiberRef.current = {
|
|
142
|
+
fiber: devToolsFiber,
|
|
143
|
+
queue: getReducerQueue(devToolsFiber, renderMarker),
|
|
144
|
+
};
|
|
145
|
+
return devToolsFiber;
|
|
146
|
+
}
|
|
147
|
+
const knownCapture = capture ?? fiberRef.current;
|
|
148
|
+
if (!knownCapture) return undefined;
|
|
149
|
+
const knownFiber = knownCapture.fiber;
|
|
59
150
|
const fiber =
|
|
60
|
-
|
|
61
|
-
(
|
|
62
|
-
?
|
|
63
|
-
:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}, [fiber]);
|
|
71
|
-
|
|
151
|
+
capture?.fiber ??
|
|
152
|
+
(hasRenderMarker(knownFiber, renderMarker)
|
|
153
|
+
? knownFiber
|
|
154
|
+
: knownFiber.alternate && hasRenderMarker(knownFiber.alternate, renderMarker)
|
|
155
|
+
? knownFiber.alternate
|
|
156
|
+
: getRenderingFiberFromRoot(knownCapture, renderMarker));
|
|
157
|
+
if (fiber) {
|
|
158
|
+
knownCapture.fiber = fiber;
|
|
159
|
+
fiberRef.current = knownCapture;
|
|
160
|
+
}
|
|
72
161
|
return fiber ?? undefined;
|
|
73
162
|
};
|
|
74
|
-
|
|
75
|
-
export const useFiber =
|
|
76
|
-
typeof useSyncExternalStore === "function" ? useFiberWithExternalStore : useFiberWithReducer;
|
package/src/source/constants.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
57
|
-
|
|
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(
|
|
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);
|
package/src/source/get-source.ts
CHANGED
|
@@ -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
|
-
|
|
73
|
-
|
|
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,
|
|
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
|
-
|
|
123
|
-
|
|
130
|
+
shouldUseCache = true,
|
|
131
|
+
sourceFetch?: SourceFetch,
|
|
132
|
+
requestOptions: SourceMapRequestOptions = {},
|
|
124
133
|
): Promise<FiberSource | null> => {
|
|
125
|
-
if (hasDebugSource(fiber))
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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(
|
|
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,
|
|
165
|
+
return getSourceByComponentName(fiber, shouldUseCache, sourceFetch, requestOptions);
|
|
146
166
|
};
|
|
147
167
|
|
|
148
168
|
const getPathSegmentCount = (path: string): number => path.split("/").filter(Boolean).length;
|
package/src/source/index.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
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 {
|
|
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";
|