DevTools: Add break-on-warn feature (#19048)
This commit adds a new tab to the Settings modal: Debugging This new tab has the append component stacks feature and a new one: break on warn This new feature adds a debugger statement into the console override
Brian Vaughn committed
May 29, 2020 at 14:34 UTC
2efe63d99cdeb338077bf148babffd7bb599d182
19 files changed
+281
-79
packages/react-devtools-core/src/standalone.js
+8
-4
@@ -17,8 +17,9 @@ import {
17
import Bridge from 'react-devtools-shared/src/bridge';
18
import Store from 'react-devtools-shared/src/devtools/store';
19
import {
20
- getSavedComponentFilters,
20
getAppendComponentStack,
21
+ getBreakOnConsoleErrors,
22
+ getSavedComponentFilters,
23
} from 'react-devtools-shared/src/utils';
24
import {Server} from 'ws';
25
import {join} from 'path';
@@ -282,11 +283,14 @@ function startServer(port?: number = 8097) {
283
// Because of this it relies on the extension to pass filters, so include them wth the response here.
284
// This will ensure that saved filters are shared across different web pages.
285
const savedPreferencesString = `
285
- window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = ${JSON.stringify(
286
- getSavedComponentFilters(),
287
- )};
286
window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = ${JSON.stringify(
287
getAppendComponentStack(),
288
+ )};
289
+ window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ = ${JSON.stringify(
290
+ getBreakOnConsoleErrors(),
291
+ )};
292
+ window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = ${JSON.stringify(
293
+ getSavedComponentFilters(),
294
)};`;
295
296
response.end(
packages/react-devtools-extensions/src/main.js
+10
-8
@@ -7,8 +7,9 @@ import Store from 'react-devtools-shared/src/devtools/store';
7
import {getBrowserName, getBrowserTheme} from './utils';
8
import {LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY} from 'react-devtools-shared/src/constants';
9
import {
10
- getSavedComponentFilters,
10
getAppendComponentStack,
11
+ getBreakOnConsoleErrors,
12
+ getSavedComponentFilters,
13
} from 'react-devtools-shared/src/utils';
14
import {
15
localStorageGetItem,
@@ -28,17 +29,18 @@ let panelCreated = false;
29
// because they are stored in localStorage within the context of the extension.
30
// Instead it relies on the extension to pass filters through.
31
function syncSavedPreferences() {
31
- const componentFilters = getSavedComponentFilters();
32
- chrome.devtools.inspectedWindow.eval(
33
- `window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = ${JSON.stringify(
34
- componentFilters,
35
- )};`,
36
- );
37
-
32
const appendComponentStack = getAppendComponentStack();
33
+ const breakOnConsoleErrors = getBreakOnConsoleErrors();
34
+ const componentFilters = getSavedComponentFilters();
35
chrome.devtools.inspectedWindow.eval(
36
`window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = ${JSON.stringify(
37
appendComponentStack,
38
+ )};
39
+ window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ = ${JSON.stringify(
40
+ breakOnConsoleErrors,
41
+ )};
42
+ window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = ${JSON.stringify(
43
+ componentFilters,
44
)};`,
45
);
46
}
packages/react-devtools-extensions/webpack.backend.js
+11
@@ -2,6 +2,7 @@
2
3
const {resolve} = require('path');
4
const {DefinePlugin} = require('webpack');
5
+const TerserPlugin = require('terser-webpack-plugin');
6
const {GITHUB_URL, getVersionString} = require('./utils');
7
8
const NODE_ENV = process.env.NODE_ENV;
@@ -39,6 +40,16 @@ module.exports = {
40
scheduler: resolve(builtModulesDir, 'scheduler'),
41
},
42
},
43
+ optimization: {
44
+ minimizer: [
45
+ new TerserPlugin({
46
+ terserOptions: {
47
+ compress: {drop_debugger: false},
48
+ output: {comments: true},
49
+ },
50
+ }),
51
+ ],
52
+ },
53
plugins: [
54
new DefinePlugin({
55
__DEV__: true,
packages/react-devtools-inline/src/backend.js
+7
-1
@@ -20,9 +20,14 @@ function startActivation(contentWindow: window) {
20
// so it's safe to cleanup after we've received it.
21
contentWindow.removeEventListener('message', onMessage);
22
23
- const {appendComponentStack, componentFilters} = data;
23
+ const {
24
+ appendComponentStack,
25
+ breakOnConsoleErrors,
26
+ componentFilters,
27
+ } = data;
28
29
contentWindow.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = appendComponentStack;
30
+ contentWindow.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ = breakOnConsoleErrors;
31
contentWindow.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;
32
33
// TRICKY
@@ -33,6 +38,7 @@ function startActivation(contentWindow: window) {
38
// but it doesn't really hurt anything to store them there too.
39
if (contentWindow !== window) {
40
window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = appendComponentStack;
41
+ window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ = breakOnConsoleErrors;
42
window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;
43
}
44
packages/react-devtools-inline/src/frontend.js
+3
-1
@@ -6,8 +6,9 @@ import Bridge from 'react-devtools-shared/src/bridge';
6
import Store from 'react-devtools-shared/src/devtools/store';
7
import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';
8
import {
9
- getSavedComponentFilters,
9
getAppendComponentStack,
10
+ getBreakOnConsoleErrors,
11
+ getSavedComponentFilters,
12
} from 'react-devtools-shared/src/utils';
13
import {
14
MESSAGE_TYPE_GET_SAVED_PREFERENCES,
@@ -38,6 +39,7 @@ export function initialize(
39
{
40
type: MESSAGE_TYPE_SAVED_PREFERENCES,
41
appendComponentStack: getAppendComponentStack(),
42
+ breakOnConsoleErrors: getBreakOnConsoleErrors(),
43
componentFilters: getSavedComponentFilters(),
44
},
45
'*',
packages/react-devtools-inline/webpack.config.js
+11
@@ -1,5 +1,6 @@
1
const {resolve} = require('path');
2
const {DefinePlugin} = require('webpack');
3
+const TerserPlugin = require('terser-webpack-plugin');
4
const {
5
GITHUB_URL,
6
getVersionString,
@@ -36,6 +37,16 @@ module.exports = {
37
'react-is': 'react-is',
38
scheduler: 'scheduler',
39
},
40
+ optimization: {
41
+ minimizer: [
42
+ new TerserPlugin({
43
+ terserOptions: {
44
+ compress: {drop_debugger: false},
45
+ output: {comments: true},
46
+ },
47
+ }),
48
+ ],
49
+ },
50
plugins: [
51
new DefinePlugin({
52
__DEV__,
packages/react-devtools-shared/src/__tests__/console-test.js
+12
-3
@@ -44,7 +44,10 @@ describe('console', () => {
44
45
// Note the Console module only patches once,
46
// so it's important to patch the test console before injection.
47
- patchConsole();
47
+ patchConsole({
48
+ appendComponentStack: true,
49
+ breakOnWarn: false,
50
+ });
51
52
const inject = global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;
53
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = internals => {
@@ -79,7 +82,10 @@ describe('console', () => {
82
it('should only patch the console once', () => {
83
const {error, warn} = fakeConsole;
84
82
- patchConsole();
85
+ patchConsole({
86
+ appendComponentStack: true,
87
+ breakOnWarn: false,
88
+ });
89
90
expect(fakeConsole.error).toBe(error);
91
expect(fakeConsole.warn).toBe(warn);
@@ -330,7 +336,10 @@ describe('console', () => {
336
expect(mockError.mock.calls[0]).toHaveLength(1);
337
expect(mockError.mock.calls[0][0]).toBe('error');
338
333
- patchConsole();
339
+ patchConsole({
340
+ appendComponentStack: true,
341
+ breakOnWarn: false,
342
+ });
343
act(() => ReactDOM.render(<Child />, document.createElement('div')));
344
345
expect(mockWarn).toHaveBeenCalledTimes(2);
packages/react-devtools-shared/src/backend/agent.js
+11
-5
@@ -161,8 +161,8 @@ export default class Agent extends EventEmitter<{|
161
);
162
bridge.addListener('shutdown', this.shutdown);
163
bridge.addListener(
164
- 'updateAppendComponentStack',
165
- this.updateAppendComponentStack,
164
+ 'updateConsolePatchSettings',
165
+ this.updateConsolePatchSettings,
166
);
167
bridge.addListener('updateComponentFilters', this.updateComponentFilters);
168
bridge.addListener('viewAttributeSource', this.viewAttributeSource);
@@ -443,13 +443,19 @@ export default class Agent extends EventEmitter<{|
443
}
444
};
445
446
- updateAppendComponentStack = (appendComponentStack: boolean) => {
446
+ updateConsolePatchSettings = ({
447
+ appendComponentStack,
448
+ breakOnConsoleErrors,
449
+ }: {|
450
+ appendComponentStack: boolean,
451
+ breakOnConsoleErrors: boolean,
452
+ |}) => {
453
// If the frontend preference has change,
454
// or in the case of React Native- if the backend is just finding out the preference-
455
// then install or uninstall the console overrides.
456
// It's safe to call these methods multiple times, so we don't need to worry about that.
451
- if (appendComponentStack) {
452
- patchConsole();
457
+ if (appendComponentStack || breakOnConsoleErrors) {
458
+ patchConsole({appendComponentStack, breakOnConsoleErrors});
459
} else {
460
unpatchConsole();
461
}
packages/react-devtools-shared/src/backend/console.js
+63
-31
@@ -80,9 +80,25 @@ export function registerRenderer(renderer: ReactRenderer): void {
80
}
81
}
82
83
+const consoleSettingsRef = {
84
+ appendComponentStack: false,
85
+ breakOnConsoleErrors: false,
86
+};
87
+
88
// Patches whitelisted console methods to append component stack for the current fiber.
89
// Call unpatch() to remove the injected behavior.
85
-export function patch(): void {
90
+export function patch({
91
+ appendComponentStack,
92
+ breakOnConsoleErrors,
93
+}: {
94
+ appendComponentStack: boolean,
95
+ breakOnConsoleErrors: boolean,
96
+}): void {
97
+ // Settings may change after we've patched the console.
98
+ // Using a shared ref allows the patch function to read the latest values.
99
+ consoleSettingsRef.appendComponentStack = appendComponentStack;
100
+ consoleSettingsRef.breakOnConsoleErrors = breakOnConsoleErrors;
101
+
102
if (unpatchFn !== null) {
103
// Don't patch twice.
104
return;
@@ -105,40 +121,56 @@ export function patch(): void {
121
targetConsole[method]);
122
123
const overrideMethod = (...args) => {
108
- try {
109
- // If we are ever called with a string that already has a component stack, e.g. a React error/warning,
110
- // don't append a second stack.
111
- const lastArg = args.length > 0 ? args[args.length - 1] : null;
112
- const alreadyHasComponentStack =
113
- lastArg !== null &&
114
- (PREFIX_REGEX.test(lastArg) ||
115
- ROW_COLUMN_NUMBER_REGEX.test(lastArg));
116
-
117
- if (!alreadyHasComponentStack) {
118
- // If there's a component stack for at least one of the injected renderers, append it.
119
- // We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?)
120
- // eslint-disable-next-line no-for-of-loops/no-for-of-loops
121
- for (const {
122
- currentDispatcherRef,
123
- getCurrentFiber,
124
- workTagMap,
125
- } of injectedRenderers.values()) {
126
- const current: ?Fiber = getCurrentFiber();
127
- if (current != null) {
128
- const componentStack = getStackByFiberInDevAndProd(
129
- workTagMap,
130
- current,
131
- currentDispatcherRef,
132
- );
133
- if (componentStack !== '') {
134
- args.push(componentStack);
124
+ const latestAppendComponentStack =
125
+ consoleSettingsRef.appendComponentStack;
126
+ const latestBreakOnConsoleErrors =
127
+ consoleSettingsRef.breakOnConsoleErrors;
128
+
129
+ if (latestAppendComponentStack) {
130
+ try {
131
+ // If we are ever called with a string that already has a component stack, e.g. a React error/warning,
132
+ // don't append a second stack.
133
+ const lastArg = args.length > 0 ? args[args.length - 1] : null;
134
+ const alreadyHasComponentStack =
135
+ lastArg !== null &&
136
+ (PREFIX_REGEX.test(lastArg) ||
137
+ ROW_COLUMN_NUMBER_REGEX.test(lastArg));
138
+
139
+ if (!alreadyHasComponentStack) {
140
+ // If there's a component stack for at least one of the injected renderers, append it.
141
+ // We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?)
142
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
143
+ for (const {
144
+ currentDispatcherRef,
145
+ getCurrentFiber,
146
+ workTagMap,
147
+ } of injectedRenderers.values()) {
148
+ const current: ?Fiber = getCurrentFiber();
149
+ if (current != null) {
150
+ const componentStack = getStackByFiberInDevAndProd(
151
+ workTagMap,
152
+ current,
153
+ currentDispatcherRef,
154
+ );
155
+ if (componentStack !== '') {
156
+ args.push(componentStack);
157
+ }
158
+ break;
159
}
136
- break;
160
}
161
}
162
+ } catch (error) {
163
+ // Don't let a DevTools or React internal error interfere with logging.
164
}
140
- } catch (error) {
141
- // Don't let a DevTools or React internal error interfere with logging.
165
+ }
166
+
167
+ if (latestBreakOnConsoleErrors) {
168
+ // --- Welcome to debugging with React DevTools ---
169
+ // This debugger statement means that you've enabled the "break on warnings" feature.
170
+ // Use the browser's Call Stack panel to step out of this override function-
171
+ // to where the original warning or error was logged.
172
+ // eslint-disable-next-line no-debugger
173
+ debugger;
174
}
175
176
originalMethod(...args);
packages/react-devtools-shared/src/backend/renderer.js
+10
-3
@@ -430,11 +430,18 @@ export function attach(
430
if (process.env.NODE_ENV !== 'test') {
431
registerRendererWithConsole(renderer);
432
433
- // The renderer interface can't read this preference directly,
433
+ // The renderer interface can't read these preferences directly,
434
// because it is stored in localStorage within the context of the extension.
435
// It relies on the extension to pass the preference through via the global.
436
- if (window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ !== false) {
437
- patchConsole();
436
+ const appendComponentStack =
437
+ window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ !== false;
438
+ const breakOnConsoleErrors =
439
+ window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ === true;
440
+ if (appendComponentStack || breakOnConsoleErrors) {
441
+ patchConsole({
442
+ appendComponentStack,
443
+ breakOnConsoleErrors,
444
+ });
445
}
446
}
447
packages/react-devtools-shared/src/bridge.js
+6
-1
@@ -85,6 +85,11 @@ type NativeStyleEditor_SetValueParams = {|
85
value: string,
86
|};
87
88
+type UpdateConsolePatchSettingsParams = {|
89
+ appendComponentStack: boolean,
90
+ breakOnConsoleErrors: boolean,
91
+|};
92
+
93
type BackendEvents = {|
94
extensionBackendInitialized: [],
95
inspectedElement: [InspectedElementPayload],
@@ -133,8 +138,8 @@ type FrontendEvents = {|
138
stopInspectingNative: [boolean],
139
stopProfiling: [],
140
storeAsGlobal: [StoreAsGlobalParams],
136
- updateAppendComponentStack: [boolean],
141
updateComponentFilters: [Array<ComponentFilter>],
142
+ updateConsolePatchSettings: [UpdateConsolePatchSettingsParams],
143
viewAttributeSource: [ViewAttributeSourceParams],
144
viewElementSource: [ElementAndRendererID],
145
packages/react-devtools-shared/src/constants.js
+3
@@ -27,6 +27,9 @@ export const SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY =
27
export const SESSION_STORAGE_RELOAD_AND_PROFILE_KEY =
28
'React::DevTools::reloadAndProfile';
29
30
+export const LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS =
31
+ 'React::DevTools::breakOnConsoleErrors';
32
+
33
export const LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY =
34
'React::DevTools::appendComponentStack';
35
packages/react-devtools-shared/src/devtools/views/Settings/DebuggingSettings.js
new
+53
@@ -0,0 +1,53 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import * as React from 'react';
11
+import {useContext} from 'react';
12
+import {SettingsContext} from './SettingsContext';
13
+
14
+import styles from './SettingsShared.css';
15
+
16
+export default function DebuggingSettings(_: {||}) {
17
+ const {
18
+ appendComponentStack,
19
+ breakOnConsoleErrors,
20
+ setAppendComponentStack,
21
+ setBreakOnConsoleErrors,
22
+ } = useContext(SettingsContext);
23
+
24
+ return (
25
+ <div className={styles.Settings}>
26
+ <div className={styles.Setting}>
27
+ <label>
28
+ <input
29
+ type="checkbox"
30
+ checked={appendComponentStack}
31
+ onChange={({currentTarget}) =>
32
+ setAppendComponentStack(currentTarget.checked)
33
+ }
34
+ />{' '}
35
+ Append component stacks to console warnings and errors.
36
+ </label>
37
+ </div>
38
+
39
+ <div className={styles.Setting}>
40
+ <label>
41
+ <input
42
+ type="checkbox"
43
+ checked={breakOnConsoleErrors}
44
+ onChange={({currentTarget}) =>
45
+ setBreakOnConsoleErrors(currentTarget.checked)
46
+ }
47
+ />{' '}
48
+ Break on warnings
49
+ </label>
50
+ </div>
51
+ </div>
52
+ );
53
+}
packages/react-devtools-shared/src/devtools/views/Settings/GeneralSettings.js
-15
@@ -17,9 +17,7 @@ import styles from './SettingsShared.css';
17
18
export default function GeneralSettings(_: {||}) {
19
const {
20
- appendComponentStack,
20
displayDensity,
22
- setAppendComponentStack,
21
setDisplayDensity,
22
setTheme,
23
setTraceUpdatesEnabled,
@@ -71,19 +69,6 @@ export default function GeneralSettings(_: {||}) {
69
</div>
70
)}
71
74
- <div className={styles.Setting}>
75
- <label>
76
- <input
77
- type="checkbox"
78
- checked={appendComponentStack}
79
- onChange={({currentTarget}) =>
80
- setAppendComponentStack(currentTarget.checked)
81
- }
82
- />{' '}
83
- Append component stacks to console warnings and errors.
84
- </label>
85
- </div>
86
-
72
<div className={styles.ReleaseNotes}>
73
<a
74
className={styles.ReleaseNotesLink}
packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js
+20
-2
@@ -18,6 +18,7 @@ import {
18
import {
19
COMFORTABLE_LINE_HEIGHT,
20
COMPACT_LINE_HEIGHT,
21
+ LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
22
LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
23
LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY,
24
} from 'react-devtools-shared/src/constants';
@@ -40,6 +41,9 @@ type Context = {|
41
appendComponentStack: boolean,
42
setAppendComponentStack: (value: boolean) => void,
43
44
+ breakOnConsoleErrors: boolean,
45
+ setBreakOnConsoleErrors: (value: boolean) => void,
46
+
47
theme: Theme,
48
setTheme(value: Theme): void,
49
@@ -79,6 +83,13 @@ function SettingsContextController({
83
appendComponentStack,
84
setAppendComponentStack,
85
] = useLocalStorage<boolean>(LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY, true);
86
+ const [
87
+ breakOnConsoleErrors,
88
+ setBreakOnConsoleErrors,
89
+ ] = useLocalStorage<boolean>(
90
+ LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
91
+ false,
92
+ );
93
const [
94
traceUpdatesEnabled,
95
setTraceUpdatesEnabled,
@@ -133,8 +144,11 @@ function SettingsContextController({
144
}, [browserTheme, theme, documentElements]);
145
146
useEffect(() => {
136
- bridge.send('updateAppendComponentStack', appendComponentStack);
137
- }, [bridge, appendComponentStack]);
147
+ bridge.send('updateConsolePatchSettings', {
148
+ appendComponentStack,
149
+ breakOnConsoleErrors,
150
+ });
151
+ }, [bridge, appendComponentStack, breakOnConsoleErrors]);
152
153
useEffect(() => {
154
bridge.send('setTraceUpdatesEnabled', traceUpdatesEnabled);
@@ -143,12 +157,14 @@ function SettingsContextController({
157
const value = useMemo(
158
() => ({
159
appendComponentStack,
160
+ breakOnConsoleErrors,
161
displayDensity,
162
lineHeight:
163
displayDensity === 'compact'
164
? COMPACT_LINE_HEIGHT
165
: COMFORTABLE_LINE_HEIGHT,
166
setAppendComponentStack,
167
+ setBreakOnConsoleErrors,
168
setDisplayDensity,
169
setTheme,
170
setTraceUpdatesEnabled,
@@ -157,8 +173,10 @@ function SettingsContextController({
173
}),
174
[
175
appendComponentStack,
176
+ breakOnConsoleErrors,
177
displayDensity,
178
setAppendComponentStack,
179
+ setBreakOnConsoleErrors,
180
setDisplayDensity,
181
setTheme,
182
setTraceUpdatesEnabled,
packages/react-devtools-shared/src/devtools/views/Settings/SettingsModal.js
+12
-3
@@ -20,6 +20,7 @@ import {
20
useSubscription,
21
} from '../hooks';
22
import ComponentsSettings from './ComponentsSettings';
23
+import DebuggingSettings from './DebuggingSettings';
24
import GeneralSettings from './GeneralSettings';
25
import ProfilerSettings from './ProfilerSettings';
26
@@ -78,15 +79,18 @@ function SettingsModalImpl(_: {||}) {
79
80
let view = null;
81
switch (selectedTabID) {
82
+ case 'components':
83
+ view = <ComponentsSettings />;
84
+ break;
85
+ case 'debugging':
86
+ view = <DebuggingSettings />;
87
+ break;
88
case 'general':
89
view = <GeneralSettings />;
90
break;
91
case 'profiler':
92
view = <ProfilerSettings />;
93
break;
87
- case 'components':
88
- view = <ComponentsSettings />;
89
- break;
94
default:
95
break;
96
}
@@ -119,6 +123,11 @@ const tabs = [
123
icon: 'settings',
124
label: 'General',
125
},
126
+ {
127
+ id: 'debugging',
128
+ icon: 'bug',
129
+ label: 'Debugging',
130
+ },
131
{
132
id: 'components',
133
icon: 'components',
packages/react-devtools-shared/src/hook.js
+10
-2
@@ -174,6 +174,11 @@ export function installHook(target: any): DevToolsHook | null {
174
// Don't patch in test environments because we don't want to interfere with Jest's own console overrides.
175
if (process.env.NODE_ENV !== 'test') {
176
try {
177
+ const appendComponentStack =
178
+ window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ !== false;
179
+ const breakOnConsoleErrors =
180
+ window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ === true;
181
+
182
// The installHook() function is injected by being stringified in the browser,
183
// so imports outside of this function do not get included.
184
//
@@ -181,9 +186,12 @@ export function installHook(target: any): DevToolsHook | null {
186
// but Webpack wraps imports with an object (e.g. _backend_console__WEBPACK_IMPORTED_MODULE_0__)
187
// and the object itself will be undefined as well for the reasons mentioned above,
188
// so we use try/catch instead.
184
- if (window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ !== false) {
189
+ if (appendComponentStack || breakOnConsoleErrors) {
190
registerRendererWithConsole(renderer);
186
- patchConsole();
191
+ patchConsole({
192
+ appendComponentStack,
193
+ breakOnConsoleErrors,
194
+ });
195
}
196
} catch (error) {}
197
}
packages/react-devtools-shared/src/utils.js
+20
@@ -31,6 +31,7 @@ import {
31
import {ElementTypeRoot} from 'react-devtools-shared/src/types';
32
import {
33
LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
34
+ LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
35
LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
36
} from './constants';
37
import {ComponentFilterElementType, ElementTypeHostComponent} from './types';
@@ -248,6 +249,25 @@ export function setAppendComponentStack(value: boolean): void {
249
);
250
}
251
252
+export function getBreakOnConsoleErrors(): boolean {
253
+ try {
254
+ const raw = localStorageGetItem(
255
+ LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
256
+ );
257
+ if (raw != null) {
258
+ return JSON.parse(raw);
259
+ }
260
+ } catch (error) {}
261
+ return true;
262
+}
263
+
264
+export function setBreakOnConsoleErrors(value: boolean): void {
265
+ localStorageSetItem(
266
+ LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
267
+ JSON.stringify(value),
268
+ );
269
+}
270
+
271
export function separateDisplayNameAndHOCs(
272
displayName: string | null,
273
type: ElementType,
packages/react-devtools-shell/webpack.config.js
+11
@@ -1,5 +1,6 @@
1
const {resolve} = require('path');
2
const {DefinePlugin} = require('webpack');
3
+const TerserPlugin = require('terser-webpack-plugin');
4
const {
5
GITHUB_URL,
6
getVersionString,
@@ -39,6 +40,16 @@ const config = {
40
scheduler: resolve(builtModulesDir, 'scheduler'),
41
},
42
},
43
+ optimization: {
44
+ minimizer: [
45
+ new TerserPlugin({
46
+ terserOptions: {
47
+ compress: {drop_debugger: false},
48
+ output: {comments: true},
49
+ },
50
+ }),
51
+ ],
52
+ },
53
plugins: [
54
new DefinePlugin({
55
__DEV__,