From afba3fcd104eba54fc558eba22defc8b14798ed3 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Thu, 11 May 2017 13:57:15 -0400 Subject: [PATCH 01/21] Browser sort is not stable --- .../react-error-overlay/src/utils/unmapper.js | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/react-error-overlay/src/utils/unmapper.js b/packages/react-error-overlay/src/utils/unmapper.js index 415d18dc1a0..a251d20a691 100644 --- a/packages/react-error-overlay/src/utils/unmapper.js +++ b/packages/react-error-overlay/src/utils/unmapper.js @@ -4,6 +4,12 @@ import { getSourceMap } from './getSourceMap'; import { getLinesAround } from './getLinesAround'; import path from 'path'; +function count(search: string, string: string): number { + let count = -1, index = 0; + for (; index !== -1; count++, (index = string.indexOf(search, index + 1))); + return count; +} + /** * Turns a set of mapped StackFrames back into their generated code position and enhances them with code. * @param {string} fileUri The URI of the bundle.js file. @@ -39,28 +45,22 @@ async function unmap( return frame; } const fN: string = fileName; - const splitCache1: any = {}, splitCache2: any = {}, splitCache3: any = {}; const source = map .getSources() - .map(s => s.replace(/[\\]+/g, '/')) - .filter(s => { - s = path.normalize(s); - return s.indexOf(fN) === s.length - fN.length; - }) - .sort((a, b) => { - let a2 = splitCache1[a] || (splitCache1[a] = a.split(path.sep)), - b2 = splitCache1[b] || (splitCache1[b] = b.split(path.sep)); - return Math.sign(a2.length - b2.length); - }) - .sort((a, b) => { - let a2 = splitCache2[a] || (splitCache2[a] = a.split('node_modules')), - b2 = splitCache2[b] || (splitCache2[b] = b.split('node_modules')); - return Math.sign(a2.length - b2.length); + .map(s => path.normalize(s.replace(/[\\]+/g, '/'))) + .filter(p => { + const i = p.lastIndexOf(fN); + return i !== -1 && i === p.length - fN.length; }) + .map(p => ({ + token: p, + seps: count(path.sep, p), + penalties: count('node_modules', p) + count('~', p), + })) .sort((a, b) => { - let a2 = splitCache3[a] || (splitCache3[a] = a.split('~')), - b2 = splitCache3[b] || (splitCache3[b] = b.split('~')); - return Math.sign(a2.length - b2.length); + const s = Math.sign(a.seps - b.seps); + if (s !== 0) return s; + return Math.sign(a.penalties - b.penalties); }); if (source.length < 1 || lineNumber == null) { return new StackFrame( @@ -76,13 +76,14 @@ async function unmap( null ); } + const sourceT = source[0].token; const { line, column } = map.getGeneratedPosition( - source[0], + sourceT, lineNumber, // $FlowFixMe columnNumber ); - const originalSource = map.getSource(source[0]); + const originalSource = map.getSource(sourceT); return new StackFrame( functionName, fileUri, From 47cd783be74cb6e378186a1adcbd58e1834d56f3 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Thu, 11 May 2017 14:02:03 -0400 Subject: [PATCH 02/21] Fix ordering of final message --- packages/react-error-overlay/src/components/overlay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-error-overlay/src/components/overlay.js b/packages/react-error-overlay/src/components/overlay.js index 1a337d2f58a..3acd5b34c6f 100644 --- a/packages/react-error-overlay/src/components/overlay.js +++ b/packages/react-error-overlay/src/components/overlay.js @@ -52,7 +52,7 @@ function createOverlay( applyStyles(header, headerStyle); // Make message prettier - let finalMessage = message.match(/^\w*:/) ? name + ': ' + message : message; + let finalMessage = message.match(/^\w*:/) ? message : name + ': ' + message; finalMessage = finalMessage // TODO: maybe remove this prefix from fbjs? // It's just scaring people From cf897f0d02cba190e904acbef2d7f60d589cc6d1 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Thu, 11 May 2017 14:08:51 -0400 Subject: [PATCH 03/21] Register the warning capture --- packages/react-error-overlay/src/overlay.js | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/react-error-overlay/src/overlay.js b/packages/react-error-overlay/src/overlay.js index 63c919409c2..d65b1313bd2 100644 --- a/packages/react-error-overlay/src/overlay.js +++ b/packages/react-error-overlay/src/overlay.js @@ -19,6 +19,9 @@ import { register as registerStackTraceLimit, unregister as unregisterStackTraceLimit, } from './effects/stackTraceLimit'; +import { + permanentRegister as permanentRegisterConsole, +} from './effects/proxyConsole'; import { consume as consumeError, @@ -205,6 +208,26 @@ function inject() { registerPromise(window, error => crash(error, true)); registerShortcuts(window, shortcutHandler); registerStackTraceLimit(); + + permanentRegisterConsole('error', warning => { + const nIndex = warning.indexOf('\n'); + let message = warning; + if (nIndex !== -1) { + message = message.substring(0, nIndex); + } + const stack = warning.substring(nIndex + 1); + window.requestAnimationFrame(function() { + return crash( + // $FlowFixMe + { + message: message, + stack: stack, + __unmap_source: '/static/js/bundle.js', + }, + false + ); + }); + }); } function uninject() { From a150609f82721c0dcab602abc3fa5a14f40ce577 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Thu, 11 May 2017 14:50:24 -0400 Subject: [PATCH 04/21] Display only createElement warnings --- packages/react-error-overlay/src/overlay.js | 29 ++++++------- .../react-error-overlay/src/utils/warnings.js | 43 +++++++++++++++++++ 2 files changed, 55 insertions(+), 17 deletions(-) create mode 100644 packages/react-error-overlay/src/utils/warnings.js diff --git a/packages/react-error-overlay/src/overlay.js b/packages/react-error-overlay/src/overlay.js index d65b1313bd2..4c1d1f48144 100644 --- a/packages/react-error-overlay/src/overlay.js +++ b/packages/react-error-overlay/src/overlay.js @@ -22,6 +22,7 @@ import { import { permanentRegister as permanentRegisterConsole, } from './effects/proxyConsole'; +import { massage as massageWarning } from './utils/warnings'; import { consume as consumeError, @@ -210,23 +211,17 @@ function inject() { registerStackTraceLimit(); permanentRegisterConsole('error', warning => { - const nIndex = warning.indexOf('\n'); - let message = warning; - if (nIndex !== -1) { - message = message.substring(0, nIndex); - } - const stack = warning.substring(nIndex + 1); - window.requestAnimationFrame(function() { - return crash( - // $FlowFixMe - { - message: message, - stack: stack, - __unmap_source: '/static/js/bundle.js', - }, - false - ); - }); + const data = massageWarning(warning); + if (data == null) return; + crash( + // $FlowFixMe + { + message: data.message, + stack: data.stack, + __unmap_source: '/static/js/bundle.js', + }, + false + ); }); } diff --git a/packages/react-error-overlay/src/utils/warnings.js b/packages/react-error-overlay/src/utils/warnings.js new file mode 100644 index 00000000000..d64b3d34dd4 --- /dev/null +++ b/packages/react-error-overlay/src/utils/warnings.js @@ -0,0 +1,43 @@ +// @flow + +// This is a list of React warnings to display +// There must be zero or one capture group; and the capture group is assumed to be a missing stack frame. +const warnings = [ + /^.*React.createElement: type is invalid.+Check your code at (.*?:.*)[.]$/, +]; +// This is a list of information to remove from React warnings, it's not particularly useful to show +const removals = [/Check your code at (.*?:.*)[.]/]; + +function massage(warning: string): { message: string, stack: string } | null { + const nIndex = warning.indexOf('\n'); + let message = warning; + if (nIndex !== -1) { + message = message.substring(0, nIndex); + } + let stack = warning.substring(nIndex + 1); + + let found = false; + for (const warning of warnings) { + const m = message.match(warning); + if (!m) { + continue; + } + found = true; + if (!m[1]) { + break; + } + stack = `in render (at ${m[1]})\n${stack}`; + break; + } + if (!found) { + return null; + } + + for (const trim of removals) { + message = message.replace(trim, ''); + } + + return { message, stack }; +} + +export { massage }; From 8dfb007b47599530282562bea1ec74c906fac20f Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Thu, 11 May 2017 14:53:32 -0400 Subject: [PATCH 05/21] Use different method name --- packages/react-error-overlay/src/utils/warnings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-error-overlay/src/utils/warnings.js b/packages/react-error-overlay/src/utils/warnings.js index d64b3d34dd4..28d66e994b6 100644 --- a/packages/react-error-overlay/src/utils/warnings.js +++ b/packages/react-error-overlay/src/utils/warnings.js @@ -26,7 +26,7 @@ function massage(warning: string): { message: string, stack: string } | null { if (!m[1]) { break; } - stack = `in render (at ${m[1]})\n${stack}`; + stack = `in (render function) (at ${m[1]})\n${stack}`; break; } if (!found) { From 4bae90da63e7b5e5968a60898a0c57bbf4b6f1ad Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Thu, 11 May 2017 15:00:22 -0400 Subject: [PATCH 06/21] Fix regression --- packages/react-error-overlay/src/utils/unmapper.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react-error-overlay/src/utils/unmapper.js b/packages/react-error-overlay/src/utils/unmapper.js index a251d20a691..628383f71e4 100644 --- a/packages/react-error-overlay/src/utils/unmapper.js +++ b/packages/react-error-overlay/src/utils/unmapper.js @@ -47,14 +47,15 @@ async function unmap( const fN: string = fileName; const source = map .getSources() - .map(s => path.normalize(s.replace(/[\\]+/g, '/'))) + .map(s => s.replace(/[\\]+/g, '/')) .filter(p => { + p = path.normalize(p); const i = p.lastIndexOf(fN); return i !== -1 && i === p.length - fN.length; }) .map(p => ({ token: p, - seps: count(path.sep, p), + seps: count(path.sep, path.normalize(p)), penalties: count('node_modules', p) + count('~', p), })) .sort((a, b) => { From ab812623bbf91efefe09d8f73cb26b2cdfbb8ffb Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Thu, 11 May 2017 15:15:07 -0400 Subject: [PATCH 07/21] Ignore errors with only node_module files --- packages/react-error-overlay/src/overlay.js | 4 ++++ packages/react-error-overlay/src/utils/errorRegister.js | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/react-error-overlay/src/overlay.js b/packages/react-error-overlay/src/overlay.js index 4c1d1f48144..8edefef1c64 100644 --- a/packages/react-error-overlay/src/overlay.js +++ b/packages/react-error-overlay/src/overlay.js @@ -160,6 +160,10 @@ function crash(error: Error, unhandledRejection = false) { } consumeError(error, unhandledRejection, CONTEXT_SIZE) .then(ref => { + if (ref == null) { + console.warn('react-error-overlay ignored:', error); + return; + } errorReferences.push(ref); if (iframeReference !== null && additionalReference !== null) { updateAdditional( diff --git a/packages/react-error-overlay/src/utils/errorRegister.js b/packages/react-error-overlay/src/utils/errorRegister.js index f14ff9d923e..769d9f75507 100644 --- a/packages/react-error-overlay/src/utils/errorRegister.js +++ b/packages/react-error-overlay/src/utils/errorRegister.js @@ -19,7 +19,7 @@ function consume( error: Error, unhandledRejection: boolean = false, contextSize: number = 3 -): Promise { +): Promise { const parsedFrames = parse(error); let enhancedFramesPromise; if (error.__unmap_source) { @@ -33,6 +33,13 @@ function consume( enhancedFramesPromise = map(parsedFrames, contextSize); } return enhancedFramesPromise.then(enhancedFrames => { + if ( + enhancedFrames + .map(f => f._originalFileName) + .filter(f => f == null || f.indexOf('node_modules') === -1).length === 0 + ) { + return null; + } enhancedFrames = enhancedFrames.filter( ({ functionName }) => functionName == null || From 79af0fc56162179cd58fa227d3d1d018f4c40e37 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Thu, 11 May 2017 15:15:46 -0400 Subject: [PATCH 08/21] Ignore null files, too --- packages/react-error-overlay/src/utils/errorRegister.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-error-overlay/src/utils/errorRegister.js b/packages/react-error-overlay/src/utils/errorRegister.js index 769d9f75507..3fc1ab6b965 100644 --- a/packages/react-error-overlay/src/utils/errorRegister.js +++ b/packages/react-error-overlay/src/utils/errorRegister.js @@ -36,7 +36,7 @@ function consume( if ( enhancedFrames .map(f => f._originalFileName) - .filter(f => f == null || f.indexOf('node_modules') === -1).length === 0 + .filter(f => f != null && f.indexOf('node_modules') === -1).length === 0 ) { return null; } From ec12b8cf7e873c50f1d8896a62aad46d109cf177 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Fri, 12 May 2017 16:58:37 -0400 Subject: [PATCH 09/21] Revise count --- packages/react-error-overlay/src/utils/unmapper.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/react-error-overlay/src/utils/unmapper.js b/packages/react-error-overlay/src/utils/unmapper.js index 628383f71e4..bf6e5917dfc 100644 --- a/packages/react-error-overlay/src/utils/unmapper.js +++ b/packages/react-error-overlay/src/utils/unmapper.js @@ -5,8 +5,15 @@ import { getLinesAround } from './getLinesAround'; import path from 'path'; function count(search: string, string: string): number { - let count = -1, index = 0; - for (; index !== -1; count++, (index = string.indexOf(search, index + 1))); + // Count starts at -1 becuse a do-while loop always runs at least once + let count = -1, index = -1; + do { + // First call or the while case evaluated true, meaning we have to make + // count 0 or we found a character + ++count; + // Find the index of our search string, starting after the previous index + index = string.indexOf(search, index + 1); + } while (index !== -1); return count; } From fb7411b03cc7eaba835206bf84e3bc29a3d8b8c4 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Fri, 12 May 2017 16:58:43 -0400 Subject: [PATCH 10/21] Revise warning --- packages/react-error-overlay/src/overlay.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/react-error-overlay/src/overlay.js b/packages/react-error-overlay/src/overlay.js index 8edefef1c64..43d9e2d518a 100644 --- a/packages/react-error-overlay/src/overlay.js +++ b/packages/react-error-overlay/src/overlay.js @@ -161,7 +161,11 @@ function crash(error: Error, unhandledRejection = false) { consumeError(error, unhandledRejection, CONTEXT_SIZE) .then(ref => { if (ref == null) { - console.warn('react-error-overlay ignored:', error); + console.warn( + `react-error-overlay could not deduce if the previous error was from your code. Since we can't display anything useful, we're giving you this warning instead. +This error is most likely a bug with the package you see in the stack trace, however, it may be caused indirectly by something you are calling within the package. +Try searching or opening an issue with the relevant package.` + ); return; } errorReferences.push(ref); From cd9c43f06b37bb232df17c44f3412676e520cb07 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Fri, 12 May 2017 19:20:29 -0400 Subject: [PATCH 11/21] Update overlay.js --- packages/react-error-overlay/src/overlay.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/react-error-overlay/src/overlay.js b/packages/react-error-overlay/src/overlay.js index 43d9e2d518a..7c3ebb5363c 100644 --- a/packages/react-error-overlay/src/overlay.js +++ b/packages/react-error-overlay/src/overlay.js @@ -161,11 +161,6 @@ function crash(error: Error, unhandledRejection = false) { consumeError(error, unhandledRejection, CONTEXT_SIZE) .then(ref => { if (ref == null) { - console.warn( - `react-error-overlay could not deduce if the previous error was from your code. Since we can't display anything useful, we're giving you this warning instead. -This error is most likely a bug with the package you see in the stack trace, however, it may be caused indirectly by something you are calling within the package. -Try searching or opening an issue with the relevant package.` - ); return; } errorReferences.push(ref); From 83e63242511dd64a11b47a9fce4f7937cf4962f7 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Fri, 12 May 2017 21:23:26 -0400 Subject: [PATCH 12/21] Add support for https://github.com/facebook/react/pull/9679 --- .../src/effects/proxyConsole.js | 33 ++++++++++-- packages/react-error-overlay/src/overlay.js | 8 ++- .../react-error-overlay/src/utils/warnings.js | 52 +++++++++---------- 3 files changed, 60 insertions(+), 33 deletions(-) diff --git a/packages/react-error-overlay/src/effects/proxyConsole.js b/packages/react-error-overlay/src/effects/proxyConsole.js index a0a771f0e71..d66bb8fb063 100644 --- a/packages/react-error-overlay/src/effects/proxyConsole.js +++ b/packages/react-error-overlay/src/effects/proxyConsole.js @@ -1,5 +1,32 @@ /* @flow */ -type ConsoleProxyCallback = (message: string) => void; + +type ReactFrame = { + fileName: string | null, + lineNumber: number | null, + functionName: string | null, +}; +const ReactFrameStack: Array = []; + +export type { ReactFrame }; + +const registerReactStack = () => { + // $FlowFixMe + console.stack = frames => ReactFrameStack.push(frames); + // $FlowFixMe + console.stackEnd = frames => ReactFrameStack.pop(); +}; + +const unregisterReactStack = () => { + // $FlowFixMe + console.stack = undefined; + // $FlowFixMe + console.stackEnd = undefined; +}; + +type ConsoleProxyCallback = ( + message: string, + frames: ReactFrame[] | void +) => void; const permanentRegister = function proxyConsole( type: string, callback: ConsoleProxyCallback @@ -7,9 +34,9 @@ const permanentRegister = function proxyConsole( const orig = console[type]; console[type] = function __stack_frame_overlay_proxy_console__() { const message = [].slice.call(arguments).join(' '); - callback(message); + callback(message, ReactFrameStack[ReactFrameStack.length - 1]); return orig.apply(this, arguments); }; }; -export { permanentRegister }; +export { permanentRegister, registerReactStack, unregisterReactStack }; diff --git a/packages/react-error-overlay/src/overlay.js b/packages/react-error-overlay/src/overlay.js index 7c3ebb5363c..273c9ecb00f 100644 --- a/packages/react-error-overlay/src/overlay.js +++ b/packages/react-error-overlay/src/overlay.js @@ -21,6 +21,8 @@ import { } from './effects/stackTraceLimit'; import { permanentRegister as permanentRegisterConsole, + registerReactStack, + unregisterReactStack, } from './effects/proxyConsole'; import { massage as massageWarning } from './utils/warnings'; @@ -213,8 +215,9 @@ function inject() { registerShortcuts(window, shortcutHandler); registerStackTraceLimit(); - permanentRegisterConsole('error', warning => { - const data = massageWarning(warning); + registerReactStack(); + permanentRegisterConsole('error', (warning, stack) => { + const data = massageWarning(warning, stack); if (data == null) return; crash( // $FlowFixMe @@ -233,6 +236,7 @@ function uninject() { unregisterShortcuts(window); unregisterPromise(window); unregisterError(window); + unregisterReactStack(); } export { inject, uninject }; diff --git a/packages/react-error-overlay/src/utils/warnings.js b/packages/react-error-overlay/src/utils/warnings.js index 28d66e994b6..8a0a660c648 100644 --- a/packages/react-error-overlay/src/utils/warnings.js +++ b/packages/react-error-overlay/src/utils/warnings.js @@ -1,42 +1,38 @@ // @flow +import type { ReactFrame } from '../effects/proxyConsole'; -// This is a list of React warnings to display -// There must be zero or one capture group; and the capture group is assumed to be a missing stack frame. -const warnings = [ - /^.*React.createElement: type is invalid.+Check your code at (.*?:.*)[.]$/, -]; -// This is a list of information to remove from React warnings, it's not particularly useful to show +// This is a list of information to remove from React warnings, it's not particularly useful to show. const removals = [/Check your code at (.*?:.*)[.]/]; -function massage(warning: string): { message: string, stack: string } | null { - const nIndex = warning.indexOf('\n'); - let message = warning; - if (nIndex !== -1) { - message = message.substring(0, nIndex); - } - let stack = warning.substring(nIndex + 1); - - let found = false; - for (const warning of warnings) { - const m = message.match(warning); - if (!m) { - continue; - } - found = true; - if (!m[1]) { - break; - } - stack = `in (render function) (at ${m[1]})\n${stack}`; - break; - } - if (!found) { +function massage( + warning: string, + frames: ReactFrame[] | void +): { message: string, stack: string } | null { + if (!frames) { return null; } + let message = warning; + const nIndex = message.indexOf('\n'); + if (nIndex !== -1) message = message.substring(0, nIndex); + for (const trim of removals) { message = message.replace(trim, ''); } + let stack = ''; + for (let index = 0; index < frames.length; ++index) { + const { fileName, lineNumber } = frames[index]; + if (fileName == null || lineNumber == null) continue; + let { functionName } = frames[index]; + if (functionName == null && index === 0 && index + 1 < frames.length) { + functionName = frames[index + 1].functionName; + if (functionName !== null) functionName = `(${functionName})`; + } + functionName = functionName || '(unknown function)'; + + stack += `in ${functionName} (at ${fileName}:${lineNumber})\n`; + } return { message, stack }; } From ede4f9bf52ed20942edf56ecf0aee6101cb3f16a Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Fri, 12 May 2017 21:23:36 -0400 Subject: [PATCH 13/21] Use absolute paths --- packages/react-scripts/config/webpack.config.dev.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index e7ab84a2ee1..3d18991c584 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -77,7 +77,7 @@ module.exports = { publicPath: publicPath, // Point sourcemap entries to original disk location devtoolModuleFilenameTemplate: info => - path.relative(paths.appSrc, info.absoluteResourcePath), + path.resolve(info.absoluteResourcePath), }, resolve: { // This allows you to set a fallback for where Webpack should look for modules. From 8997f580bb2af94a4f83157f8444d1c6dd7fbc16 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Fri, 12 May 2017 21:23:59 -0400 Subject: [PATCH 14/21] Trim path if it's absolute --- .../src/components/frame.js | 7 +++++-- packages/react-scripts/template/src/App.js | 19 +++++++++++++++++-- .../react-scripts/template/src/fold/Bad.js | 6 ++++++ 3 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 packages/react-scripts/template/src/fold/Bad.js diff --git a/packages/react-error-overlay/src/components/frame.js b/packages/react-error-overlay/src/components/frame.js index 1b6a1d5d8b2..f4bcd4d173a 100644 --- a/packages/react-error-overlay/src/components/frame.js +++ b/packages/react-error-overlay/src/components/frame.js @@ -127,13 +127,12 @@ function createFrame( lastElement: boolean ) { const { compiled } = frameSetting; - let { functionName } = frame; + let { functionName, _originalFileName: sourceFileName } = frame; const { fileName, lineNumber, columnNumber, _scriptCode: scriptLines, - _originalFileName: sourceFileName, _originalLineNumber: sourceLineNumber, _originalColumnNumber: sourceColumnNumber, _originalScriptCode: sourceLines, @@ -149,6 +148,10 @@ function createFrame( let url; if (!compiled && sourceFileName && sourceLineNumber) { + // Remove everything up to the first /src/ + const trimMatch = /.*?[/|\\](src[/|\\].*)/.exec(sourceFileName); + if (trimMatch && trimMatch[1]) sourceFileName = trimMatch[1]; + url = sourceFileName + ':' + sourceLineNumber; if (sourceColumnNumber) { url += ':' + sourceColumnNumber; diff --git a/packages/react-scripts/template/src/App.js b/packages/react-scripts/template/src/App.js index d7d52a7f38a..c2f914571c6 100644 --- a/packages/react-scripts/template/src/App.js +++ b/packages/react-scripts/template/src/App.js @@ -2,13 +2,28 @@ import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; +import Bad from './fold/Bad'; + class App extends Component { render() { return (
- logo -

Welcome to React

+
+
+
+
+
+
+ logo + Welcome to React + {[1, 2, 3].map(s => {s})} +
{' '} +
{' '} +
{' '} +
{' '} +
{' '} +

To get started, edit src/App.js and save to reload. diff --git a/packages/react-scripts/template/src/fold/Bad.js b/packages/react-scripts/template/src/fold/Bad.js new file mode 100644 index 00000000000..c27d878fd6d --- /dev/null +++ b/packages/react-scripts/template/src/fold/Bad.js @@ -0,0 +1,6 @@ +import React from 'react'; + +let Lol = null; +export default function Bad() { + return

; +} From 19eadc2075c8dcd9a60b6b29f134ad4c4d7f4075 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Fri, 12 May 2017 21:26:55 -0400 Subject: [PATCH 15/21] Make sure it's an absolute path --- packages/react-error-overlay/src/components/frame.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-error-overlay/src/components/frame.js b/packages/react-error-overlay/src/components/frame.js index f4bcd4d173a..f42ff3d4d15 100644 --- a/packages/react-error-overlay/src/components/frame.js +++ b/packages/react-error-overlay/src/components/frame.js @@ -149,7 +149,7 @@ function createFrame( let url; if (!compiled && sourceFileName && sourceLineNumber) { // Remove everything up to the first /src/ - const trimMatch = /.*?[/|\\](src[/|\\].*)/.exec(sourceFileName); + const trimMatch = /^[/|\\].*?[/|\\](src[/|\\].*)/.exec(sourceFileName); if (trimMatch && trimMatch[1]) sourceFileName = trimMatch[1]; url = sourceFileName + ':' + sourceLineNumber; From 43a97e3e0d070f94f1a368a69063707032304c4f Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Fri, 12 May 2017 21:30:32 -0400 Subject: [PATCH 16/21] Oops --- packages/react-scripts/template/src/App.js | 19 ++----------------- .../react-scripts/template/src/fold/Bad.js | 6 ------ 2 files changed, 2 insertions(+), 23 deletions(-) delete mode 100644 packages/react-scripts/template/src/fold/Bad.js diff --git a/packages/react-scripts/template/src/App.js b/packages/react-scripts/template/src/App.js index c2f914571c6..d7d52a7f38a 100644 --- a/packages/react-scripts/template/src/App.js +++ b/packages/react-scripts/template/src/App.js @@ -2,28 +2,13 @@ import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; -import Bad from './fold/Bad'; - class App extends Component { render() { return (
-
-
-
-
-
-
- logo - Welcome to React - {[1, 2, 3].map(s => {s})} -
{' '} -
{' '} -
{' '} -
{' '} -
{' '} -
+ logo +

Welcome to React

To get started, edit src/App.js and save to reload. diff --git a/packages/react-scripts/template/src/fold/Bad.js b/packages/react-scripts/template/src/fold/Bad.js deleted file mode 100644 index c27d878fd6d..00000000000 --- a/packages/react-scripts/template/src/fold/Bad.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; - -let Lol = null; -export default function Bad() { - return

; -} From 52a39a4e93de0e746df7efe59c80fe39c35dc98f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sat, 13 May 2017 12:39:23 +0100 Subject: [PATCH 17/21] Tweak for new behavior --- .../react-error-overlay/src/utils/warnings.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/react-error-overlay/src/utils/warnings.js b/packages/react-error-overlay/src/utils/warnings.js index 8a0a660c648..8858c3798e5 100644 --- a/packages/react-error-overlay/src/utils/warnings.js +++ b/packages/react-error-overlay/src/utils/warnings.js @@ -14,7 +14,9 @@ function massage( let message = warning; const nIndex = message.indexOf('\n'); - if (nIndex !== -1) message = message.substring(0, nIndex); + if (nIndex !== -1) { + message = message.substring(0, nIndex); + } for (const trim of removals) { message = message.replace(trim, ''); @@ -23,14 +25,11 @@ function massage( let stack = ''; for (let index = 0; index < frames.length; ++index) { const { fileName, lineNumber } = frames[index]; - if (fileName == null || lineNumber == null) continue; - let { functionName } = frames[index]; - if (functionName == null && index === 0 && index + 1 < frames.length) { - functionName = frames[index + 1].functionName; - if (functionName !== null) functionName = `(${functionName})`; + if (fileName == null || lineNumber == null) { + continue; } - functionName = functionName || '(unknown function)'; - + let { functionName } = frames[index]; + functionName = functionName || '(anonymous function)'; stack += `in ${functionName} (at ${fileName}:${lineNumber})\n`; } return { message, stack }; From 4d9f647298346169f9ce482ae6b53c773a967c9d Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sat, 13 May 2017 12:52:54 +0100 Subject: [PATCH 18/21] Make it safer --- .../src/effects/proxyConsole.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/react-error-overlay/src/effects/proxyConsole.js b/packages/react-error-overlay/src/effects/proxyConsole.js index d66bb8fb063..7541dd74c7c 100644 --- a/packages/react-error-overlay/src/effects/proxyConsole.js +++ b/packages/react-error-overlay/src/effects/proxyConsole.js @@ -5,15 +5,15 @@ type ReactFrame = { lineNumber: number | null, functionName: string | null, }; -const ReactFrameStack: Array = []; +const reactFrameStack: Array = []; export type { ReactFrame }; const registerReactStack = () => { // $FlowFixMe - console.stack = frames => ReactFrameStack.push(frames); + console.stack = frames => reactFrameStack.push(frames); // $FlowFixMe - console.stackEnd = frames => ReactFrameStack.pop(); + console.stackEnd = frames => reactFrameStack.pop(); }; const unregisterReactStack = () => { @@ -33,8 +33,17 @@ const permanentRegister = function proxyConsole( ) { const orig = console[type]; console[type] = function __stack_frame_overlay_proxy_console__() { - const message = [].slice.call(arguments).join(' '); - callback(message, ReactFrameStack[ReactFrameStack.length - 1]); + try { + const message = arguments[0]; + if (typeof message === 'string' && reactFrameStack.length > 0) { + callback(message, reactFrameStack[reactFrameStack.length - 1]); + } + } catch (err) { + // Warnings must never crash. Rethrow with a clean stack. + setTimeout(function() { + throw err; + }); + } return orig.apply(this, arguments); }; }; From d7f81e7e22fcaf279cf531caa9b41e0b5a7b2605 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sat, 13 May 2017 13:20:50 +0100 Subject: [PATCH 19/21] More resilient warnings --- .../src/components/frame.js | 4 ++- .../src/components/overlay.js | 7 ++++-- packages/react-error-overlay/src/overlay.js | 3 +-- .../react-error-overlay/src/utils/warnings.js | 25 +++++++------------ 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/packages/react-error-overlay/src/components/frame.js b/packages/react-error-overlay/src/components/frame.js index f42ff3d4d15..db9812cc30c 100644 --- a/packages/react-error-overlay/src/components/frame.js +++ b/packages/react-error-overlay/src/components/frame.js @@ -150,7 +150,9 @@ function createFrame( if (!compiled && sourceFileName && sourceLineNumber) { // Remove everything up to the first /src/ const trimMatch = /^[/|\\].*?[/|\\](src[/|\\].*)/.exec(sourceFileName); - if (trimMatch && trimMatch[1]) sourceFileName = trimMatch[1]; + if (trimMatch && trimMatch[1]) { + sourceFileName = trimMatch[1]; + } url = sourceFileName + ':' + sourceLineNumber; if (sourceColumnNumber) { diff --git a/packages/react-error-overlay/src/components/overlay.js b/packages/react-error-overlay/src/components/overlay.js index 3acd5b34c6f..85a0c814e90 100644 --- a/packages/react-error-overlay/src/components/overlay.js +++ b/packages/react-error-overlay/src/components/overlay.js @@ -12,7 +12,7 @@ import type { SwitchCallback } from './additional'; function createOverlay( document: Document, - name: string, + name: ?string, message: string, frames: StackFrame[], contextSize: number, @@ -52,7 +52,10 @@ function createOverlay( applyStyles(header, headerStyle); // Make message prettier - let finalMessage = message.match(/^\w*:/) ? message : name + ': ' + message; + let finalMessage = message.match(/^\w*:/) || !name + ? message + : name + ': ' + message; + finalMessage = finalMessage // TODO: maybe remove this prefix from fbjs? // It's just scaring people diff --git a/packages/react-error-overlay/src/overlay.js b/packages/react-error-overlay/src/overlay.js index 273c9ecb00f..fe29a6c7b36 100644 --- a/packages/react-error-overlay/src/overlay.js +++ b/packages/react-error-overlay/src/overlay.js @@ -72,7 +72,7 @@ const css = [ '}', ].join('\n'); -function render(name: string, message: string, resolvedFrames: StackFrame[]) { +function render(name: ?string, message: string, resolvedFrames: StackFrame[]) { disposeCurrentView(); const iframe = window.document.createElement('iframe'); @@ -218,7 +218,6 @@ function inject() { registerReactStack(); permanentRegisterConsole('error', (warning, stack) => { const data = massageWarning(warning, stack); - if (data == null) return; crash( // $FlowFixMe { diff --git a/packages/react-error-overlay/src/utils/warnings.js b/packages/react-error-overlay/src/utils/warnings.js index 8858c3798e5..ce9fcd3c0d0 100644 --- a/packages/react-error-overlay/src/utils/warnings.js +++ b/packages/react-error-overlay/src/utils/warnings.js @@ -1,25 +1,17 @@ // @flow import type { ReactFrame } from '../effects/proxyConsole'; -// This is a list of information to remove from React warnings, it's not particularly useful to show. -const removals = [/Check your code at (.*?:.*)[.]/]; +function stripInlineStacktrace(message: string): string { + return message.split('\n').filter(line => !line.match(/^\s*in/)).join('\n'); // " in Foo" +} function massage( warning: string, - frames: ReactFrame[] | void -): { message: string, stack: string } | null { - if (!frames) { - return null; - } - - let message = warning; - const nIndex = message.indexOf('\n'); - if (nIndex !== -1) { - message = message.substring(0, nIndex); - } - - for (const trim of removals) { - message = message.replace(trim, ''); + frames: ReactFrame[] +): { message: string, stack: string } { + let message = stripInlineStacktrace(warning); + if (message.indexOf('Warning: ') === 0) { + message = message.substring('Warning: '.length); } let stack = ''; @@ -32,6 +24,7 @@ function massage( functionName = functionName || '(anonymous function)'; stack += `in ${functionName} (at ${fileName}:${lineNumber})\n`; } + return { message, stack }; } From 4cb6be38572d4250f9498c4a1d156eb5875a4c15 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sat, 13 May 2017 13:27:26 +0100 Subject: [PATCH 20/21] Prettier output --- packages/react-error-overlay/src/components/overlay.js | 7 +++++-- packages/react-error-overlay/src/utils/warnings.js | 4 +--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/react-error-overlay/src/components/overlay.js b/packages/react-error-overlay/src/components/overlay.js index 85a0c814e90..e812bb44154 100644 --- a/packages/react-error-overlay/src/components/overlay.js +++ b/packages/react-error-overlay/src/components/overlay.js @@ -59,10 +59,13 @@ function createOverlay( finalMessage = finalMessage // TODO: maybe remove this prefix from fbjs? // It's just scaring people - .replace('Invariant Violation: ', '') + .replace(/^Invariant Violation:\s*/, '') + // This is not helpful either: + .replace(/^Warning:\s*/, '') // Break the actionable part to the next line. // AFAIK React 16+ should already do this. - .replace(' Check the render method', '\n\nCheck the render method'); + .replace(' Check the render method', '\n\nCheck the render method') + .replace(' Check your code at', '\n\nCheck your code at'); // Put it in the DOM header.appendChild(document.createTextNode(finalMessage)); diff --git a/packages/react-error-overlay/src/utils/warnings.js b/packages/react-error-overlay/src/utils/warnings.js index ce9fcd3c0d0..b2fc34bb473 100644 --- a/packages/react-error-overlay/src/utils/warnings.js +++ b/packages/react-error-overlay/src/utils/warnings.js @@ -10,10 +10,8 @@ function massage( frames: ReactFrame[] ): { message: string, stack: string } { let message = stripInlineStacktrace(warning); - if (message.indexOf('Warning: ') === 0) { - message = message.substring('Warning: '.length); - } + // Reassemble the stack with full filenames provided by React let stack = ''; for (let index = 0; index < frames.length; ++index) { const { fileName, lineNumber } = frames[index]; From 9da83b304b38d2d06a46878bee639b39b057a89f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sat, 13 May 2017 13:32:34 +0100 Subject: [PATCH 21/21] Fix flow --- packages/react-error-overlay/src/effects/proxyConsole.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/react-error-overlay/src/effects/proxyConsole.js b/packages/react-error-overlay/src/effects/proxyConsole.js index 7541dd74c7c..062055e0bc2 100644 --- a/packages/react-error-overlay/src/effects/proxyConsole.js +++ b/packages/react-error-overlay/src/effects/proxyConsole.js @@ -23,10 +23,7 @@ const unregisterReactStack = () => { console.stackEnd = undefined; }; -type ConsoleProxyCallback = ( - message: string, - frames: ReactFrame[] | void -) => void; +type ConsoleProxyCallback = (message: string, frames: ReactFrame[]) => void; const permanentRegister = function proxyConsole( type: string, callback: ConsoleProxyCallback