bippy 0.5.43 → 0.6.0
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/dist/core.cjs +1 -1
- package/dist/core.d.cts +28 -313
- package/dist/core.d.ts +28 -313
- package/dist/core.js +1 -1
- package/dist/core2.d.cts +3 -2
- package/dist/core2.d.ts +3 -2
- package/dist/get-source.cjs +19 -0
- package/dist/get-source.js +19 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.iife.js +1 -1
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.iife.js +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/react-refresh.cjs +9 -0
- package/dist/react-refresh.d.cts +66 -0
- package/dist/react-refresh.d.ts +66 -0
- package/dist/react-refresh.js +9 -0
- package/dist/source.cjs +4 -19
- package/dist/source.d.cts +35 -27
- package/dist/source.d.ts +35 -27
- package/dist/source.js +4 -19
- package/dist/unsubscribe.d.cts +298 -0
- package/dist/unsubscribe.d.ts +298 -0
- package/package.json +14 -3
- package/src/core.ts +312 -334
- package/src/rdt-hook.ts +57 -16
- package/src/react-refresh/constants.ts +9 -0
- package/src/react-refresh/detect-hmr-transport.ts +33 -0
- package/src/react-refresh/index.ts +173 -0
- package/src/react-refresh/metro-hmr-transport.ts +188 -0
- package/src/react-refresh/next-webpack-hmr-transport.ts +72 -0
- package/src/react-refresh/normalize-hmr-file-path.ts +24 -0
- package/src/react-refresh/types.ts +7 -0
- package/src/react-refresh/vite-hmr-transport.ts +116 -0
- package/src/source/constants.ts +11 -0
- package/src/source/get-display-name-from-source.ts +3 -3
- package/src/source/get-source.ts +53 -9
- package/src/source/index.ts +17 -8
- package/src/source/inspect-hooks.ts +7 -4
- package/src/source/owner-stack.ts +193 -29
- package/src/source/parse-debug-stack.ts +133 -0
- package/src/source/parse-stack.ts +6 -105
- package/src/source/symbolication.ts +48 -13
- package/src/types.ts +20 -7
- package/src/unsubscribe.ts +17 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { JSX_FACTORY_FRAME_COUNT, REACT_STACK_BOTTOM_FRAME_PATTERNS } from "./constants.js";
|
|
2
|
+
import { parseStack, StackFrame } from "./parse-stack.js";
|
|
3
|
+
|
|
4
|
+
interface V8CallSite {
|
|
5
|
+
getFunctionName?: () => string | null;
|
|
6
|
+
getScriptNameOrSourceURL?: () => string | null;
|
|
7
|
+
getLineNumber?: () => number | null;
|
|
8
|
+
getColumnNumber?: () => number | null;
|
|
9
|
+
getEnclosingLineNumber?: () => number | null;
|
|
10
|
+
getEnclosingColumnNumber?: () => number | null;
|
|
11
|
+
getTypeName?: () => string | null;
|
|
12
|
+
getMethodName?: () => string | null;
|
|
13
|
+
getEvalOrigin?: () => string | null;
|
|
14
|
+
isNative?: () => boolean;
|
|
15
|
+
isEval?: () => boolean;
|
|
16
|
+
toString: () => string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ParsedDebugStack {
|
|
20
|
+
frames: StackFrame[];
|
|
21
|
+
// React appends a react-stack-bottom-frame sentinel to stacks captured
|
|
22
|
+
// during render; without it the JSX was created outside a render and the
|
|
23
|
+
// lower frames are arbitrary bootstrapping code
|
|
24
|
+
isTrusted: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const parsedDebugStackCache = new WeakMap<Error, ParsedDebugStack>();
|
|
28
|
+
|
|
29
|
+
const isReactBottomFrameName = (functionName: string): boolean =>
|
|
30
|
+
REACT_STACK_BOTTOM_FRAME_PATTERNS.some((pattern) => functionName.includes(pattern));
|
|
31
|
+
|
|
32
|
+
const getCallSiteFunctionName = (callSite: V8CallSite): string => {
|
|
33
|
+
const functionName = callSite.getFunctionName?.() ?? "";
|
|
34
|
+
if (functionName) {
|
|
35
|
+
return functionName;
|
|
36
|
+
}
|
|
37
|
+
const typeName = callSite.getTypeName?.() ?? "";
|
|
38
|
+
const methodName = callSite.getMethodName?.() ?? "";
|
|
39
|
+
if (typeName && methodName) {
|
|
40
|
+
return `${typeName}.${methodName}`;
|
|
41
|
+
}
|
|
42
|
+
return methodName;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const collectStructuredFrames = (callSites: V8CallSite[]): ParsedDebugStack => {
|
|
46
|
+
const frames: StackFrame[] = [];
|
|
47
|
+
for (
|
|
48
|
+
let callSiteIndex = JSX_FACTORY_FRAME_COUNT;
|
|
49
|
+
callSiteIndex < callSites.length;
|
|
50
|
+
callSiteIndex++
|
|
51
|
+
) {
|
|
52
|
+
const callSite = callSites[callSiteIndex];
|
|
53
|
+
const functionName = getCallSiteFunctionName(callSite);
|
|
54
|
+
if (isReactBottomFrameName(functionName)) {
|
|
55
|
+
return { frames, isTrusted: true };
|
|
56
|
+
}
|
|
57
|
+
if (callSite.isNative?.()) {
|
|
58
|
+
frames.push({ functionName: functionName || undefined });
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
let fileName = callSite.getScriptNameOrSourceURL?.() ?? "";
|
|
62
|
+
if (!fileName && callSite.isEval?.()) {
|
|
63
|
+
fileName = callSite.getEvalOrigin?.() ?? "";
|
|
64
|
+
}
|
|
65
|
+
frames.push({
|
|
66
|
+
functionName: functionName && functionName !== "<anonymous>" ? functionName : undefined,
|
|
67
|
+
fileName: fileName && fileName !== "<anonymous>" ? fileName : undefined,
|
|
68
|
+
lineNumber: callSite.getLineNumber?.() ?? undefined,
|
|
69
|
+
columnNumber: callSite.getColumnNumber?.() ?? undefined,
|
|
70
|
+
enclosingLineNumber: callSite.getEnclosingLineNumber?.() ?? undefined,
|
|
71
|
+
enclosingColumnNumber: callSite.getEnclosingColumnNumber?.() ?? undefined,
|
|
72
|
+
source: ` at ${callSite.toString()}`,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return { frames, isTrusted: false };
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const parseMaterializedStack = (stackString: string): ParsedDebugStack => {
|
|
79
|
+
let bottomFrameIndex = -1;
|
|
80
|
+
for (const pattern of REACT_STACK_BOTTOM_FRAME_PATTERNS) {
|
|
81
|
+
bottomFrameIndex = stackString.indexOf(pattern);
|
|
82
|
+
if (bottomFrameIndex !== -1) break;
|
|
83
|
+
}
|
|
84
|
+
const trimmedStack =
|
|
85
|
+
bottomFrameIndex === -1
|
|
86
|
+
? stackString
|
|
87
|
+
: stackString.slice(0, stackString.lastIndexOf("\n", bottomFrameIndex));
|
|
88
|
+
return {
|
|
89
|
+
frames: parseStack(trimmedStack).slice(JSX_FACTORY_FRAME_COUNT),
|
|
90
|
+
isTrusted: bottomFrameIndex !== -1,
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Parses a React `_debugStack` Error, preferring V8's structured CallSite API
|
|
96
|
+
* (via Error.prepareStackTrace) over string parsing. Structured frames carry
|
|
97
|
+
* enclosing line/column - the function definition start, not the call site -
|
|
98
|
+
* and are immune to source-mapped `.stack` strings. Falls back to string
|
|
99
|
+
* parsing on engines without CallSites (JSC, SpiderMonkey) or when the stack
|
|
100
|
+
* was already materialized. The leading JSX factory frame (jsxDEV) and
|
|
101
|
+
* everything at or below React's bottom-frame sentinel are dropped.
|
|
102
|
+
*/
|
|
103
|
+
export const parseDebugStack = (debugStack: Error): ParsedDebugStack => {
|
|
104
|
+
const cachedResult = parsedDebugStackCache.get(debugStack);
|
|
105
|
+
if (cachedResult) {
|
|
106
|
+
return cachedResult;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let structuredResult: ParsedDebugStack | null = null;
|
|
110
|
+
const collectFramesAndFormatStack = (error: Error, callSites: V8CallSite[]): string => {
|
|
111
|
+
structuredResult = collectStructuredFrames(callSites);
|
|
112
|
+
// this return value becomes error.stack permanently, so emit the default
|
|
113
|
+
// V8 format for any later reader of the same error
|
|
114
|
+
let stackString = `${error.name || "Error"}: ${error.message || ""}`;
|
|
115
|
+
for (const callSite of callSites) {
|
|
116
|
+
stackString += `\n at ${callSite.toString()}`;
|
|
117
|
+
}
|
|
118
|
+
return stackString;
|
|
119
|
+
};
|
|
120
|
+
const previousPrepareStackTrace = Error.prepareStackTrace;
|
|
121
|
+
// node's CallSite typings disagree with browser-safe optional methods
|
|
122
|
+
Error.prepareStackTrace = collectFramesAndFormatStack as typeof Error.prepareStackTrace;
|
|
123
|
+
let stackString: string;
|
|
124
|
+
try {
|
|
125
|
+
stackString = String(debugStack.stack);
|
|
126
|
+
} finally {
|
|
127
|
+
Error.prepareStackTrace = previousPrepareStackTrace;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const result = structuredResult ?? parseMaterializedStack(stackString);
|
|
131
|
+
parsedDebugStackCache.set(debugStack, result);
|
|
132
|
+
return result;
|
|
133
|
+
};
|
|
@@ -2,11 +2,17 @@ export interface StackFrame {
|
|
|
2
2
|
args?: unknown[];
|
|
3
3
|
columnNumber?: number;
|
|
4
4
|
lineNumber?: number;
|
|
5
|
+
// start of the enclosing function (the definition, not the call site);
|
|
6
|
+
// only available from V8's structured CallSite API
|
|
7
|
+
enclosingLineNumber?: number;
|
|
8
|
+
enclosingColumnNumber?: number;
|
|
5
9
|
fileName?: string;
|
|
6
10
|
functionName?: string;
|
|
7
11
|
source?: string;
|
|
8
12
|
isServer?: boolean;
|
|
9
13
|
isSymbolicated?: boolean;
|
|
14
|
+
// the source map ignore-listed this frame's original source (x_google_ignoreList)
|
|
15
|
+
isIgnoreListed?: boolean;
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
export interface ParseOptions {
|
|
@@ -19,14 +25,6 @@ const FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\S+:\d+/;
|
|
|
19
25
|
const CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;
|
|
20
26
|
const SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code\])?$/;
|
|
21
27
|
|
|
22
|
-
const getNonStandardStacktrace = (error: unknown): string | null => {
|
|
23
|
-
if (error && typeof error === "object") {
|
|
24
|
-
const stacktrace = (error as Record<string, unknown>)["stacktrace"];
|
|
25
|
-
return typeof stacktrace === "string" ? stacktrace : null;
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
28
|
export const parseStack = (stackString: string, options?: ParseOptions): StackFrame[] => {
|
|
31
29
|
if (options?.includeInElement !== false) {
|
|
32
30
|
const lines = stackString.split("\n");
|
|
@@ -76,10 +74,6 @@ const applySlice = <T>(lines: T[], options?: ParseOptions): T[] => {
|
|
|
76
74
|
return lines;
|
|
77
75
|
};
|
|
78
76
|
|
|
79
|
-
export const parseV8OrIE = (error: Error, options?: ParseOptions): StackFrame[] => {
|
|
80
|
-
return parseV8OrIeString(error.stack!, options);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
77
|
export const parseV8OrIeString = (stack: string, options?: ParseOptions): StackFrame[] => {
|
|
84
78
|
const filteredLines = applySlice(
|
|
85
79
|
stack.split("\n").filter((line) => {
|
|
@@ -120,10 +114,6 @@ export const parseV8OrIeString = (stack: string, options?: ParseOptions): StackF
|
|
|
120
114
|
});
|
|
121
115
|
};
|
|
122
116
|
|
|
123
|
-
export const parseFFOrSafari = (error: Error, options?: ParseOptions): StackFrame[] => {
|
|
124
|
-
return parseFFOrSafariString(error.stack!, options);
|
|
125
|
-
};
|
|
126
|
-
|
|
127
117
|
export const parseFFOrSafariString = (stack: string, options?: ParseOptions): StackFrame[] => {
|
|
128
118
|
const filteredLines = applySlice(
|
|
129
119
|
stack.split("\n").filter((line) => {
|
|
@@ -158,92 +148,3 @@ export const parseFFOrSafariString = (stack: string, options?: ParseOptions): St
|
|
|
158
148
|
}
|
|
159
149
|
});
|
|
160
150
|
};
|
|
161
|
-
|
|
162
|
-
export const parseOpera = (error: Error, options?: ParseOptions): StackFrame[] => {
|
|
163
|
-
const nonStandardStacktrace = getNonStandardStacktrace(error);
|
|
164
|
-
if (
|
|
165
|
-
!nonStandardStacktrace ||
|
|
166
|
-
(error.message.includes("\n") &&
|
|
167
|
-
error.message.split("\n").length > nonStandardStacktrace.split("\n").length)
|
|
168
|
-
) {
|
|
169
|
-
return parseOpera9(error, options);
|
|
170
|
-
}
|
|
171
|
-
if (!error.stack) return parseOpera10(error, options);
|
|
172
|
-
return parseOpera11(error, options);
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
export const parseOpera9 = (error: Error, options?: ParseOptions): StackFrame[] => {
|
|
176
|
-
const lineRegex = /Line (\d+).*script (?:in )?(\S+)/i;
|
|
177
|
-
const messageLines = error.message.split("\n");
|
|
178
|
-
const parsedFrames: StackFrame[] = [];
|
|
179
|
-
|
|
180
|
-
for (let i = 2, len = messageLines.length; i < len; i += 2) {
|
|
181
|
-
const match = lineRegex.exec(messageLines[i]);
|
|
182
|
-
if (match) {
|
|
183
|
-
parsedFrames.push({
|
|
184
|
-
fileName: match[2],
|
|
185
|
-
lineNumber: +match[1],
|
|
186
|
-
source: messageLines[i],
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return applySlice(parsedFrames, options);
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
export const parseOpera10 = (error: Error, options?: ParseOptions): StackFrame[] => {
|
|
195
|
-
const lineRegex = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
|
|
196
|
-
const nonStandardStacktrace = getNonStandardStacktrace(error);
|
|
197
|
-
const stacktraceLines = (nonStandardStacktrace || "").split("\n");
|
|
198
|
-
const parsedFrames: StackFrame[] = [];
|
|
199
|
-
|
|
200
|
-
for (let i = 0, len = stacktraceLines.length; i < len; i += 2) {
|
|
201
|
-
const match = lineRegex.exec(stacktraceLines[i]);
|
|
202
|
-
if (match) {
|
|
203
|
-
parsedFrames.push({
|
|
204
|
-
functionName: match[3] || undefined,
|
|
205
|
-
fileName: match[2],
|
|
206
|
-
lineNumber: match[1] ? +match[1] : undefined,
|
|
207
|
-
source: stacktraceLines[i],
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return applySlice(parsedFrames, options);
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
export const parseOpera11 = (error: Error, options?: ParseOptions): StackFrame[] => {
|
|
216
|
-
const filteredLines = applySlice(
|
|
217
|
-
// @ts-expect-error missing stack property
|
|
218
|
-
error.stack.split("\n").filter((line) => {
|
|
219
|
-
return !!line.match(FIREFOX_SAFARI_STACK_REGEXP) && !line.match(/^Error created at/);
|
|
220
|
-
}),
|
|
221
|
-
options,
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
return filteredLines.map<StackFrame>((line) => {
|
|
225
|
-
const tokens = line.split("@");
|
|
226
|
-
const locationParts = extractLocation(tokens.pop()!);
|
|
227
|
-
const functionCall = tokens.shift() || "";
|
|
228
|
-
const functionName =
|
|
229
|
-
functionCall.replace(/<anonymous function(: (\w+))?>/, "$2").replace(/\([^)]*\)/g, "") ||
|
|
230
|
-
undefined;
|
|
231
|
-
let argsRaw: string | undefined;
|
|
232
|
-
if (functionCall.match(/\(([^)]*)\)/))
|
|
233
|
-
argsRaw = functionCall.replace(/^[^(]+\(([^)]*)\)$/, "$1");
|
|
234
|
-
|
|
235
|
-
const args =
|
|
236
|
-
argsRaw === undefined || argsRaw === "[arguments not available]"
|
|
237
|
-
? undefined
|
|
238
|
-
: argsRaw.split(",");
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
functionName,
|
|
242
|
-
args,
|
|
243
|
-
fileName: locationParts[0],
|
|
244
|
-
lineNumber: locationParts[1] ? +locationParts[1] : undefined,
|
|
245
|
-
columnNumber: locationParts[2] ? +locationParts[2] : undefined,
|
|
246
|
-
source: line,
|
|
247
|
-
};
|
|
248
|
-
});
|
|
249
|
-
};
|
|
@@ -5,6 +5,7 @@ import { StackFrame } from "./parse-stack.js";
|
|
|
5
5
|
export interface DecodedSourceMapSection {
|
|
6
6
|
map: {
|
|
7
7
|
file?: string;
|
|
8
|
+
ignoredSourceIndices?: Set<number>;
|
|
8
9
|
mappings: SourceMapSegment[][];
|
|
9
10
|
names?: string[];
|
|
10
11
|
sourceRoot?: string;
|
|
@@ -35,6 +36,7 @@ export type RawSourceMap = IndexSourceMap | StandardSourceMap;
|
|
|
35
36
|
|
|
36
37
|
export interface SourceMap {
|
|
37
38
|
file?: string;
|
|
39
|
+
ignoredSourceIndices?: Set<number>;
|
|
38
40
|
mappings: SourceMapSegment[][];
|
|
39
41
|
names?: string[];
|
|
40
42
|
sections?: DecodedSourceMapSection[];
|
|
@@ -47,12 +49,14 @@ export interface SourceMap {
|
|
|
47
49
|
// https://developer.chrome.com/blog/sourcemaps#the_anatomy_of_a_source_map
|
|
48
50
|
export interface StandardSourceMap {
|
|
49
51
|
file?: string;
|
|
52
|
+
ignoreList?: number[];
|
|
50
53
|
mappings: string;
|
|
51
54
|
names?: string[];
|
|
52
55
|
sourceRoot?: string;
|
|
53
56
|
sources: string[];
|
|
54
57
|
sourcesContent?: string[];
|
|
55
58
|
version: 3;
|
|
59
|
+
x_google_ignoreList?: number[];
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
// has a scheme, e.g. http://, https://, file://, data:, etc.
|
|
@@ -77,6 +81,7 @@ const getSourceFromMappings = (
|
|
|
77
81
|
sources: string[],
|
|
78
82
|
lineIndexInMappings: number,
|
|
79
83
|
column: number,
|
|
84
|
+
ignoredSourceIndices?: Set<number>,
|
|
80
85
|
): StackFrame | null => {
|
|
81
86
|
if (lineIndexInMappings < 0 || lineIndexInMappings >= mappings.length) {
|
|
82
87
|
return null;
|
|
@@ -87,12 +92,18 @@ const getSourceFromMappings = (
|
|
|
87
92
|
return null;
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
// Segments within a line are sorted by generated column, so binary search for
|
|
96
|
+
// the last segment at or before the column.
|
|
90
97
|
let closestLineSegment: null | SourceMapSegment = null;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
let lowIndex = 0;
|
|
99
|
+
let highIndex = lineMapping.length - 1;
|
|
100
|
+
while (lowIndex <= highIndex) {
|
|
101
|
+
const middleIndex = (lowIndex + highIndex) >> 1;
|
|
102
|
+
if (lineMapping[middleIndex][0] <= column) {
|
|
103
|
+
closestLineSegment = lineMapping[middleIndex];
|
|
104
|
+
lowIndex = middleIndex + 1;
|
|
94
105
|
} else {
|
|
95
|
-
|
|
106
|
+
highIndex = middleIndex - 1;
|
|
96
107
|
}
|
|
97
108
|
}
|
|
98
109
|
|
|
@@ -116,6 +127,7 @@ const getSourceFromMappings = (
|
|
|
116
127
|
columnNumber: sourceColumn,
|
|
117
128
|
fileName,
|
|
118
129
|
lineNumber: sourceLine + 1,
|
|
130
|
+
isIgnoreListed: ignoredSourceIndices?.has(sourceIndex) ?? false,
|
|
119
131
|
};
|
|
120
132
|
};
|
|
121
133
|
|
|
@@ -125,12 +137,14 @@ export const getSourceFromSourceMap = (
|
|
|
125
137
|
column: number,
|
|
126
138
|
): StackFrame | null => {
|
|
127
139
|
if (sourceMap.sections) {
|
|
140
|
+
// Section offsets are 0-based while stack trace lines are 1-based.
|
|
141
|
+
const lineIndex = line - 1;
|
|
128
142
|
let targetSection: DecodedSourceMapSection | null = null;
|
|
129
143
|
|
|
130
144
|
for (const section of sourceMap.sections) {
|
|
131
145
|
if (
|
|
132
|
-
|
|
133
|
-
(
|
|
146
|
+
lineIndex > section.offset.line ||
|
|
147
|
+
(lineIndex === section.offset.line && column >= section.offset.column)
|
|
134
148
|
) {
|
|
135
149
|
targetSection = section;
|
|
136
150
|
} else {
|
|
@@ -142,29 +156,40 @@ export const getSourceFromSourceMap = (
|
|
|
142
156
|
return null;
|
|
143
157
|
}
|
|
144
158
|
|
|
145
|
-
const relativeLine =
|
|
159
|
+
const relativeLine = lineIndex - targetSection.offset.line;
|
|
146
160
|
const relativeColumn =
|
|
147
|
-
|
|
161
|
+
lineIndex === targetSection.offset.line ? column - targetSection.offset.column : column;
|
|
148
162
|
|
|
149
163
|
return getSourceFromMappings(
|
|
150
164
|
targetSection.map.mappings,
|
|
151
165
|
targetSection.map.sources,
|
|
152
166
|
relativeLine,
|
|
153
167
|
relativeColumn,
|
|
168
|
+
targetSection.map.ignoredSourceIndices,
|
|
154
169
|
);
|
|
155
170
|
}
|
|
156
171
|
|
|
157
|
-
return getSourceFromMappings(
|
|
172
|
+
return getSourceFromMappings(
|
|
173
|
+
sourceMap.mappings,
|
|
174
|
+
sourceMap.sources,
|
|
175
|
+
line - 1,
|
|
176
|
+
column,
|
|
177
|
+
sourceMap.ignoredSourceIndices,
|
|
178
|
+
);
|
|
158
179
|
};
|
|
159
180
|
|
|
160
181
|
const getSourceMapUrl = (url: string, content: string): null | string => {
|
|
161
|
-
|
|
182
|
+
// Walk lines backwards without content.split("\n"), which would allocate a
|
|
183
|
+
// string per line of the entire bundle.
|
|
162
184
|
let sourceMapUrl: string | undefined;
|
|
163
|
-
|
|
164
|
-
|
|
185
|
+
let searchEnd = content.length;
|
|
186
|
+
while (searchEnd > 0 && !sourceMapUrl) {
|
|
187
|
+
const lineStart = content.lastIndexOf("\n", searchEnd - 1) + 1;
|
|
188
|
+
const regexMatch = content.slice(lineStart, searchEnd).match(SOURCEMAP_REGEX);
|
|
165
189
|
if (regexMatch) {
|
|
166
190
|
sourceMapUrl = regexMatch[1] || regexMatch[2];
|
|
167
191
|
}
|
|
192
|
+
searchEnd = lineStart - 1;
|
|
168
193
|
}
|
|
169
194
|
|
|
170
195
|
if (!sourceMapUrl) {
|
|
@@ -181,8 +206,14 @@ const getSourceMapUrl = (url: string, content: string): null | string => {
|
|
|
181
206
|
return sourceMapUrl;
|
|
182
207
|
};
|
|
183
208
|
|
|
209
|
+
const getIgnoredSourceIndices = (rawSourceMap: StandardSourceMap): Set<number> | undefined => {
|
|
210
|
+
const ignoreList = rawSourceMap.ignoreList ?? rawSourceMap.x_google_ignoreList;
|
|
211
|
+
return Array.isArray(ignoreList) && ignoreList.length > 0 ? new Set(ignoreList) : undefined;
|
|
212
|
+
};
|
|
213
|
+
|
|
184
214
|
const decodeStandardSourceMap = (rawSourceMap: StandardSourceMap): SourceMap => ({
|
|
185
215
|
file: rawSourceMap.file,
|
|
216
|
+
ignoredSourceIndices: getIgnoredSourceIndices(rawSourceMap),
|
|
186
217
|
mappings: decode(rawSourceMap.mappings),
|
|
187
218
|
names: rawSourceMap.names,
|
|
188
219
|
sourceRoot: rawSourceMap.sourceRoot,
|
|
@@ -196,6 +227,7 @@ const decodeIndexSourceMap = (rawSourceMap: IndexSourceMap): SourceMap => {
|
|
|
196
227
|
({ map, offset }) => ({
|
|
197
228
|
map: {
|
|
198
229
|
...map,
|
|
230
|
+
ignoredSourceIndices: getIgnoredSourceIndices(map),
|
|
199
231
|
mappings: decode(map.mappings),
|
|
200
232
|
},
|
|
201
233
|
offset,
|
|
@@ -268,7 +300,9 @@ export const getSourceMapImpl = async (
|
|
|
268
300
|
const sourceMapUrl = getSourceMapUrl(bundleUrl, bundleContent);
|
|
269
301
|
|
|
270
302
|
if (!sourceMapUrl) return null;
|
|
271
|
-
|
|
303
|
+
// inline data: maps (vite dev, babel inline sourcemaps) are decoded by
|
|
304
|
+
// fetch itself, so they bypass the network-url check
|
|
305
|
+
if (!isFetchableUrl(sourceMapUrl) && !INLINE_SOURCEMAP_REGEX.test(sourceMapUrl)) {
|
|
272
306
|
return null;
|
|
273
307
|
}
|
|
274
308
|
|
|
@@ -356,6 +390,7 @@ export const symbolicateStack = async (
|
|
|
356
390
|
fileName: symbolicatedSource.fileName,
|
|
357
391
|
lineNumber: symbolicatedSource.lineNumber,
|
|
358
392
|
columnNumber: symbolicatedSource.columnNumber,
|
|
393
|
+
isIgnoreListed: symbolicatedSource.isIgnoreListed,
|
|
359
394
|
isSymbolicated: true,
|
|
360
395
|
};
|
|
361
396
|
}),
|
package/src/types.ts
CHANGED
|
@@ -298,6 +298,22 @@ export interface Family {
|
|
|
298
298
|
current: unknown;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
/**
|
|
302
|
+
* React 19 flight metadata for a server component owner (ReactComponentInfo).
|
|
303
|
+
* Unlike client owners it has no `tag`; the owner chain continues via `owner`.
|
|
304
|
+
*/
|
|
305
|
+
export interface ServerComponentInfo {
|
|
306
|
+
name?: string;
|
|
307
|
+
env?: string;
|
|
308
|
+
owner?: Fiber | ServerComponentInfo | null;
|
|
309
|
+
debugStack?: Error | null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export interface RendererRefreshUpdate {
|
|
313
|
+
staleFamilies: Set<Family>;
|
|
314
|
+
updatedFamilies: Set<Family>;
|
|
315
|
+
}
|
|
316
|
+
|
|
301
317
|
/**
|
|
302
318
|
* Represents a react-internal Fiber node.
|
|
303
319
|
*/
|
|
@@ -367,6 +383,9 @@ export interface ReactDevToolsGlobalHook {
|
|
|
367
383
|
onCommitFiberRoot: (rendererID: number, root: FiberRoot, priority: number | void) => void;
|
|
368
384
|
onCommitFiberUnmount: (rendererID: number, fiber: Fiber) => void;
|
|
369
385
|
onPostCommitFiberRoot: (rendererID: number, root: FiberRoot) => void;
|
|
386
|
+
// called by dev builds of react-reconciler on root schedule; absent from
|
|
387
|
+
// the hook react-refresh installs, so it stays optional
|
|
388
|
+
onScheduleFiberRoot?: (rendererID: number, root: FiberRoot, children: ReactNode) => void;
|
|
370
389
|
renderers: Map<number, ReactRenderer>;
|
|
371
390
|
supportsFiber: boolean;
|
|
372
391
|
|
|
@@ -402,13 +421,7 @@ export interface ReactRenderer {
|
|
|
402
421
|
reconcilerVersion: string;
|
|
403
422
|
rendererPackageName: string;
|
|
404
423
|
// react refresh
|
|
405
|
-
scheduleRefresh?: (
|
|
406
|
-
root: FiberRoot,
|
|
407
|
-
update: {
|
|
408
|
-
staleFamilies: Set<Family>;
|
|
409
|
-
updatedFamilies: Set<Family>;
|
|
410
|
-
},
|
|
411
|
-
) => void;
|
|
424
|
+
scheduleRefresh?: (root: FiberRoot, update: RendererRefreshUpdate) => void;
|
|
412
425
|
scheduleRoot?: (root: FiberRoot, element: React.ReactNode) => void;
|
|
413
426
|
scheduleUpdate?: (fiber: Fiber) => void;
|
|
414
427
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface Unsubscribe extends Disposable {
|
|
2
|
+
(): void;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Wraps a teardown callback so it is both callable and a `Disposable`,
|
|
7
|
+
* letting subscriptions compose through explicit resource management:
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* using instrumentation = instrument({ onCommitFiberRoot });
|
|
12
|
+
* using refresh = instrumentReactRefresh({ onRefresh: handleRefresh });
|
|
13
|
+
* // both torn down automatically at scope exit
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export const toUnsubscribe = (dispose: () => void): Unsubscribe =>
|
|
17
|
+
Object.assign(dispose, { [Symbol.dispose]: dispose });
|