mirror of
https://github.com/facebook/react.git
synced 2026-02-21 19:31:52 +00:00
[DevTools] Ignore List Stack Traces (#34210)
Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
This commit is contained in:
committed by
GitHub
parent
7d29ecbeb2
commit
a85ec041d6
@@ -15,6 +15,7 @@ const TerserPlugin = require('terser-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||
const DevToolsIgnorePlugin = require('devtools-ignore-webpack-plugin');
|
||||
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
|
||||
const paths = require('./paths');
|
||||
const modules = require('./modules');
|
||||
@@ -685,6 +686,15 @@ module.exports = function (webpackEnv) {
|
||||
},
|
||||
}),
|
||||
// Fork Start
|
||||
new DevToolsIgnorePlugin({
|
||||
shouldIgnorePath: function (path) {
|
||||
return (
|
||||
path.includes('/node_modules/') ||
|
||||
path.includes('/webpack/') ||
|
||||
path.endsWith('/src/index.js')
|
||||
);
|
||||
},
|
||||
}),
|
||||
new ReactFlightWebpackPlugin({
|
||||
isServer: false,
|
||||
clientReferences: {
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"concurrently": "^7.3.0",
|
||||
"css-loader": "^6.5.1",
|
||||
"css-minimizer-webpack-plugin": "^3.2.0",
|
||||
"devtools-ignore-webpack-plugin": "^0.2.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"dotenv-expand": "^5.1.0",
|
||||
"file-loader": "^6.2.0",
|
||||
|
||||
@@ -58,7 +58,8 @@ function filterStackFrame(sourceURL, functionName) {
|
||||
sourceURL !== '' &&
|
||||
!sourceURL.startsWith('node:') &&
|
||||
!sourceURL.includes('node_modules') &&
|
||||
!sourceURL.endsWith('library.js')
|
||||
!sourceURL.endsWith('library.js') &&
|
||||
!sourceURL.includes('/server/region.js')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4614,6 +4614,11 @@ detect-port-alt@^1.1.6:
|
||||
address "^1.0.1"
|
||||
debug "^2.6.0"
|
||||
|
||||
devtools-ignore-webpack-plugin@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/devtools-ignore-webpack-plugin/-/devtools-ignore-webpack-plugin-0.2.0.tgz#a7b3d1bd0f593c7fee5cbb7534b07860e5e2447c"
|
||||
integrity sha512-4P+1Y1VhSt1MRBRF6my8N1bs9nNMOFfIFSBHI6u18W73iCHWXNHTSWYeMoQQ72PIIHrP1q4koKpYg1Em3jb9Rw==
|
||||
|
||||
didyoumean@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
|
||||
@@ -8650,16 +8655,7 @@ string-length@^5.0.1:
|
||||
char-regex "^2.0.0"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -8730,14 +8726,7 @@ string_decoder@^1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@@ -9452,16 +9441,7 @@ wordwrap@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
||||
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
|
||||
@@ -401,7 +401,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.f = f;
|
||||
function f() { }
|
||||
//# sourceMappingURL=`;
|
||||
const result = ['', 'http://test/a.mts', 1, 17];
|
||||
const result = {
|
||||
location: ['', 'http://test/a.mts', 1, 17],
|
||||
ignored: false,
|
||||
};
|
||||
const fs = {
|
||||
'http://test/a.mts': `export function f() {}`,
|
||||
'http://test/a.mjs.map': `{"version":3,"file":"a.mjs","sourceRoot":"","sources":["a.mts"],"names":[],"mappings":";;AAAA,cAAsB;AAAtB,SAAgB,CAAC,KAAI,CAAC"}`,
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource';
|
||||
|
||||
import * as React from 'react';
|
||||
import {useCallback, useContext, useSyncExternalStore} from 'react';
|
||||
import {TreeStateContext} from './TreeContext';
|
||||
@@ -28,8 +30,6 @@ import useEditorURL from '../useEditorURL';
|
||||
|
||||
import styles from './InspectedElement.css';
|
||||
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
|
||||
export type Props = {};
|
||||
|
||||
// TODO Make edits and deletes also use transition API!
|
||||
@@ -61,7 +61,7 @@ export default function InspectedElementWrapper(_: Props): React.Node {
|
||||
? inspectedElement.stack[0]
|
||||
: null;
|
||||
|
||||
const symbolicatedSourcePromise: Promise<ReactFunctionLocation | null> =
|
||||
const symbolicatedSourcePromise: Promise<SourceMappedLocation | null> =
|
||||
React.useMemo(() => {
|
||||
if (fetchFileWithCaching == null) return noSourcePromise;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/wit
|
||||
|
||||
import useOpenResource from '../useOpenResource';
|
||||
|
||||
import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
import styles from './InspectedElementSourcePanel.css';
|
||||
|
||||
@@ -24,7 +25,7 @@ import formatLocationForDisplay from './formatLocationForDisplay';
|
||||
|
||||
type Props = {
|
||||
source: ReactFunctionLocation,
|
||||
symbolicatedSourcePromise: Promise<ReactFunctionLocation | null>,
|
||||
symbolicatedSourcePromise: Promise<SourceMappedLocation | null>,
|
||||
};
|
||||
|
||||
function InspectedElementSourcePanel({
|
||||
@@ -80,7 +81,7 @@ function CopySourceButton({source, symbolicatedSourcePromise}: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const [, sourceURL, line, column] = symbolicatedSource;
|
||||
const [, sourceURL, line, column] = symbolicatedSource.location;
|
||||
const handleCopy = withPermissionsCheck(
|
||||
{permissions: ['clipboardWrite']},
|
||||
() => copy(`${sourceURL}:${line}:${column}`),
|
||||
@@ -98,11 +99,11 @@ function FormattedSourceString({source, symbolicatedSourcePromise}: Props) {
|
||||
|
||||
const [linkIsEnabled, viewSource] = useOpenResource(
|
||||
source,
|
||||
symbolicatedSource,
|
||||
symbolicatedSource == null ? null : symbolicatedSource.location,
|
||||
);
|
||||
|
||||
const [, sourceURL, line, column] =
|
||||
symbolicatedSource == null ? source : symbolicatedSource;
|
||||
symbolicatedSource == null ? source : symbolicatedSource.location;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -34,7 +34,7 @@ import type {
|
||||
} from 'react-devtools-shared/src/frontend/types';
|
||||
import type {HookNames} from 'react-devtools-shared/src/frontend/types';
|
||||
import type {ToggleParseHookNames} from './InspectedElementContext';
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource';
|
||||
|
||||
type Props = {
|
||||
element: Element,
|
||||
@@ -42,7 +42,7 @@ type Props = {
|
||||
inspectedElement: InspectedElement,
|
||||
parseHookNames: boolean,
|
||||
toggleParseHookNames: ToggleParseHookNames,
|
||||
symbolicatedSourcePromise: Promise<ReactFunctionLocation | null>,
|
||||
symbolicatedSourcePromise: Promise<SourceMappedLocation | null>,
|
||||
};
|
||||
|
||||
export default function InspectedElementView({
|
||||
|
||||
@@ -13,12 +13,13 @@ import ButtonIcon from '../ButtonIcon';
|
||||
import Button from '../Button';
|
||||
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource';
|
||||
|
||||
import useOpenResource from '../useOpenResource';
|
||||
|
||||
type Props = {
|
||||
source: null | ReactFunctionLocation,
|
||||
symbolicatedSourcePromise: Promise<ReactFunctionLocation | null> | null,
|
||||
symbolicatedSourcePromise: Promise<SourceMappedLocation | null> | null,
|
||||
};
|
||||
|
||||
function InspectedElementViewSourceButton({
|
||||
@@ -42,7 +43,7 @@ function InspectedElementViewSourceButton({
|
||||
|
||||
type ActualSourceButtonProps = {
|
||||
source: null | ReactFunctionLocation,
|
||||
symbolicatedSourcePromise: Promise<ReactFunctionLocation | null> | null,
|
||||
symbolicatedSourcePromise: Promise<SourceMappedLocation | null> | null,
|
||||
};
|
||||
function ActualSourceButton({
|
||||
source,
|
||||
@@ -55,7 +56,7 @@ function ActualSourceButton({
|
||||
|
||||
const [buttonIsEnabled, viewSource] = useOpenResource(
|
||||
source,
|
||||
symbolicatedSource,
|
||||
symbolicatedSource == null ? null : symbolicatedSource.location,
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
|
||||
@@ -13,13 +13,14 @@ import Button from 'react-devtools-shared/src/devtools/views/Button';
|
||||
import ButtonIcon from 'react-devtools-shared/src/devtools/views/ButtonIcon';
|
||||
|
||||
import type {ReactFunctionLocation} from 'shared/ReactTypes';
|
||||
import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource';
|
||||
|
||||
import {checkConditions} from '../Editor/utils';
|
||||
|
||||
type Props = {
|
||||
editorURL: string,
|
||||
source: ReactFunctionLocation,
|
||||
symbolicatedSourcePromise: Promise<ReactFunctionLocation | null>,
|
||||
symbolicatedSourcePromise: Promise<SourceMappedLocation | null>,
|
||||
};
|
||||
|
||||
function OpenInEditorButton({
|
||||
@@ -31,7 +32,7 @@ function OpenInEditorButton({
|
||||
|
||||
const {url, shouldDisableButton} = checkConditions(
|
||||
editorURL,
|
||||
symbolicatedSource ? symbolicatedSource : source,
|
||||
symbolicatedSource ? symbolicatedSource.location : source,
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.CallSite {
|
||||
.CallSite, .IgnoredCallSite {
|
||||
display: block;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.IgnoredCallSite {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.Link {
|
||||
color: var(--color-link);
|
||||
white-space: pre;
|
||||
|
||||
@@ -16,11 +16,9 @@ import ElementBadges from './ElementBadges';
|
||||
|
||||
import styles from './StackTraceView.css';
|
||||
|
||||
import type {
|
||||
ReactStackTrace,
|
||||
ReactCallSite,
|
||||
ReactFunctionLocation,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {ReactStackTrace, ReactCallSite} from 'shared/ReactTypes';
|
||||
|
||||
import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource';
|
||||
|
||||
import FetchFileWithCachingContext from './FetchFileWithCachingContext';
|
||||
|
||||
@@ -42,7 +40,7 @@ export function CallSiteView({
|
||||
const [virtualFunctionName, virtualURL, virtualLine, virtualColumn] =
|
||||
callSite;
|
||||
|
||||
const symbolicatedCallSite: null | ReactFunctionLocation =
|
||||
const symbolicatedCallSite: null | SourceMappedLocation =
|
||||
fetchFileWithCaching !== null
|
||||
? use(
|
||||
symbolicateSourceWithCache(
|
||||
@@ -56,12 +54,20 @@ export function CallSiteView({
|
||||
|
||||
const [linkIsEnabled, viewSource] = useOpenResource(
|
||||
callSite,
|
||||
symbolicatedCallSite,
|
||||
symbolicatedCallSite == null ? null : symbolicatedCallSite.location,
|
||||
);
|
||||
const [functionName, url, line, column] =
|
||||
symbolicatedCallSite !== null ? symbolicatedCallSite : callSite;
|
||||
symbolicatedCallSite !== null ? symbolicatedCallSite.location : callSite;
|
||||
const ignored =
|
||||
symbolicatedCallSite !== null ? symbolicatedCallSite.ignored : false;
|
||||
if (ignored) {
|
||||
// TODO: Make an option to be able to toggle the display of ignore listed rows.
|
||||
// Ideally this UI should be higher than a single Stack Trace so that there's not
|
||||
// multiple buttons in a single inspection taking up space.
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={styles.CallSite}>
|
||||
<div className={ignored ? styles.IgnoredCallSite : styles.CallSite}>
|
||||
{functionName || virtualFunctionName}
|
||||
{url !== '' && (
|
||||
<>
|
||||
|
||||
@@ -26,6 +26,7 @@ type ResultPosition = {
|
||||
line: number,
|
||||
sourceContent: string | null,
|
||||
sourceURL: string | null,
|
||||
ignored: boolean,
|
||||
};
|
||||
|
||||
export type SourceMapConsumerType = {
|
||||
@@ -117,12 +118,15 @@ function BasicSourceMapConsumer(sourceMapJSON: BasicSourceMap) {
|
||||
const sourceURL = sourceMapJSON.sources[sourceIndex] ?? null;
|
||||
const line = nearestEntry[2] + 1;
|
||||
const column = nearestEntry[3];
|
||||
|
||||
const ignored =
|
||||
sourceMapJSON.ignoreList != null &&
|
||||
sourceMapJSON.ignoreList.includes(sourceIndex);
|
||||
return {
|
||||
column,
|
||||
line,
|
||||
sourceContent: ((sourceContent: any): string | null),
|
||||
sourceURL: ((sourceURL: any): string | null),
|
||||
ignored,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ export type BasicSourceMap = {
|
||||
+version: number,
|
||||
+x_facebook_sources?: FBSourcesArray,
|
||||
+x_react_sources?: ReactSourcesArray,
|
||||
+ignoreList?: Array<number>,
|
||||
};
|
||||
|
||||
export type IndexSourceMapSection = {
|
||||
|
||||
@@ -14,15 +14,20 @@ import type {FetchFileWithCaching} from 'react-devtools-shared/src/devtools/view
|
||||
|
||||
const symbolicationCache: Map<
|
||||
string,
|
||||
Promise<ReactFunctionLocation | null>,
|
||||
Promise<SourceMappedLocation | null>,
|
||||
> = new Map();
|
||||
|
||||
export type SourceMappedLocation = {
|
||||
location: ReactFunctionLocation,
|
||||
ignored: boolean, // Whether the file for this location was ignore listed
|
||||
};
|
||||
|
||||
export function symbolicateSourceWithCache(
|
||||
fetchFileWithCaching: FetchFileWithCaching,
|
||||
sourceURL: string,
|
||||
line: number, // 1-based
|
||||
column: number, // 1-based
|
||||
): Promise<ReactFunctionLocation | null> {
|
||||
): Promise<SourceMappedLocation | null> {
|
||||
const key = `${sourceURL}:${line}:${column}`;
|
||||
const cachedPromise = symbolicationCache.get(key);
|
||||
if (cachedPromise != null) {
|
||||
@@ -46,7 +51,7 @@ export async function symbolicateSource(
|
||||
sourceURL: string,
|
||||
lineNumber: number, // 1-based
|
||||
columnNumber: number, // 1-based
|
||||
): Promise<ReactFunctionLocation | null> {
|
||||
): Promise<SourceMappedLocation | null> {
|
||||
const resource = await fetchFileWithCaching(sourceURL).catch(() => null);
|
||||
if (resource == null) {
|
||||
return null;
|
||||
@@ -83,6 +88,7 @@ export async function symbolicateSource(
|
||||
sourceURL: possiblyURL,
|
||||
line,
|
||||
column: columnZeroBased,
|
||||
ignored,
|
||||
} = consumer.originalPositionFor({
|
||||
lineNumber, // 1-based
|
||||
columnNumber, // 1-based
|
||||
@@ -97,7 +103,10 @@ export async function symbolicateSource(
|
||||
// sourceMapURL = https://react.dev/script.js.map
|
||||
void new URL(possiblyURL); // test if it is a valid URL
|
||||
|
||||
return [functionName, possiblyURL, line, column];
|
||||
return {
|
||||
location: [functionName, possiblyURL, line, column],
|
||||
ignored,
|
||||
};
|
||||
} catch (e) {
|
||||
// This is not valid URL
|
||||
if (
|
||||
@@ -107,7 +116,10 @@ export async function symbolicateSource(
|
||||
possiblyURL.slice(1).startsWith(':\\\\')
|
||||
) {
|
||||
// This is an absolute path
|
||||
return [functionName, possiblyURL, line, column];
|
||||
return {
|
||||
location: [functionName, possiblyURL, line, column],
|
||||
ignored,
|
||||
};
|
||||
}
|
||||
|
||||
// This is a relative path
|
||||
@@ -116,7 +128,10 @@ export async function symbolicateSource(
|
||||
possiblyURL,
|
||||
sourceMapURL,
|
||||
).toString();
|
||||
return [functionName, absoluteSourcePath, line, column];
|
||||
return {
|
||||
location: [functionName, absoluteSourcePath, line, column],
|
||||
ignored,
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user