@samitouri / QOS-React-2 / commits / 2efe63d99c

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__,