Skip to content

Commit 197bfd8

Browse files
committed
Initial strict mode implementation
1 parent c0bc544 commit 197bfd8

24 files changed

+238
-98
lines changed

packages/react-devtools-core/src/standalone.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
getBreakOnConsoleErrors,
2222
getSavedComponentFilters,
2323
getShowInlineWarningsAndErrors,
24+
getHideDoubleLogsInStrictLegacy,
2425
} from 'react-devtools-shared/src/utils';
2526
import {Server} from 'ws';
2627
import {join} from 'path';
@@ -310,6 +311,9 @@ function startServer(
310311
)};
311312
window.__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__ = ${JSON.stringify(
312313
getShowInlineWarningsAndErrors(),
314+
)};
315+
window.__REACT_DEVTOOLS_HIDE_DOUBLE_LOGS_IN_STRICT_LEGACY__ = ${JSON.stringify(
316+
getHideDoubleLogsInStrictLegacy(),
313317
)};`;
314318

315319
response.end(

packages/react-devtools-extensions/src/main.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
getBreakOnConsoleErrors,
1212
getSavedComponentFilters,
1313
getShowInlineWarningsAndErrors,
14+
getHideDoubleLogsInStrictLegacy,
1415
} from 'react-devtools-shared/src/utils';
1516
import {parseHookNames, purgeCachedMetadata} from './parseHookNames';
1617
import {
@@ -43,6 +44,9 @@ function syncSavedPreferences() {
4344
)};
4445
window.__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__ = ${JSON.stringify(
4546
getShowInlineWarningsAndErrors(),
47+
)};
48+
window.__REACT_DEVTOOLS_HIDE_DOUBLE_LOGS_IN_STRICT_LEGACY__ = ${JSON.stringify(
49+
getHideDoubleLogsInStrictLegacy(),
4650
)};`,
4751
);
4852
}

packages/react-devtools-inline/src/backend.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ function startActivation(contentWindow: window) {
2525
breakOnConsoleErrors,
2626
componentFilters,
2727
showInlineWarningsAndErrors,
28+
hideDoubleLogsInStrictLegacy,
2829
} = data;
2930

3031
contentWindow.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = appendComponentStack;
3132
contentWindow.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ = breakOnConsoleErrors;
3233
contentWindow.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;
3334
contentWindow.__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__ = showInlineWarningsAndErrors;
35+
contentWindow.__REACT_DEVTOOLS_HIDE_DOUBLE_LOGS_IN_STRICT_LEGACY__ = hideDoubleLogsInStrictLegacy;
3436

3537
// TRICKY
3638
// The backend entry point may be required in the context of an iframe or the parent window.
@@ -43,6 +45,7 @@ function startActivation(contentWindow: window) {
4345
window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ = breakOnConsoleErrors;
4446
window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;
4547
window.__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__ = showInlineWarningsAndErrors;
48+
window.__REACT_DEVTOOLS_HIDE_DOUBLE_LOGS_IN_STRICT_LEGACY__ = hideDoubleLogsInStrictLegacy;
4649
}
4750

4851
finishActivation(contentWindow);

packages/react-devtools-inline/src/frontend.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
getBreakOnConsoleErrors,
1111
getSavedComponentFilters,
1212
getShowInlineWarningsAndErrors,
13+
getHideDoubleLogsInStrictLegacy,
1314
} from 'react-devtools-shared/src/utils';
1415
import {
1516
MESSAGE_TYPE_GET_SAVED_PREFERENCES,
@@ -83,6 +84,7 @@ export function initialize(
8384
breakOnConsoleErrors: getBreakOnConsoleErrors(),
8485
componentFilters: getSavedComponentFilters(),
8586
showInlineWarningsAndErrors: getShowInlineWarningsAndErrors(),
87+
hideDoubleLogsInStrictLegacy: getHideDoubleLogsInStrictLegacy(),
8688
},
8789
'*',
8890
);

packages/react-devtools-shared/src/backend/agent.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,10 +635,13 @@ export default class Agent extends EventEmitter<{|
635635
appendComponentStack,
636636
breakOnConsoleErrors,
637637
showInlineWarningsAndErrors,
638+
hideDoubleLogsInStrictLegacy,
639+
__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__,
638640
}: {|
639641
appendComponentStack: boolean,
640642
breakOnConsoleErrors: boolean,
641643
showInlineWarningsAndErrors: boolean,
644+
hideDoubleLogsInStrictLegacy: boolean,
642645
|}) => {
643646
// If the frontend preference has change,
644647
// or in the case of React Native- if the backend is just finding out the preference-
@@ -647,12 +650,14 @@ export default class Agent extends EventEmitter<{|
647650
if (
648651
appendComponentStack ||
649652
breakOnConsoleErrors ||
650-
showInlineWarningsAndErrors
653+
showInlineWarningsAndErrors ||
654+
hideDoubleLogsInStrictLegacy
651655
) {
652656
patchConsole({
653657
appendComponentStack,
654658
breakOnConsoleErrors,
655659
showInlineWarningsAndErrors,
660+
hideDoubleLogsInStrictLegacy,
656661
});
657662
} else {
658663
unpatchConsole();

packages/react-devtools-shared/src/backend/console.js

Lines changed: 68 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99

1010
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
1111
import type {CurrentDispatcherRef, ReactRenderer, WorkTagMap} from './types';
12+
import {format} from './utils';
1213

1314
import {getInternalReactConstants} from './renderer';
1415
import {getStackByFiberInDevAndProd} from './DevToolsFiberComponentStack';
1516

16-
const APPEND_STACK_TO_METHODS = ['error', 'trace', 'warn'];
17+
const APPEND_STACK_TO_METHODS = ['error', 'trace', 'warn', 'log'];
1718

1819
// React's custom built component stack strings match "\s{4}in"
1920
// Chrome's prefix matches "\s{4}at"
@@ -39,6 +40,7 @@ const injectedRenderers: Map<
3940
getCurrentFiber: () => Fiber | null,
4041
onErrorOrWarning: ?OnErrorOrWarning,
4142
workTagMap: WorkTagMap,
43+
getIsStrictModeLegacy: () => boolean,
4244
|},
4345
> = new Map();
4446

@@ -72,6 +74,7 @@ export function registerRenderer(
7274
const {
7375
currentDispatcherRef,
7476
getCurrentFiber,
77+
getIsStrictModeLegacy,
7578
findFiberByHostInstance,
7679
version,
7780
} = renderer;
@@ -89,6 +92,7 @@ export function registerRenderer(
8992
injectedRenderers.set(renderer, {
9093
currentDispatcherRef,
9194
getCurrentFiber,
95+
getIsStrictModeLegacy,
9296
workTagMap: ReactTypeOfWork,
9397
onErrorOrWarning,
9498
});
@@ -99,6 +103,7 @@ const consoleSettingsRef = {
99103
appendComponentStack: false,
100104
breakOnConsoleErrors: false,
101105
showInlineWarningsAndErrors: false,
106+
hideDoubleLogsInStrictLegacy: false,
102107
};
103108

104109
// Patches console methods to append component stack for the current fiber.
@@ -107,16 +112,19 @@ export function patch({
107112
appendComponentStack,
108113
breakOnConsoleErrors,
109114
showInlineWarningsAndErrors,
115+
hideDoubleLogsInStrictLegacy,
110116
}: {
111117
appendComponentStack: boolean,
112118
breakOnConsoleErrors: boolean,
113119
showInlineWarningsAndErrors: boolean,
120+
hideDoubleLogsInStrictLegacy: boolean,
114121
}): void {
115122
// Settings may change after we've patched the console.
116123
// Using a shared ref allows the patch function to read the latest values.
117124
consoleSettingsRef.appendComponentStack = appendComponentStack;
118125
consoleSettingsRef.breakOnConsoleErrors = breakOnConsoleErrors;
119126
consoleSettingsRef.showInlineWarningsAndErrors = showInlineWarningsAndErrors;
127+
consoleSettingsRef.hideDoubleLogsInStrictLegacy = hideDoubleLogsInStrictLegacy;
120128

121129
if (unpatchFn !== null) {
122130
// Don't patch twice.
@@ -141,61 +149,68 @@ export function patch({
141149

142150
const overrideMethod = (...args) => {
143151
let shouldAppendWarningStack = false;
144-
if (consoleSettingsRef.appendComponentStack) {
145-
const lastArg = args.length > 0 ? args[args.length - 1] : null;
146-
const alreadyHasComponentStack =
147-
typeof lastArg === 'string' && isStringComponentStack(lastArg);
148-
149-
// If we are ever called with a string that already has a component stack,
150-
// e.g. a React error/warning, don't append a second stack.
151-
shouldAppendWarningStack = !alreadyHasComponentStack;
152+
if (method === 'error' || method === 'warn' || method === 'trace') {
153+
if (consoleSettingsRef.appendComponentStack) {
154+
const lastArg = args.length > 0 ? args[args.length - 1] : null;
155+
const alreadyHasComponentStack =
156+
typeof lastArg === 'string' && isStringComponentStack(lastArg);
157+
158+
// If we are ever called with a string that already has a component stack,
159+
// e.g. a React error/warning, don't append a second stack.
160+
shouldAppendWarningStack = !alreadyHasComponentStack;
161+
}
152162
}
153163

154164
const shouldShowInlineWarningsAndErrors =
155165
consoleSettingsRef.showInlineWarningsAndErrors &&
156166
(method === 'error' || method === 'warn');
157167

158-
if (shouldAppendWarningStack || shouldShowInlineWarningsAndErrors) {
159-
// Search for the first renderer that has a current Fiber.
160-
// We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?)
161-
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
162-
for (const {
163-
currentDispatcherRef,
164-
getCurrentFiber,
165-
onErrorOrWarning,
166-
workTagMap,
167-
} of injectedRenderers.values()) {
168-
const current: ?Fiber = getCurrentFiber();
169-
if (current != null) {
170-
try {
171-
if (shouldShowInlineWarningsAndErrors) {
172-
// patch() is called by two places: (1) the hook and (2) the renderer backend.
173-
// The backend is what implements a message queue, so it's the only one that injects onErrorOrWarning.
174-
if (typeof onErrorOrWarning === 'function') {
175-
onErrorOrWarning(
176-
current,
177-
((method: any): 'error' | 'warn'),
178-
// Copy args before we mutate them (e.g. adding the component stack)
179-
args.slice(),
180-
);
181-
}
182-
}
168+
let isInStrictModeLegacy = false;
169+
170+
// Search for the first renderer that has a current Fiber.
171+
// We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?)
172+
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
173+
for (const {
174+
currentDispatcherRef,
175+
getCurrentFiber,
176+
onErrorOrWarning,
177+
workTagMap,
178+
getIsStrictModeLegacy,
179+
} of injectedRenderers.values()) {
180+
const current: ?Fiber = getCurrentFiber();
181+
if (current != null) {
182+
try {
183+
if (getIsStrictModeLegacy()) {
184+
isInStrictModeLegacy = true;
185+
}
183186

184-
if (shouldAppendWarningStack) {
185-
const componentStack = getStackByFiberInDevAndProd(
186-
workTagMap,
187+
if (shouldShowInlineWarningsAndErrors) {
188+
// patch() is called by two places: (1) the hook and (2) the renderer backend.
189+
// The backend is what impliments a message queue, so it's the only one that injects onErrorOrWarning.
190+
if (typeof onErrorOrWarning === 'function') {
191+
onErrorOrWarning(
187192
current,
188-
currentDispatcherRef,
193+
((method: any): 'error' | 'warn'),
194+
// Copy args before we mutate them (e.g. adding the component stack)
195+
args.slice(),
189196
);
190-
if (componentStack !== '') {
191-
args.push(componentStack);
192-
}
193197
}
194-
} catch (error) {
195-
// Don't let a DevTools or React internal error interfere with logging.
196-
} finally {
197-
break;
198198
}
199+
200+
if (shouldAppendWarningStack) {
201+
const componentStack = getStackByFiberInDevAndProd(
202+
workTagMap,
203+
current,
204+
currentDispatcherRef,
205+
);
206+
if (componentStack !== '') {
207+
args.push(componentStack);
208+
}
209+
}
210+
} catch (error) {
211+
// Don't let a DevTools or React internal error interfere with logging.
212+
} finally {
213+
break;
199214
}
200215
}
201216
}
@@ -209,7 +224,14 @@ export function patch({
209224
debugger;
210225
}
211226

212-
originalMethod(...args);
227+
if (isInStrictModeLegacy) {
228+
if (!consoleSettingsRef.hideDoubleLogsInStrictLegacy) {
229+
// TODO(luna) pick a better color than gray
230+
originalMethod(`%c${format(...args)}`, 'color: gray');
231+
}
232+
} else {
233+
originalMethod(...args);
234+
}
213235
};
214236

215237
overrideMethod.__REACT_DEVTOOLS_ORIGINAL_METHOD__ = originalMethod;

packages/react-devtools-shared/src/backend/renderer.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -740,15 +740,19 @@ export function attach(
740740
window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ === true;
741741
const showInlineWarningsAndErrors =
742742
window.__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__ !== false;
743+
const hideDoubleLogsInStrictLegacy =
744+
window.__REACT_DEVTOOLS_HIDE_DOUBLE_LOGS_IN_STRICT_LEGACY__ === true;
743745
if (
744746
appendComponentStack ||
745747
breakOnConsoleErrors ||
746-
showInlineWarningsAndErrors
748+
showInlineWarningsAndErrors ||
749+
hideDoubleLogsInStrictLegacy
747750
) {
748751
patchConsole({
749752
appendComponentStack,
750753
breakOnConsoleErrors,
751754
showInlineWarningsAndErrors,
755+
hideDoubleLogsInStrictLegacy,
752756
});
753757
}
754758
}

packages/react-devtools-shared/src/backend/types.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ export type ReactRenderer = {
136136
// Only injected by React v16.9+ in DEV mode.
137137
// Enables DevTools to append owners-only component stack to error messages.
138138
getCurrentFiber?: () => Fiber | null,
139+
140+
getIsStrictModeLegacy?: () => boolean,
139141
// 17.0.2+
140142
reconcilerVersion?: string,
141143
// Uniquely identifies React DOM v15.

packages/react-devtools-shared/src/bridge.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ type UpdateConsolePatchSettingsParams = {|
164164
appendComponentStack: boolean,
165165
breakOnConsoleErrors: boolean,
166166
showInlineWarningsAndErrors: boolean,
167+
hideDoubleLogsInStrictLegacy: boolean,
167168
|};
168169

169170
export type BackendEvents = {|

packages/react-devtools-shared/src/constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ export const LOCAL_STORAGE_SHOW_INLINE_WARNINGS_AND_ERRORS_KEY =
4444
export const LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY =
4545
'React::DevTools::traceUpdatesEnabled';
4646

47+
export const LOCAL_STORAGE_HIDE_DOUBLE_LOGS_IN_STRICT_LEGACY =
48+
'React::DevTools::hideDoubleLogsInStrictLegacy';
49+
4750
export const PROFILER_EXPORT_VERSION = 5;
4851

4952
export const CHANGE_LOG_URL =

packages/react-devtools-shared/src/devtools/views/Settings/DebuggingSettings.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ export default function DebuggingSettings(_: {||}) {
1717
const {
1818
appendComponentStack,
1919
breakOnConsoleErrors,
20+
hideDoubleLogsInStrictLegacy,
2021
setAppendComponentStack,
2122
setBreakOnConsoleErrors,
2223
setShowInlineWarningsAndErrors,
2324
showInlineWarningsAndErrors,
25+
setHideDoubleLogsInStrictLegacy,
2426
} = useContext(SettingsContext);
2527

2628
return (
@@ -64,6 +66,19 @@ export default function DebuggingSettings(_: {||}) {
6466
</label>
6567
</div>
6668

69+
<div className={styles.Setting}>
70+
<label>
71+
<input
72+
type="checkbox"
73+
checked={hideDoubleLogsInStrictLegacy}
74+
onChange={({currentTarget}) =>
75+
setHideDoubleLogsInStrictLegacy(currentTarget.checked)
76+
}
77+
/>{' '}
78+
Hide double logs in strict mdoe
79+
</label>
80+
</div>
81+
6782
<div className={styles.ConsoleAPIWarning}>
6883
These settings require DevTools to override native console APIs.
6984
</div>

0 commit comments

Comments
 (0)