From e165ff9f0ac2239bcb28c96c9d47fc22288d94f6 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 12:11:08 -0400 Subject: [PATCH 01/18] =?UTF-8?q?=E0=BC=BC=20=E3=81=A4=20=E2=97=95=5F?= =?UTF-8?q?=E2=97=95=20=E0=BC=BD=E3=81=A4=20stack-frame-overlay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 9 +- packages/react-dev-utils/crashOverlay.js | 1165 ----------------- .../config/webpack.config.dev.js | 2 +- packages/react-scripts/package.json | 1 + packages/stack-frame-overlay/.babelrc | 3 + packages/stack-frame-overlay/.eslintrc | 3 + packages/stack-frame-overlay/.flowconfig | 8 + packages/stack-frame-overlay/.gitignore | 2 + packages/stack-frame-overlay/.npmignore | 3 + packages/stack-frame-overlay/README.md | 7 + packages/stack-frame-overlay/package.json | 52 + .../src/components/additional.js | 52 + .../src/components/close.js | 25 + .../src/components/code.js | 87 ++ .../src/components/footer.js | 22 + .../src/components/frame.js | 223 ++++ .../src/components/frames.js | 117 ++ .../src/components/overlay.js | 74 ++ .../src/effects/proxyConsole.js | 15 + .../src/effects/shortcuts.js | 44 + .../src/effects/stackTraceLimit.js | 36 + .../src/effects/unhandledError.js | 40 + .../src/effects/unhandledRejection.js | 46 + packages/stack-frame-overlay/src/index.js | 9 + packages/stack-frame-overlay/src/overlay.js | 213 +++ packages/stack-frame-overlay/src/styles.js | 186 +++ .../src/utils/dom/absolutifyCaret.js | 34 + .../src/utils/dom/consumeEvent.js | 9 + .../stack-frame-overlay/src/utils/dom/css.js | 40 + .../src/utils/dom/enableTabClick.js | 15 + .../src/utils/errorRegister.js | 63 + .../src/utils/isInternalFile.js | 10 + 32 files changed, 1447 insertions(+), 1168 deletions(-) delete mode 100644 packages/react-dev-utils/crashOverlay.js create mode 100644 packages/stack-frame-overlay/.babelrc create mode 100644 packages/stack-frame-overlay/.eslintrc create mode 100644 packages/stack-frame-overlay/.flowconfig create mode 100644 packages/stack-frame-overlay/.gitignore create mode 100644 packages/stack-frame-overlay/.npmignore create mode 100644 packages/stack-frame-overlay/README.md create mode 100644 packages/stack-frame-overlay/package.json create mode 100644 packages/stack-frame-overlay/src/components/additional.js create mode 100644 packages/stack-frame-overlay/src/components/close.js create mode 100644 packages/stack-frame-overlay/src/components/code.js create mode 100644 packages/stack-frame-overlay/src/components/footer.js create mode 100644 packages/stack-frame-overlay/src/components/frame.js create mode 100644 packages/stack-frame-overlay/src/components/frames.js create mode 100644 packages/stack-frame-overlay/src/components/overlay.js create mode 100644 packages/stack-frame-overlay/src/effects/proxyConsole.js create mode 100644 packages/stack-frame-overlay/src/effects/shortcuts.js create mode 100644 packages/stack-frame-overlay/src/effects/stackTraceLimit.js create mode 100644 packages/stack-frame-overlay/src/effects/unhandledError.js create mode 100644 packages/stack-frame-overlay/src/effects/unhandledRejection.js create mode 100644 packages/stack-frame-overlay/src/index.js create mode 100644 packages/stack-frame-overlay/src/overlay.js create mode 100644 packages/stack-frame-overlay/src/styles.js create mode 100644 packages/stack-frame-overlay/src/utils/dom/absolutifyCaret.js create mode 100644 packages/stack-frame-overlay/src/utils/dom/consumeEvent.js create mode 100644 packages/stack-frame-overlay/src/utils/dom/css.js create mode 100644 packages/stack-frame-overlay/src/utils/dom/enableTabClick.js create mode 100644 packages/stack-frame-overlay/src/utils/errorRegister.js create mode 100644 packages/stack-frame-overlay/src/utils/isInternalFile.js diff --git a/package.json b/package.json index 9ca7cfc03d9..0991b82f03b 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,17 @@ { "private": true, "scripts": { - "build": "node packages/react-scripts/scripts/build.js", + "build:overlay": "cd packages/stack-frame-overlay/ && npm run build:prod", + "build:app": "node packages/react-scripts/scripts/build.js", + "build": "run-s build:overlay build:app", "changelog": "lerna-changelog", "create-react-app": "tasks/cra.sh", "e2e": "tasks/e2e-simple.sh", "postinstall": "lerna bootstrap", "publish": "tasks/release.sh", - "start": "node packages/react-scripts/scripts/start.js", + "start:overlay": "cd packages/stack-frame-overlay/ && npm start", + "start:app": "sleep 3 && node packages/react-scripts/scripts/start.js", + "start": "run-p start:overlay start:app", "test": "node packages/react-scripts/scripts/test.js --env=jsdom", "format": "prettier --trailing-comma es5 --single-quote --write 'packages/*/*.js' 'packages/*/!(node_modules)/**/*.js'", "precommit": "lint-staged" @@ -18,6 +22,7 @@ "lerna": "2.0.0-beta.38", "lerna-changelog": "^0.2.3", "lint-staged": "^3.3.1", + "npm-run-all": "^4.0.2", "prettier": "^0.21.0" }, "lint-staged": { diff --git a/packages/react-dev-utils/crashOverlay.js b/packages/react-dev-utils/crashOverlay.js deleted file mode 100644 index 72c110e16df..00000000000 --- a/packages/react-dev-utils/crashOverlay.js +++ /dev/null @@ -1,1165 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -'use strict'; - -var stackFrameParser = require('stack-frame-parser'); -var stackFrameMapper = require('stack-frame-mapper'); -var stackFrameUnmapper = require('stack-frame-unmapper'); -var codeFrame = require('babel-code-frame'); -var ansiHTML = require('./ansiHTML'); - -var boundErrorHandler = null; - -function errorHandler(callback, e) { - if (!e.error) { - return; - } - // $FlowFixMe - var error = e.error; - - if (error instanceof Error) { - callback(error); - } else { - // A non-error was thrown, we don't have a trace. :( - // Look in your browser's devtools for more information - callback(new Error(error)); - } -} - -function registerUnhandledError(target, callback) { - if (boundErrorHandler !== null) { - return; - } - boundErrorHandler = errorHandler.bind(undefined, callback); - target.addEventListener('error', boundErrorHandler); -} - -function unregisterUnhandledError(target) { - if (boundErrorHandler === null) { - return; - } - target.removeEventListener('error', boundErrorHandler); - boundErrorHandler = null; -} - -var boundRejectionHandler = null; - -function rejectionHandler(callback, e) { - if (e == null || e.reason == null) { - return callback(new Error('Unknown')); - } - var reason = e.reason; - - if (reason instanceof Error) { - return callback(reason); - } - // A non-error was rejected, we don't have a trace :( - // Look in your browser's devtools for more information - return callback(new Error(reason)); -} - -function registerUnhandledRejection(target, callback) { - if (boundRejectionHandler !== null) { - return; - } - boundRejectionHandler = rejectionHandler.bind(undefined, callback); - // $FlowFixMe - target.addEventListener('unhandledrejection', boundRejectionHandler); -} - -function unregisterUnhandledRejection(target) { - if (boundRejectionHandler === null) { - return; - } - // $FlowFixMe - target.removeEventListener('unhandledrejection', boundRejectionHandler); - boundRejectionHandler = null; -} - -var SHORTCUT_ESCAPE = 'SHORTCUT_ESCAPE'; -var SHORTCUT_LEFT = 'SHORTCUT_LEFT'; -var SHORTCUT_RIGHT = 'SHORTCUT_RIGHT'; - -var boundKeyHandler = null; - -function keyHandler(callback, e) { - var key = e.key, keyCode = e.keyCode, which = e.which; - - if (key === 'Escape' || keyCode === 27 || which === 27) { - callback(SHORTCUT_ESCAPE); - } else if (key === 'ArrowLeft' || keyCode === 37 || which === 37) { - callback(SHORTCUT_LEFT); - } else if (key === 'ArrowRight' || keyCode === 39 || which === 39) { - callback(SHORTCUT_RIGHT); - } -} - -function registerShortcuts(target, callback) { - if (boundKeyHandler !== null) { - return; - } - boundKeyHandler = keyHandler.bind(undefined, callback); - target.addEventListener('keydown', boundKeyHandler); -} - -function unregisterShortcuts(target) { - if (boundKeyHandler === null) { - return; - } - target.removeEventListener('keydown', boundKeyHandler); - boundKeyHandler = null; -} - -var stackTraceRegistered = false; -// Default: https://docs.microsoft.com/en-us/scripting/javascript/reference/stacktracelimit-property-error-javascript -var restoreStackTraceValue = 10; - -var MAX_STACK_LENGTH = 50; - -function registerStackTraceLimit() { - var limit = arguments.length > 0 && arguments[0] !== undefined - ? arguments[0] - : MAX_STACK_LENGTH; - - if (stackTraceRegistered) { - return; - } - try { - restoreStackTraceValue = Error.stackTraceLimit; - Error.stackTraceLimit = limit; - stackTraceRegistered = true; - } catch (e) { - // Not all browsers support this so we don't care if it errors - } -} - -function unregisterStackTraceLimit() { - if (!stackTraceRegistered) { - return; - } - try { - Error.stackTraceLimit = restoreStackTraceValue; - stackTraceRegistered = false; - } catch (e) { - // Not all browsers support this so we don't care if it errors - } -} - -var recorded = []; -var errorsConsumed = 0; - -function consume(error) { - var unhandledRejection = arguments.length > 1 && arguments[1] !== undefined - ? arguments[1] - : false; - var contextSize = arguments.length > 2 && arguments[2] !== undefined - ? arguments[2] - : 3; - - var parsedFrames = stackFrameParser.parse(error); - var enhancedFramesPromise = void 0; - if (error.__unmap_source) { - enhancedFramesPromise = stackFrameUnmapper.unmap( - error.__unmap_source, - parsedFrames, - contextSize - ); - } else { - enhancedFramesPromise = stackFrameMapper.map(parsedFrames, contextSize); - } - return enhancedFramesPromise.then(function(enhancedFrames) { - enhancedFrames = enhancedFrames.filter(function(_ref) { - var functionName = _ref.functionName; - return functionName == null || - functionName.indexOf('__stack_frame_overlay_proxy_console__') === -1; - }); - recorded[++errorsConsumed] = { - error: error, - unhandledRejection: unhandledRejection, - contextSize: contextSize, - enhancedFrames: enhancedFrames, - }; - return errorsConsumed; - }); -} - -function getErrorRecord(ref) { - return recorded[ref]; -} - -function drain() { - // $FlowFixMe - var keys = Object.keys(recorded); - for (var index = 0; index < keys.length; ++index) { - delete recorded[keys[index]]; - } -} - -var black = '#293238'; -var darkGray = '#878e91'; -var lightGray = '#fafafa'; -var red = '#ce1126'; -var lightRed = '#fccfcf'; -var yellow = '#fbf5b4'; - -var iframeStyle = { - 'background-color': lightGray, - position: 'fixed', - top: '1em', - left: '1em', - bottom: '1em', - right: '1em', - width: 'calc(100% - 2em)', - height: 'calc(100% - 2em)', - border: 'none', - 'border-radius': '3px', - 'box-shadow': '0 0 6px 0 rgba(0, 0, 0, 0.5)', - 'z-index': 1337, -}; - -var overlayStyle = { - 'box-sizing': 'border-box', - padding: '4rem', - 'font-family': 'Consolas, Menlo, monospace', - color: black, - 'white-space': 'pre-wrap', - overflow: 'auto', - 'overflow-x': 'hidden', - 'word-break': 'break-word', - 'line-height': 1.5, -}; - -var hintsStyle = { - 'font-size': '0.8em', - 'margin-top': '-3em', - 'margin-bottom': '3em', - 'text-align': 'right', - color: darkGray, -}; - -var hintStyle = { - padding: '0.5em 1em', - cursor: 'pointer', -}; - -var closeButtonStyle = { - 'font-size': '26px', - color: black, - padding: '0.5em 1em', - cursor: 'pointer', - position: 'absolute', - right: 0, - top: 0, -}; - -var additionalStyle = { - 'margin-bottom': '1.5em', - 'margin-top': '-4em', -}; - -var headerStyle = { - 'font-size': '1.7em', - 'font-weight': 'bold', - color: red, -}; - -var functionNameStyle = { - 'margin-top': '1em', - 'font-size': '1.2em', -}; - -var linkStyle = { - 'font-size': '0.9em', -}; - -var anchorStyle = { - 'text-decoration': 'none', - color: darkGray, -}; - -var traceStyle = { - 'font-size': '1em', -}; - -var depStyle = { - 'font-size': '1.2em', -}; - -var primaryErrorStyle = { - 'background-color': lightRed, -}; - -var secondaryErrorStyle = { - 'background-color': yellow, -}; - -var omittedFramesStyle = { - color: black, - 'font-size': '0.9em', - margin: '1.5em 0', - cursor: 'pointer', -}; - -var preStyle = { - display: 'block', - padding: '0.5em', - 'margin-top': '1.5em', - 'margin-bottom': '0px', - 'overflow-x': 'auto', - 'font-size': '1.1em', - 'white-space': 'pre', -}; - -var toggleStyle = { - 'margin-bottom': '1.5em', - color: darkGray, - cursor: 'pointer', -}; - -var codeStyle = { - 'font-family': 'Consolas, Menlo, monospace', -}; - -var hiddenStyle = { - display: 'none', -}; - -var groupStyle = { - 'margin-left': '1em', -}; - -var _groupElemStyle = { - 'background-color': 'inherit', - 'border-color': '#ddd', - 'border-width': '1px', - 'border-radius': '4px', - 'border-style': 'solid', - padding: '3px 6px', - cursor: 'pointer', -}; - -var groupElemLeft = Object.assign({}, _groupElemStyle, { - 'border-top-right-radius': '0px', - 'border-bottom-right-radius': '0px', - 'margin-right': '0px', -}); - -var groupElemRight = Object.assign({}, _groupElemStyle, { - 'border-top-left-radius': '0px', - 'border-bottom-left-radius': '0px', - 'margin-left': '-1px', -}); - -var footerStyle = { - 'text-align': 'center', - color: darkGray, -}; - -var injectedCount = 0; -var injectedCache = {}; - -function getHead(document) { - return document.head || document.getElementsByTagName('head')[0]; -} - -function injectCss(document, css) { - var head = getHead(document); - var style = document.createElement('style'); - style.type = 'text/css'; - style.appendChild(document.createTextNode(css)); - head.appendChild(style); - - injectedCache[++injectedCount] = style; - return injectedCount; -} - -function applyStyles(element, styles) { - element.setAttribute('style', ''); - for (var key in styles) { - if (!styles.hasOwnProperty(key)) { - continue; - } - // $FlowFixMe - element.style[key] = styles[key]; - } -} - -function createHint(document, hint) { - var span = document.createElement('span'); - span.appendChild(document.createTextNode(hint)); - applyStyles(span, hintStyle); - return span; -} - -function createClose(document, callback) { - var hints = document.createElement('div'); - applyStyles(hints, hintsStyle); - - var close = createHint(document, '×'); - close.addEventListener('click', function() { - return callback(); - }); - applyStyles(close, closeButtonStyle); - hints.appendChild(close); - return hints; -} - -function enableTabClick(node) { - node.setAttribute('tabindex', '0'); - node.addEventListener('keydown', function(e) { - var key = e.key, which = e.which, keyCode = e.keyCode; - - if (key === 'Enter' || which === 13 || keyCode === 13) { - e.preventDefault(); - if (typeof e.target.click === 'function') { - e.target.click(); - } - } - }); -} - -function removeNextBr(parent, component) { - while (component != null && component.tagName.toLowerCase() !== 'br') { - component = component.nextElementSibling; - } - if (component != null) { - parent.removeChild(component); - } -} - -function absolutifyCaret(component) { - var ccn = component.childNodes; - for (var index = 0; index < ccn.length; ++index) { - var c = ccn[index]; - // $FlowFixMe - if (c.tagName.toLowerCase() !== 'span') { - continue; - } - var _text = c.innerText; - if (_text == null) { - continue; - } - var text = _text.replace(/\s/g, ''); - if (text !== '|^') { - continue; - } - // $FlowFixMe - c.style.position = 'absolute'; - // $FlowFixMe - removeNextBr(component, c); - } -} - -function createCode(document, sourceLines, lineNum, columnNum, contextSize) { - var main = arguments.length > 5 && arguments[5] !== undefined - ? arguments[5] - : false; - - var sourceCode = []; - var whiteSpace = Infinity; - sourceLines.forEach(function(e) { - var text = e.content; - - var m = text.match(/^\s*/); - if (text === '') { - return; - } - if (m && m[0]) { - whiteSpace = Math.min(whiteSpace, m[0].length); - } else { - whiteSpace = 0; - } - }); - sourceLines.forEach(function(e) { - var text = e.content; - var line = e.lineNumber; - - if (isFinite(whiteSpace)) { - text = text.substring(whiteSpace); - } - sourceCode[line - 1] = text; - }); - var ansiHighlight = codeFrame( - sourceCode.join('\n'), - lineNum, - columnNum - (isFinite(whiteSpace) ? whiteSpace : 0), - { - forceColor: true, - linesAbove: contextSize, - linesBelow: contextSize, - } - ); - var htmlHighlight = ansiHTML(ansiHighlight); - var code = document.createElement('code'); - code.innerHTML = htmlHighlight; - absolutifyCaret(code); - applyStyles(code, codeStyle); - - var ccn = code.childNodes; - oLoop: for (var index = 0; index < ccn.length; ++index) { - var node = ccn[index]; - var ccn2 = node.childNodes; - for (var index2 = 0; index2 < ccn2.length; ++index2) { - var lineNode = ccn2[index2]; - var text = lineNode.innerText; - if (text == null) { - continue; - } - if (text.indexOf(' ' + lineNum + ' |') === -1) { - continue; - } - // $FlowFixMe - applyStyles(node, main ? primaryErrorStyle : secondaryErrorStyle); - break oLoop; - } - } - var pre = document.createElement('pre'); - applyStyles(pre, preStyle); - pre.appendChild(code); - return pre; -} - -function isInternalFile(url, sourceFileName) { - return url.indexOf('/~/') !== -1 || - url.indexOf('/node_modules/') !== -1 || - url.trim().indexOf(' ') !== -1 || - sourceFileName == null || - sourceFileName.length === 0; -} - -function getGroupToggle(document, omitsCount, omitBundle) { - var omittedFrames = document.createElement('div'); - enableTabClick(omittedFrames); - var text1 = document.createTextNode( - '\u25B6 ' + omitsCount + ' stack frames were collapsed.' - ); - omittedFrames.appendChild(text1); - omittedFrames.addEventListener('click', function() { - var hide = text1.textContent.match(/▲/); - var list = document.getElementsByName('bundle-' + omitBundle); - for (var index = 0; index < list.length; ++index) { - var n = list[index]; - if (hide) { - n.style.display = 'none'; - } else { - n.style.display = ''; - } - } - if (hide) { - text1.textContent = text1.textContent.replace(/▲/, '▶'); - text1.textContent = text1.textContent.replace(/expanded/, 'collapsed'); - } else { - text1.textContent = text1.textContent.replace(/▶/, '▲'); - text1.textContent = text1.textContent.replace(/collapsed/, 'expanded'); - } - }); - applyStyles(omittedFrames, omittedFramesStyle); - return omittedFrames; -} - -function insertBeforeBundle( - document, - parent, - omitsCount, - omitBundle, - actionElement -) { - var children = document.getElementsByName('bundle-' + omitBundle); - if (children.length < 1) { - return; - } - var first = children[0]; - while (first != null && first.parentNode !== parent) { - first = first.parentNode; - } - var div = document.createElement('div'); - enableTabClick(div); - div.setAttribute('name', 'bundle-' + omitBundle); - var text = document.createTextNode( - '\u25BC ' + omitsCount + ' stack frames were expanded.' - ); - div.appendChild(text); - div.addEventListener('click', function() { - return actionElement.click(); - }); - applyStyles(div, omittedFramesStyle); - div.style.display = 'none'; - - parent.insertBefore(div, first); -} - -function frameDiv(document, functionName, url, internalUrl) { - var frame = document.createElement('div'); - var frameFunctionName = document.createElement('div'); - - var cleanedFunctionName = void 0; - if (!functionName || functionName === 'Object.') { - cleanedFunctionName = '(anonymous function)'; - } else { - cleanedFunctionName = functionName; - } - - var cleanedUrl = url.replace('webpack://', '.'); - - if (internalUrl) { - applyStyles( - frameFunctionName, - Object.assign({}, functionNameStyle, depStyle) - ); - } else { - applyStyles(frameFunctionName, functionNameStyle); - } - - frameFunctionName.appendChild(document.createTextNode(cleanedFunctionName)); - frame.appendChild(frameFunctionName); - - var frameLink = document.createElement('div'); - applyStyles(frameLink, linkStyle); - var frameAnchor = document.createElement('a'); - applyStyles(frameAnchor, anchorStyle); - frameAnchor.appendChild(document.createTextNode(cleanedUrl)); - frameLink.appendChild(frameAnchor); - frame.appendChild(frameLink); - - return frame; -} - -function createFrame( - document, - frameSetting, - frame, - contextSize, - critical, - omits, - omitBundle, - parentContainer, - lastElement -) { - var compiled = frameSetting.compiled; - var functionName = frame.functionName, - fileName = frame.fileName, - lineNumber = frame.lineNumber, - columnNumber = frame.columnNumber, - scriptLines = frame._scriptCode, - sourceFileName = frame._originalFileName, - sourceLineNumber = frame._originalLineNumber, - sourceColumnNumber = frame._originalColumnNumber, - sourceLines = frame._originalScriptCode; - - var url = void 0; - if (!compiled && sourceFileName) { - url = sourceFileName + ':' + sourceLineNumber; - if (sourceColumnNumber) { - url += ':' + sourceColumnNumber; - } - } else { - url = fileName + ':' + lineNumber; - if (columnNumber) { - url += ':' + columnNumber; - } - } - - var needsHidden = false; - var internalUrl = isInternalFile(url, sourceFileName); - if (internalUrl) { - ++omits.value; - needsHidden = true; - } - var collapseElement = null; - if (!internalUrl || lastElement) { - if (omits.value > 0) { - var capV = omits.value; - var omittedFrames = getGroupToggle(document, capV, omitBundle); - window.requestAnimationFrame(function() { - insertBeforeBundle( - document, - parentContainer, - capV, - omitBundle, - omittedFrames - ); - }); - if (lastElement && internalUrl) { - collapseElement = omittedFrames; - } else { - parentContainer.appendChild(omittedFrames); - } - ++omits.bundle; - } - omits.value = 0; - } - - var elem = frameDiv(document, functionName, url, internalUrl); - if (needsHidden) { - applyStyles(elem, hiddenStyle); - elem.setAttribute('name', 'bundle-' + omitBundle); - } - - var hasSource = false; - if (!internalUrl) { - if (compiled && scriptLines.length !== 0) { - elem.appendChild( - createCode( - document, - scriptLines, - lineNumber, - columnNumber, - contextSize, - critical - ) - ); - hasSource = true; - } else if (!compiled && sourceLines.length !== 0) { - elem.appendChild( - createCode( - document, - sourceLines, - sourceLineNumber, - sourceColumnNumber, - contextSize, - critical - ) - ); - hasSource = true; - } - } - - return { elem: elem, hasSource: hasSource, collapseElement: collapseElement }; -} - -function createFrameWrapper( - document, - parent, - factory, - lIndex, - frameSettings, - contextSize -) { - var fac = factory(); - if (fac == null) { - return; - } - var hasSource = fac.hasSource, - elem = fac.elem, - collapseElement = fac.collapseElement; - - var elemWrapper = document.createElement('div'); - elemWrapper.appendChild(elem); - - if (hasSource) { - var compiledDiv = document.createElement('div'); - enableTabClick(compiledDiv); - applyStyles(compiledDiv, toggleStyle); - - var o = frameSettings[lIndex]; - var compiledText = document.createTextNode( - 'View ' + (o && o.compiled ? 'source' : 'compiled') - ); - compiledDiv.addEventListener('click', function() { - if (o) { - o.compiled = !o.compiled; - } - - var next = createFrameWrapper( - document, - parent, - factory, - lIndex, - frameSettings, - contextSize - ); - if (next != null) { - parent.insertBefore(next, elemWrapper); - parent.removeChild(elemWrapper); - } - }); - compiledDiv.appendChild(compiledText); - elemWrapper.appendChild(compiledDiv); - } - - if (collapseElement != null) { - elemWrapper.appendChild(collapseElement); - } - - return elemWrapper; -} - -function createFrames(document, resolvedFrames, frameSettings, contextSize) { - if (resolvedFrames.length !== frameSettings.length) { - throw new Error( - 'You must give a frame settings array of identical length to resolved frames.' - ); - } - var trace = document.createElement('div'); - applyStyles(trace, traceStyle); - - var index = 0; - var critical = true; - var omits = { value: 0, bundle: 1 }; - resolvedFrames.forEach(function(frame) { - var lIndex = index++; - var elem = createFrameWrapper( - document, - trace, - createFrame.bind( - undefined, - document, - frameSettings[lIndex], - frame, - contextSize, - critical, - omits, - omits.bundle, - trace, - index === resolvedFrames.length - ), - lIndex, - frameSettings, - contextSize - ); - if (elem == null) { - return; - } - critical = false; - trace.appendChild(elem); - }); - //TODO: fix this - omits.value = 0; - - return trace; -} - -function createFooter(document) { - var div = document.createElement('div'); - applyStyles(div, footerStyle); - div.appendChild( - document.createTextNode( - 'This screen is visible only in development. It will not appear when the app crashes in production.' - ) - ); - div.appendChild(document.createElement('br')); - div.appendChild( - document.createTextNode( - 'Open your browser’s developer console to further inspect this error.' - ) - ); - return div; -} - -function consumeEvent(e) { - e.preventDefault(); - if (typeof e.target.blur === 'function') { - e.target.blur(); - } -} - -function updateAdditional( - document, - additionalReference, - currentError, - totalErrors, - switchCallback -) { - if (additionalReference.lastChild) { - additionalReference.removeChild(additionalReference.lastChild); - } - - var text = ' '; - if (totalErrors <= 1) { - additionalReference.appendChild(document.createTextNode(text)); - return; - } - text = 'Errors ' + currentError + ' of ' + totalErrors; - var span = document.createElement('span'); - span.appendChild(document.createTextNode(text)); - var group = document.createElement('span'); - applyStyles(group, groupStyle); - var left = document.createElement('button'); - applyStyles(left, groupElemLeft); - left.addEventListener('click', function(e) { - consumeEvent(e); - switchCallback(-1); - }); - left.appendChild(document.createTextNode('←')); - enableTabClick(left); - var right = document.createElement('button'); - applyStyles(right, groupElemRight); - right.addEventListener('click', function(e) { - consumeEvent(e); - switchCallback(1); - }); - right.appendChild(document.createTextNode('→')); - enableTabClick(right); - group.appendChild(left); - group.appendChild(right); - span.appendChild(group); - additionalReference.appendChild(span); -} - -function createOverlay( - document, - name, - message, - frames, - contextSize, - currentError, - totalErrors, - switchCallback, - closeCallback -) { - var frameSettings = frames.map(function() { - return { compiled: false }; - }); - // Create overlay - var overlay = document.createElement('div'); - applyStyles(overlay, overlayStyle); - overlay.appendChild(createClose(document, closeCallback)); - - // Create container - var container = document.createElement('div'); - container.className = 'cra-container'; - overlay.appendChild(container); - - // Create additional - var additional = document.createElement('div'); - applyStyles(additional, additionalStyle); - container.appendChild(additional); - updateAdditional( - document, - additional, - currentError, - totalErrors, - switchCallback - ); - - // Create header - var header = document.createElement('div'); - applyStyles(header, headerStyle); - if (message.match(/^\w*:/)) { - header.appendChild(document.createTextNode(message)); - } else { - header.appendChild(document.createTextNode(name + ': ' + message)); - } - container.appendChild(header); - - // Create trace - container.appendChild( - createFrames(document, frames, frameSettings, contextSize) - ); - - // Show message - container.appendChild(createFooter(document)); - - return { - overlay: overlay, - additional: additional, - }; -} - -var CONTEXT_SIZE = 3; -var iframeReference = null; -var additionalReference = null; -var errorReferences = []; -var currReferenceIndex = -1; - -var css = [ - '.cra-container {', - ' padding-right: 15px;', - ' padding-left: 15px;', - ' margin-right: auto;', - ' margin-left: auto;', - '}', - '', - '@media (min-width: 768px) {', - ' .cra-container {', - ' width: calc(750px - 6em);', - ' }', - '}', - '', - '@media (min-width: 992px) {', - ' .cra-container {', - ' width: calc(970px - 6em);', - ' }', - '}', - '', - '@media (min-width: 1200px) {', - ' .cra-container {', - ' width: calc(1170px - 6em);', - ' }', - '}', -].join('\n'); - -function render(name, message, resolvedFrames) { - disposeCurrentView(); - - var iframe = window.document.createElement('iframe'); - applyStyles(iframe, iframeStyle); - iframeReference = iframe; - iframe.onload = function() { - if (iframeReference == null) { - return; - } - var w = iframeReference.contentWindow; - var document = iframeReference.contentDocument; - - var _createOverlay = createOverlay( - document, - name, - message, - resolvedFrames, - CONTEXT_SIZE, - currReferenceIndex + 1, - errorReferences.length, - function(offset) { - switchError(offset); - }, - function() { - unmount(); - } - ), - overlay = _createOverlay.overlay, - additional = _createOverlay.additional; - - if (w != null) { - w.onkeydown = function(event) { - keyHandler( - function(type) { - return shortcutHandler(type); - }, - event - ); - }; - } - injectCss(iframeReference.contentDocument, css); - if (document.body != null) { - document.body.appendChild(overlay); - } - additionalReference = additional; - }; - window.document.body.appendChild(iframe); -} - -function renderErrorByIndex(index) { - currReferenceIndex = index; - - var _getErrorRecord = getErrorRecord(errorReferences[index]), - error = _getErrorRecord.error, - unhandledRejection = _getErrorRecord.unhandledRejection, - enhancedFrames = _getErrorRecord.enhancedFrames; - - if (unhandledRejection) { - render( - 'Unhandled Rejection (' + error.name + ')', - error.message, - enhancedFrames - ); - } else { - render(error.name, error.message, enhancedFrames); - } -} - -function switchError(offset) { - var nextView = currReferenceIndex + offset; - if (nextView < 0 || nextView >= errorReferences.length) { - return; - } - renderErrorByIndex(nextView); -} - -function disposeCurrentView() { - if (iframeReference === null) { - return; - } - window.document.body.removeChild(iframeReference); - iframeReference = null; - additionalReference = null; -} - -function unmount() { - disposeCurrentView(); - drain(); - errorReferences = []; - currReferenceIndex = -1; -} - -function crash(error) { - var unhandledRejection = arguments.length > 1 && arguments[1] !== undefined - ? arguments[1] - : false; - - if (module.hot && typeof module.hot.decline === 'function') { - module.hot.decline(); - } - consume(error, unhandledRejection, CONTEXT_SIZE) - .then(function(ref) { - errorReferences.push(ref); - if (iframeReference !== null && additionalReference !== null) { - updateAdditional( - iframeReference.contentDocument, - additionalReference, - currReferenceIndex + 1, - errorReferences.length, - function(offset) { - switchError(offset); - } - ); - } else { - if (errorReferences.length !== 1) { - throw new Error('Something is *really* wrong.'); - } - renderErrorByIndex((currReferenceIndex = 0)); - } - }) - .catch(function(e) { - console.log('Could not consume error:', e); - }); -} - -function shortcutHandler(type) { - switch (type) { - case SHORTCUT_ESCAPE: { - unmount(); - break; - } - case SHORTCUT_LEFT: { - switchError(-1); - break; - } - case SHORTCUT_RIGHT: { - switchError(1); - break; - } - } -} - -function inject() { - registerUnhandledError(window, function(error) { - return crash(error); - }); - registerUnhandledRejection(window, function(error) { - return crash(error, true); - }); - registerShortcuts(window, shortcutHandler); - registerStackTraceLimit(); -} - -function uninject() { - unregisterStackTraceLimit(); - unregisterShortcuts(window); - unregisterUnhandledRejection(window); - unregisterUnhandledError(window); -} - -inject(); -if (module.hot && typeof module.hot.dispose === 'function') { - module.hot.dispose(function() { - uninject(); - }); -} diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index e86847546e4..5079854d811 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -55,7 +55,7 @@ module.exports = { // We ship a few polyfills by default: require.resolve('./polyfills'), // Errors should be considered fatal in development - require.resolve('react-dev-utils/crashOverlay'), + require.resolve('stack-frame-overlay'), // Finally, this is your app's code: paths.appIndexJs, // We include the app code last so that if there is a runtime error during diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 4efdad0881c..a1077442a5a 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -52,6 +52,7 @@ "postcss-loader": "1.3.3", "promise": "7.1.1", "react-dev-utils": "^0.5.2", + "stack-frame-overlay": "^0.4.0", "style-loader": "0.16.1", "url-loader": "0.5.8", "webpack": "2.4.1", diff --git a/packages/stack-frame-overlay/.babelrc b/packages/stack-frame-overlay/.babelrc new file mode 100644 index 00000000000..c14b2828d16 --- /dev/null +++ b/packages/stack-frame-overlay/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["react-app"] +} diff --git a/packages/stack-frame-overlay/.eslintrc b/packages/stack-frame-overlay/.eslintrc new file mode 100644 index 00000000000..5e603ecd193 --- /dev/null +++ b/packages/stack-frame-overlay/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "react-app" +} diff --git a/packages/stack-frame-overlay/.flowconfig b/packages/stack-frame-overlay/.flowconfig new file mode 100644 index 00000000000..261b8646fc3 --- /dev/null +++ b/packages/stack-frame-overlay/.flowconfig @@ -0,0 +1,8 @@ +[ignore] + +[include] +src/**/*.js + +[libs] + +[options] diff --git a/packages/stack-frame-overlay/.gitignore b/packages/stack-frame-overlay/.gitignore new file mode 100644 index 00000000000..2e6b92a0c97 --- /dev/null +++ b/packages/stack-frame-overlay/.gitignore @@ -0,0 +1,2 @@ +lib/ +coverage/ diff --git a/packages/stack-frame-overlay/.npmignore b/packages/stack-frame-overlay/.npmignore new file mode 100644 index 00000000000..4958645358d --- /dev/null +++ b/packages/stack-frame-overlay/.npmignore @@ -0,0 +1,3 @@ +__tests__ +*.test.js +*.spec.js diff --git a/packages/stack-frame-overlay/README.md b/packages/stack-frame-overlay/README.md new file mode 100644 index 00000000000..b55d53eb405 --- /dev/null +++ b/packages/stack-frame-overlay/README.md @@ -0,0 +1,7 @@ +# `stack-frame-overlay` + +An overlay for displaying stack frames. + +# API + + diff --git a/packages/stack-frame-overlay/package.json b/packages/stack-frame-overlay/package.json new file mode 100644 index 00000000000..fdbaad41640 --- /dev/null +++ b/packages/stack-frame-overlay/package.json @@ -0,0 +1,52 @@ +{ + "name": "stack-frame-overlay", + "version": "0.4.0", + "description": "An overlay for displaying stack frames.", + "main": "lib/index.js", + "scripts": { + "prepublishOnly": "npm run build:prod && npm test", + "start": "NODE_ENV=development npm run build -- --watch", + "test": "exit 0", + "build": "babel src/ -d lib/", + "build:prod": "NODE_ENV=production babel src/ -d lib/" + }, + "repository": "facebookincubator/create-react-app", + "license": "BSD-3-Clause", + "bugs": { + "url": "https://github.com/facebookincubator/create-react-app/issues" + }, + "keywords": [ + "overlay", + "syntax", + "error", + "red", + "box", + "redbox", + "crash", + "warning" + ], + "author": "Joe Haddad ", + "files": [ + "lib/" + ], + "dependencies": { + "anser": "^1.2.5", + "babel-code-frame": "^6.22.0", + "stack-frame": "0.4.0", + "stack-frame-mapper": "0.4.0", + "stack-frame-parser": "0.4.0", + "stack-frame-unmapper": "0.4.0" + }, + "devDependencies": { + "babel-cli": "^6.24.1", + "babel-eslint": "7.x", + "babel-preset-react-app": "^2.2.0", + "eslint": "^3.16.1", + "eslint-config-react-app": "^0.6.2", + "eslint-plugin-flowtype": "^2.21.0", + "eslint-plugin-import": "^2.0.1", + "eslint-plugin-jsx-a11y": "^4.0.0", + "eslint-plugin-react": "^6.4.1", + "react-dev-utils": "^0.5.2" + } +} diff --git a/packages/stack-frame-overlay/src/components/additional.js b/packages/stack-frame-overlay/src/components/additional.js new file mode 100644 index 00000000000..4853c922c71 --- /dev/null +++ b/packages/stack-frame-overlay/src/components/additional.js @@ -0,0 +1,52 @@ +/* @flow */ +import { applyStyles } from '../utils/dom/css'; +import { groupStyle, groupElemLeft, groupElemRight } from '../styles'; +import { consumeEvent } from '../utils/dom/consumeEvent'; +import { enableTabClick } from '../utils/dom/enableTabClick'; + +type SwitchCallback = (offset: number) => void; +function updateAdditional( + document: Document, + additionalReference: HTMLDivElement, + currentError: number, + totalErrors: number, + switchCallback: SwitchCallback +) { + if (additionalReference.lastChild) { + additionalReference.removeChild(additionalReference.lastChild); + } + + let text = ' '; + if (totalErrors <= 1) { + additionalReference.appendChild(document.createTextNode(text)); + return; + } + text = `Errors ${currentError} of ${totalErrors}`; + const span = document.createElement('span'); + span.appendChild(document.createTextNode(text)); + const group = document.createElement('span'); + applyStyles(group, groupStyle); + const left = document.createElement('button'); + applyStyles(left, groupElemLeft); + left.addEventListener('click', function(e: MouseEvent) { + consumeEvent(e); + switchCallback(-1); + }); + left.appendChild(document.createTextNode('←')); + enableTabClick(left); + const right = document.createElement('button'); + applyStyles(right, groupElemRight); + right.addEventListener('click', function(e: MouseEvent) { + consumeEvent(e); + switchCallback(1); + }); + right.appendChild(document.createTextNode('→')); + enableTabClick(right); + group.appendChild(left); + group.appendChild(right); + span.appendChild(group); + additionalReference.appendChild(span); +} + +export type { SwitchCallback }; +export { updateAdditional }; diff --git a/packages/stack-frame-overlay/src/components/close.js b/packages/stack-frame-overlay/src/components/close.js new file mode 100644 index 00000000000..e868cc0d4ca --- /dev/null +++ b/packages/stack-frame-overlay/src/components/close.js @@ -0,0 +1,25 @@ +/* @flow */ +import { applyStyles } from '../utils/dom/css'; +import { hintsStyle, hintStyle, closeButtonStyle } from '../styles'; + +function createHint(document: Document, hint: string) { + const span = document.createElement('span'); + span.appendChild(document.createTextNode(hint)); + applyStyles(span, hintStyle); + return span; +} + +type CloseCallback = () => void; +function createClose(document: Document, callback: CloseCallback) { + const hints = document.createElement('div'); + applyStyles(hints, hintsStyle); + + const close = createHint(document, '×'); + close.addEventListener('click', () => callback()); + applyStyles(close, closeButtonStyle); + hints.appendChild(close); + return hints; +} + +export type { CloseCallback }; +export { createClose }; diff --git a/packages/stack-frame-overlay/src/components/code.js b/packages/stack-frame-overlay/src/components/code.js new file mode 100644 index 00000000000..2f74e40546e --- /dev/null +++ b/packages/stack-frame-overlay/src/components/code.js @@ -0,0 +1,87 @@ +/* @flow */ +import type { ScriptLines } from 'stack-frame'; +import { applyStyles } from '../utils/dom/css'; +import { absolutifyCaret } from '../utils/dom/absolutifyCaret'; +import { + preStyle, + codeStyle, + primaryErrorStyle, + secondaryErrorStyle, +} from '../styles'; + +import generateAnsiHtml from 'react-dev-utils/ansiHTML'; + +import codeFrame from 'babel-code-frame'; + +function createCode( + document: Document, + sourceLines: ScriptLines[], + lineNum: number, + columnNum: number, + contextSize: number, + main: boolean = false +) { + const sourceCode = []; + let whiteSpace = Infinity; + sourceLines.forEach(function(e) { + const { content: text } = e; + const m = text.match(/^\s*/); + if (text === '') { + return; + } + if (m && m[0]) { + whiteSpace = Math.min(whiteSpace, m[0].length); + } else { + whiteSpace = 0; + } + }); + sourceLines.forEach(function(e) { + let { content: text } = e; + const { lineNumber: line } = e; + + if (isFinite(whiteSpace)) { + text = text.substring(whiteSpace); + } + sourceCode[line - 1] = text; + }); + const ansiHighlight = codeFrame( + sourceCode.join('\n'), + lineNum, + columnNum - (isFinite(whiteSpace) ? whiteSpace : 0), + { + forceColor: true, + linesAbove: contextSize, + linesBelow: contextSize, + } + ); + const htmlHighlight = generateAnsiHtml(ansiHighlight); + const code = document.createElement('code'); + code.innerHTML = htmlHighlight; + absolutifyCaret(code); + applyStyles(code, codeStyle); + + const ccn = code.childNodes; + oLoop: for (let index = 0; index < ccn.length; ++index) { + const node = ccn[index]; + const ccn2 = node.childNodes; + for (let index2 = 0; index2 < ccn2.length; ++index2) { + const lineNode = ccn2[index2]; + const text = lineNode.innerText; + if (text == null) { + continue; + } + if (text.indexOf(' ' + lineNum + ' |') === -1) { + continue; + } + // $FlowFixMe + applyStyles(node, main ? primaryErrorStyle : secondaryErrorStyle); + break oLoop; + } + } + const pre = document.createElement('pre'); + applyStyles(pre, preStyle); + pre.appendChild(code); + return pre; +} + +export { createCode }; diff --git a/packages/stack-frame-overlay/src/components/footer.js b/packages/stack-frame-overlay/src/components/footer.js new file mode 100644 index 00000000000..9ddfaf57c7e --- /dev/null +++ b/packages/stack-frame-overlay/src/components/footer.js @@ -0,0 +1,22 @@ +/* @flow */ +import { applyStyles } from '../utils/dom/css'; +import { footerStyle } from '../styles'; + +function createFooter(document: Document) { + const div = document.createElement('div'); + applyStyles(div, footerStyle); + div.appendChild( + document.createTextNode( + 'This screen is visible only in development. It will not appear when the app crashes in production.' + ) + ); + div.appendChild(document.createElement('br')); + div.appendChild( + document.createTextNode( + 'Open your browser’s developer console to further inspect this error.' + ) + ); + return div; +} + +export { createFooter }; diff --git a/packages/stack-frame-overlay/src/components/frame.js b/packages/stack-frame-overlay/src/components/frame.js new file mode 100644 index 00000000000..94fea29adf1 --- /dev/null +++ b/packages/stack-frame-overlay/src/components/frame.js @@ -0,0 +1,223 @@ +/* @flow */ +import { enableTabClick } from '../utils/dom/enableTabClick'; +import { createCode } from './code'; +import { isInternalFile } from '../utils/isInternalFile'; +import type { StackFrame } from 'stack-frame'; +import type { FrameSetting, OmitsObject } from './frames'; +import { applyStyles } from '../utils/dom/css'; +import { + omittedFramesStyle, + functionNameStyle, + depStyle, + linkStyle, + anchorStyle, + hiddenStyle, +} from '../styles'; + +function getGroupToggle( + document: Document, + omitsCount: number, + omitBundle: number +) { + const omittedFrames = document.createElement('div'); + enableTabClick(omittedFrames); + const text1 = document.createTextNode( + '\u25B6 ' + omitsCount + ' stack frames were collapsed.' + ); + omittedFrames.appendChild(text1); + omittedFrames.addEventListener('click', function() { + const hide = text1.textContent.match(/▲/); + const list = document.getElementsByName('bundle-' + omitBundle); + for (let index = 0; index < list.length; ++index) { + const n = list[index]; + if (hide) { + n.style.display = 'none'; + } else { + n.style.display = ''; + } + } + if (hide) { + text1.textContent = text1.textContent.replace(/▲/, '▶'); + text1.textContent = text1.textContent.replace(/expanded/, 'collapsed'); + } else { + text1.textContent = text1.textContent.replace(/▶/, '▲'); + text1.textContent = text1.textContent.replace(/collapsed/, 'expanded'); + } + }); + applyStyles(omittedFrames, omittedFramesStyle); + return omittedFrames; +} + +function insertBeforeBundle( + document: Document, + parent: Node, + omitsCount: number, + omitBundle: number, + actionElement +) { + const children = document.getElementsByName('bundle-' + omitBundle); + if (children.length < 1) { + return; + } + let first: ?Node = children[0]; + while (first != null && first.parentNode !== parent) { + first = first.parentNode; + } + const div = document.createElement('div'); + enableTabClick(div); + div.setAttribute('name', 'bundle-' + omitBundle); + const text = document.createTextNode( + '\u25BC ' + omitsCount + ' stack frames were expanded.' + ); + div.appendChild(text); + div.addEventListener('click', function() { + return actionElement.click(); + }); + applyStyles(div, omittedFramesStyle); + div.style.display = 'none'; + + parent.insertBefore(div, first); +} + +function frameDiv(document: Document, functionName, url, internalUrl) { + const frame = document.createElement('div'); + const frameFunctionName = document.createElement('div'); + + let cleanedFunctionName; + if (!functionName || functionName === 'Object.') { + cleanedFunctionName = '(anonymous function)'; + } else { + cleanedFunctionName = functionName; + } + + const cleanedUrl = url.replace('webpack://', '.'); + + if (internalUrl) { + applyStyles( + frameFunctionName, + Object.assign({}, functionNameStyle, depStyle) + ); + } else { + applyStyles(frameFunctionName, functionNameStyle); + } + + frameFunctionName.appendChild(document.createTextNode(cleanedFunctionName)); + frame.appendChild(frameFunctionName); + + const frameLink = document.createElement('div'); + applyStyles(frameLink, linkStyle); + const frameAnchor = document.createElement('a'); + applyStyles(frameAnchor, anchorStyle); + frameAnchor.appendChild(document.createTextNode(cleanedUrl)); + frameLink.appendChild(frameAnchor); + frame.appendChild(frameLink); + + return frame; +} + +function createFrame( + document: Document, + frameSetting: FrameSetting, + frame: StackFrame, + contextSize: number, + critical: boolean, + omits: OmitsObject, + omitBundle: number, + parentContainer: HTMLDivElement, + lastElement: boolean +) { + const { compiled } = frameSetting; + const { + functionName, + fileName, + lineNumber, + columnNumber, + _scriptCode: scriptLines, + _originalFileName: sourceFileName, + _originalLineNumber: sourceLineNumber, + _originalColumnNumber: sourceColumnNumber, + _originalScriptCode: sourceLines, + } = frame; + + let url; + if (!compiled && sourceFileName) { + url = sourceFileName + ':' + sourceLineNumber; + if (sourceColumnNumber) { + url += ':' + sourceColumnNumber; + } + } else { + url = fileName + ':' + lineNumber; + if (columnNumber) { + url += ':' + columnNumber; + } + } + + let needsHidden = false; + const internalUrl = isInternalFile(url, sourceFileName); + if (internalUrl) { + ++omits.value; + needsHidden = true; + } + let collapseElement = null; + if (!internalUrl || lastElement) { + if (omits.value > 0) { + const capV = omits.value; + const omittedFrames = getGroupToggle(document, capV, omitBundle); + window.requestAnimationFrame(() => { + insertBeforeBundle( + document, + parentContainer, + capV, + omitBundle, + omittedFrames + ); + }); + if (lastElement && internalUrl) { + collapseElement = omittedFrames; + } else { + parentContainer.appendChild(omittedFrames); + } + ++omits.bundle; + } + omits.value = 0; + } + + const elem = frameDiv(document, functionName, url, internalUrl); + if (needsHidden) { + applyStyles(elem, hiddenStyle); + elem.setAttribute('name', 'bundle-' + omitBundle); + } + + let hasSource = false; + if (!internalUrl) { + if (compiled && scriptLines.length !== 0) { + elem.appendChild( + createCode( + document, + scriptLines, + lineNumber, + columnNumber, + contextSize, + critical + ) + ); + hasSource = true; + } else if (!compiled && sourceLines.length !== 0) { + elem.appendChild( + createCode( + document, + sourceLines, + sourceLineNumber, + sourceColumnNumber, + contextSize, + critical + ) + ); + hasSource = true; + } + } + + return { elem: elem, hasSource: hasSource, collapseElement: collapseElement }; +} + +export { createFrame }; diff --git a/packages/stack-frame-overlay/src/components/frames.js b/packages/stack-frame-overlay/src/components/frames.js new file mode 100644 index 00000000000..9300de30173 --- /dev/null +++ b/packages/stack-frame-overlay/src/components/frames.js @@ -0,0 +1,117 @@ +/* @flow */ +import type { StackFrame } from 'stack-frame'; +import { applyStyles } from '../utils/dom/css'; +import { traceStyle, toggleStyle } from '../styles'; +import { enableTabClick } from '../utils/dom/enableTabClick'; +import { createFrame } from './frame'; + +type OmitsObject = { value: number, bundle: number }; +type FrameSetting = { compiled: boolean }; +export type { OmitsObject, FrameSetting }; + +function createFrameWrapper( + document: Document, + parent: HTMLDivElement, + factory, + lIndex: number, + frameSettings: FrameSetting[], + contextSize: number +) { + const fac = factory(); + if (fac == null) { + return; + } + const { hasSource, elem, collapseElement } = fac; + + const elemWrapper = document.createElement('div'); + elemWrapper.appendChild(elem); + + if (hasSource) { + const compiledDiv = document.createElement('div'); + enableTabClick(compiledDiv); + applyStyles(compiledDiv, toggleStyle); + + const o = frameSettings[lIndex]; + const compiledText = document.createTextNode( + 'View ' + (o && o.compiled ? 'source' : 'compiled') + ); + compiledDiv.addEventListener('click', function() { + if (o) { + o.compiled = !o.compiled; + } + + const next = createFrameWrapper( + document, + parent, + factory, + lIndex, + frameSettings, + contextSize + ); + if (next != null) { + parent.insertBefore(next, elemWrapper); + parent.removeChild(elemWrapper); + } + }); + compiledDiv.appendChild(compiledText); + elemWrapper.appendChild(compiledDiv); + } + + if (collapseElement != null) { + elemWrapper.appendChild(collapseElement); + } + + return elemWrapper; +} + +function createFrames( + document: Document, + resolvedFrames: StackFrame[], + frameSettings: FrameSetting[], + contextSize: number +) { + if (resolvedFrames.length !== frameSettings.length) { + throw new Error( + 'You must give a frame settings array of identical length to resolved frames.' + ); + } + const trace = document.createElement('div'); + applyStyles(trace, traceStyle); + + let index = 0; + let critical = true; + const omits: OmitsObject = { value: 0, bundle: 1 }; + resolvedFrames.forEach(function(frame) { + const lIndex = index++; + const elem = createFrameWrapper( + document, + trace, + createFrame.bind( + undefined, + document, + frameSettings[lIndex], + frame, + contextSize, + critical, + omits, + omits.bundle, + trace, + index === resolvedFrames.length + ), + lIndex, + frameSettings, + contextSize + ); + if (elem == null) { + return; + } + critical = false; + trace.appendChild(elem); + }); + //TODO: fix this + omits.value = 0; + + return trace; +} + +export { createFrames }; diff --git a/packages/stack-frame-overlay/src/components/overlay.js b/packages/stack-frame-overlay/src/components/overlay.js new file mode 100644 index 00000000000..3a537de3e06 --- /dev/null +++ b/packages/stack-frame-overlay/src/components/overlay.js @@ -0,0 +1,74 @@ +/* @flow */ +import { applyStyles } from '../utils/dom/css'; +import { overlayStyle, headerStyle, additionalStyle } from '../styles'; +import { createClose } from './close'; +import { createFrames } from './frames'; +import { createFooter } from './footer'; +import type { CloseCallback } from './close'; +import type { StackFrame } from 'stack-frame'; +import { updateAdditional } from './additional'; +import type { FrameSetting } from './frames'; +import type { SwitchCallback } from './additional'; + +function createOverlay( + document: Document, + name: string, + message: string, + frames: StackFrame[], + contextSize: number, + currentError: number, + totalErrors: number, + switchCallback: SwitchCallback, + closeCallback: CloseCallback +): { + overlay: HTMLDivElement, + additional: HTMLDivElement, +} { + const frameSettings: FrameSetting[] = frames.map(() => ({ compiled: false })); + // Create overlay + const overlay = document.createElement('div'); + applyStyles(overlay, overlayStyle); + overlay.appendChild(createClose(document, closeCallback)); + + // Create container + const container = document.createElement('div'); + container.className = 'cra-container'; + overlay.appendChild(container); + + // Create additional + const additional = document.createElement('div'); + applyStyles(additional, additionalStyle); + container.appendChild(additional); + updateAdditional( + document, + additional, + currentError, + totalErrors, + switchCallback + ); + + // Create header + const header = document.createElement('div'); + applyStyles(header, headerStyle); + if (message.match(/^\w*:/)) { + header.appendChild(document.createTextNode(message)); + } else { + header.appendChild(document.createTextNode(name + ': ' + message)); + } + container.appendChild(header); + + // Create trace + container.appendChild( + createFrames(document, frames, frameSettings, contextSize) + ); + + // Show message + container.appendChild(createFooter(document)); + + return { + overlay, + additional, + }; +} + +export { createOverlay }; diff --git a/packages/stack-frame-overlay/src/effects/proxyConsole.js b/packages/stack-frame-overlay/src/effects/proxyConsole.js new file mode 100644 index 00000000000..a0a771f0e71 --- /dev/null +++ b/packages/stack-frame-overlay/src/effects/proxyConsole.js @@ -0,0 +1,15 @@ +/* @flow */ +type ConsoleProxyCallback = (message: string) => void; +const permanentRegister = function proxyConsole( + type: string, + callback: ConsoleProxyCallback +) { + const orig = console[type]; + console[type] = function __stack_frame_overlay_proxy_console__() { + const message = [].slice.call(arguments).join(' '); + callback(message); + return orig.apply(this, arguments); + }; +}; + +export { permanentRegister }; diff --git a/packages/stack-frame-overlay/src/effects/shortcuts.js b/packages/stack-frame-overlay/src/effects/shortcuts.js new file mode 100644 index 00000000000..a9ff787cd22 --- /dev/null +++ b/packages/stack-frame-overlay/src/effects/shortcuts.js @@ -0,0 +1,44 @@ +/* @flow */ +const SHORTCUT_ESCAPE = 'SHORTCUT_ESCAPE', + SHORTCUT_LEFT = 'SHORTCUT_LEFT', + SHORTCUT_RIGHT = 'SHORTCUT_RIGHT'; + +let boundKeyHandler = null; + +type ShortcutCallback = (type: string) => void; + +function keyHandler(callback: ShortcutCallback, e: KeyboardEvent) { + const { key, keyCode, which } = e; + if (key === 'Escape' || keyCode === 27 || which === 27) { + callback(SHORTCUT_ESCAPE); + } else if (key === 'ArrowLeft' || keyCode === 37 || which === 37) { + callback(SHORTCUT_LEFT); + } else if (key === 'ArrowRight' || keyCode === 39 || which === 39) { + callback(SHORTCUT_RIGHT); + } +} + +function registerShortcuts(target: EventTarget, callback: ShortcutCallback) { + if (boundKeyHandler !== null) { + return; + } + boundKeyHandler = keyHandler.bind(undefined, callback); + target.addEventListener('keydown', boundKeyHandler); +} + +function unregisterShortcuts(target: EventTarget) { + if (boundKeyHandler === null) { + return; + } + target.removeEventListener('keydown', boundKeyHandler); + boundKeyHandler = null; +} + +export { + SHORTCUT_ESCAPE, + SHORTCUT_LEFT, + SHORTCUT_RIGHT, + registerShortcuts as register, + unregisterShortcuts as unregister, + keyHandler as handler, +}; diff --git a/packages/stack-frame-overlay/src/effects/stackTraceLimit.js b/packages/stack-frame-overlay/src/effects/stackTraceLimit.js new file mode 100644 index 00000000000..c683ae298fa --- /dev/null +++ b/packages/stack-frame-overlay/src/effects/stackTraceLimit.js @@ -0,0 +1,36 @@ +/* @flow */ +let stackTraceRegistered: boolean = false; +// Default: https://docs.microsoft.com/en-us/scripting/javascript/reference/stacktracelimit-property-error-javascript +let restoreStackTraceValue: number = 10; + +const MAX_STACK_LENGTH: number = 50; + +function registerStackTraceLimit(limit: number = MAX_STACK_LENGTH) { + if (stackTraceRegistered) { + return; + } + try { + restoreStackTraceValue = Error.stackTraceLimit; + Error.stackTraceLimit = limit; + stackTraceRegistered = true; + } catch (e) { + // Not all browsers support this so we don't care if it errors + } +} + +function unregisterStackTraceLimit() { + if (!stackTraceRegistered) { + return; + } + try { + Error.stackTraceLimit = restoreStackTraceValue; + stackTraceRegistered = false; + } catch (e) { + // Not all browsers support this so we don't care if it errors + } +} + +export { + registerStackTraceLimit as register, + unregisterStackTraceLimit as unregister, +}; diff --git a/packages/stack-frame-overlay/src/effects/unhandledError.js b/packages/stack-frame-overlay/src/effects/unhandledError.js new file mode 100644 index 00000000000..68de9f5075e --- /dev/null +++ b/packages/stack-frame-overlay/src/effects/unhandledError.js @@ -0,0 +1,40 @@ +/* @flow */ +let boundErrorHandler = null; + +type ErrorCallback = (error: Error) => void; + +function errorHandler(callback: ErrorCallback, e: Event): void { + if (!e.error) { + return; + } + // $FlowFixMe + const { error } = e; + if (error instanceof Error) { + callback(error); + } else { + // A non-error was thrown, we don't have a trace. :( + // Look in your browser's devtools for more information + callback(new Error(error)); + } +} + +function registerUnhandledError(target: EventTarget, callback: ErrorCallback) { + if (boundErrorHandler !== null) { + return; + } + boundErrorHandler = errorHandler.bind(undefined, callback); + target.addEventListener('error', boundErrorHandler); +} + +function unregisterUnhandledError(target: EventTarget) { + if (boundErrorHandler === null) { + return; + } + target.removeEventListener('error', boundErrorHandler); + boundErrorHandler = null; +} + +export { + registerUnhandledError as register, + unregisterUnhandledError as unregister, +}; diff --git a/packages/stack-frame-overlay/src/effects/unhandledRejection.js b/packages/stack-frame-overlay/src/effects/unhandledRejection.js new file mode 100644 index 00000000000..dd71923a488 --- /dev/null +++ b/packages/stack-frame-overlay/src/effects/unhandledRejection.js @@ -0,0 +1,46 @@ +/* @flow */ +let boundRejectionHandler = null; + +type ErrorCallback = (error: Error) => void; + +function rejectionHandler( + callback: ErrorCallback, + e: PromiseRejectionEvent +): void { + if (e == null || e.reason == null) { + return callback(new Error('Unknown')); + } + let { reason } = e; + if (reason instanceof Error) { + return callback(reason); + } + // A non-error was rejected, we don't have a trace :( + // Look in your browser's devtools for more information + return callback(new Error(reason)); +} + +function registerUnhandledRejection( + target: EventTarget, + callback: ErrorCallback +) { + if (boundRejectionHandler !== null) { + return; + } + boundRejectionHandler = rejectionHandler.bind(undefined, callback); + // $FlowFixMe + target.addEventListener('unhandledrejection', boundRejectionHandler); +} + +function unregisterUnhandledRejection(target: EventTarget) { + if (boundRejectionHandler === null) { + return; + } + // $FlowFixMe + target.removeEventListener('unhandledrejection', boundRejectionHandler); + boundRejectionHandler = null; +} + +export { + registerUnhandledRejection as register, + unregisterUnhandledRejection as unregister, +}; diff --git a/packages/stack-frame-overlay/src/index.js b/packages/stack-frame-overlay/src/index.js new file mode 100644 index 00000000000..04cb320826a --- /dev/null +++ b/packages/stack-frame-overlay/src/index.js @@ -0,0 +1,9 @@ +/* @flow */ +import { inject, uninject } from './overlay'; + +inject(); +if (module.hot && typeof module.hot.dispose === 'function') { + module.hot.dispose(function() { + uninject(); + }); +} diff --git a/packages/stack-frame-overlay/src/overlay.js b/packages/stack-frame-overlay/src/overlay.js new file mode 100644 index 00000000000..c9b4e64c8c5 --- /dev/null +++ b/packages/stack-frame-overlay/src/overlay.js @@ -0,0 +1,213 @@ +/* @flow */ +import { + register as registerError, + unregister as unregisterError, +} from './effects/unhandledError'; +import { + register as registerPromise, + unregister as unregisterPromise, +} from './effects/unhandledRejection'; +import { + register as registerShortcuts, + unregister as unregisterShortcuts, + handler as keyEventHandler, + SHORTCUT_ESCAPE, + SHORTCUT_LEFT, + SHORTCUT_RIGHT, +} from './effects/shortcuts'; +import { + register as registerStackTraceLimit, + unregister as unregisterStackTraceLimit, +} from './effects/stackTraceLimit'; + +import { + consume as consumeError, + getErrorRecord, + drain as drainErrors, +} from './utils/errorRegister'; +import type { ErrorRecordReference } from './utils/errorRegister'; + +import type { StackFrame } from 'stack-frame'; +import { iframeStyle } from './styles'; +import { injectCss, applyStyles } from './utils/dom/css'; +import { createOverlay } from './components/overlay'; +import { updateAdditional } from './components/additional'; + +const CONTEXT_SIZE: number = 3; +let iframeReference: HTMLIFrameElement | null = null; +let additionalReference = null; +let errorReferences: ErrorRecordReference[] = []; +let currReferenceIndex: number = -1; + +const css = [ + '.cra-container {', + ' padding-right: 15px;', + ' padding-left: 15px;', + ' margin-right: auto;', + ' margin-left: auto;', + '}', + '', + '@media (min-width: 768px) {', + ' .cra-container {', + ' width: calc(750px - 6em);', + ' }', + '}', + '', + '@media (min-width: 992px) {', + ' .cra-container {', + ' width: calc(970px - 6em);', + ' }', + '}', + '', + '@media (min-width: 1200px) {', + ' .cra-container {', + ' width: calc(1170px - 6em);', + ' }', + '}', +].join('\n'); + +function render(name: string, message: string, resolvedFrames: StackFrame[]) { + disposeCurrentView(); + + const iframe = window.document.createElement('iframe'); + applyStyles(iframe, iframeStyle); + iframeReference = iframe; + iframe.onload = () => { + if (iframeReference == null) { + return; + } + const w = iframeReference.contentWindow; + const document = iframeReference.contentDocument; + + const { overlay, additional } = createOverlay( + document, + name, + message, + resolvedFrames, + CONTEXT_SIZE, + currReferenceIndex + 1, + errorReferences.length, + offset => { + switchError(offset); + }, + () => { + unmount(); + } + ); + if (w != null) { + w.onkeydown = event => { + keyEventHandler(type => shortcutHandler(type), event); + }; + } + injectCss(iframeReference.contentDocument, css); + if (document.body != null) { + document.body.appendChild(overlay); + } + additionalReference = additional; + }; + window.document.body.appendChild(iframe); +} + +function renderErrorByIndex(index: number) { + currReferenceIndex = index; + + const { error, unhandledRejection, enhancedFrames } = getErrorRecord( + errorReferences[index] + ); + + if (unhandledRejection) { + render( + 'Unhandled Rejection (' + error.name + ')', + error.message, + enhancedFrames + ); + } else { + render(error.name, error.message, enhancedFrames); + } +} + +function switchError(offset) { + const nextView = currReferenceIndex + offset; + if (nextView < 0 || nextView >= errorReferences.length) { + return; + } + renderErrorByIndex(nextView); +} + +function disposeCurrentView() { + if (iframeReference === null) { + return; + } + window.document.body.removeChild(iframeReference); + iframeReference = null; + additionalReference = null; +} + +function unmount() { + disposeCurrentView(); + drainErrors(); + errorReferences = []; + currReferenceIndex = -1; +} + +function crash(error: Error, unhandledRejection = false) { + if (module.hot && typeof module.hot.decline === 'function') { + module.hot.decline(); + } + consumeError(error, unhandledRejection, CONTEXT_SIZE) + .then(ref => { + errorReferences.push(ref); + if (iframeReference !== null && additionalReference !== null) { + updateAdditional( + iframeReference.contentDocument, + additionalReference, + currReferenceIndex + 1, + errorReferences.length, + offset => { + switchError(offset); + } + ); + } else { + if (errorReferences.length !== 1) { + throw new Error('Something is *really* wrong.'); + } + renderErrorByIndex((currReferenceIndex = 0)); + } + }) + .catch(e => { + console.log('Could not consume error:', e); + }); +} + +function shortcutHandler(type: string) { + switch (type) { + case SHORTCUT_ESCAPE: { + unmount(); + break; + } + case SHORTCUT_LEFT: { + switchError(-1); + break; + } + case SHORTCUT_RIGHT: { + switchError(1); + break; + } + } +} + +function inject() { + registerError(window, error => crash(error)); + registerPromise(window, error => crash(error, true)); + registerShortcuts(window, shortcutHandler); + registerStackTraceLimit(); +} + +function uninject() { + unregisterStackTraceLimit(); + unregisterShortcuts(window); + unregisterPromise(window); + unregisterError(window); +} + +export { inject, uninject }; diff --git a/packages/stack-frame-overlay/src/styles.js b/packages/stack-frame-overlay/src/styles.js new file mode 100644 index 00000000000..75ff36f1441 --- /dev/null +++ b/packages/stack-frame-overlay/src/styles.js @@ -0,0 +1,186 @@ +/* @flow */ +const black = '#293238', + darkGray = '#878e91', + lightGray = '#fafafa', + red = '#ce1126', + lightRed = '#fccfcf', + yellow = '#fbf5b4'; + +const iframeStyle = { + 'background-color': lightGray, + position: 'fixed', + top: '1em', + left: '1em', + bottom: '1em', + right: '1em', + width: 'calc(100% - 2em)', + height: 'calc(100% - 2em)', + border: 'none', + 'border-radius': '3px', + 'box-shadow': '0 0 6px 0 rgba(0, 0, 0, 0.5)', + 'z-index': 1337, +}; + +const overlayStyle = { + 'box-sizing': 'border-box', + padding: '4rem', + 'font-family': 'Consolas, Menlo, monospace', + color: black, + 'white-space': 'pre-wrap', + overflow: 'auto', + 'overflow-x': 'hidden', + 'word-break': 'break-word', + 'line-height': 1.5, +}; + +const hintsStyle = { + 'font-size': '0.8em', + 'margin-top': '-3em', + 'margin-bottom': '3em', + 'text-align': 'right', + color: darkGray, +}; + +const hintStyle = { + padding: '0.5em 1em', + cursor: 'pointer', +}; + +const closeButtonStyle = { + 'font-size': '26px', + color: black, + padding: '0.5em 1em', + cursor: 'pointer', + position: 'absolute', + right: 0, + top: 0, +}; + +const additionalStyle = { + 'margin-bottom': '1.5em', + 'margin-top': '-4em', +}; + +const headerStyle = { + 'font-size': '1.7em', + 'font-weight': 'bold', + color: red, +}; + +const functionNameStyle = { + 'margin-top': '1em', + 'font-size': '1.2em', +}; + +const linkStyle = { + 'font-size': '0.9em', +}; + +const anchorStyle = { + 'text-decoration': 'none', + color: darkGray, +}; + +const traceStyle = { + 'font-size': '1em', +}; + +const depStyle = { + 'font-size': '1.2em', +}; + +const primaryErrorStyle = { + 'background-color': lightRed, +}; + +const secondaryErrorStyle = { + 'background-color': yellow, +}; + +const omittedFramesStyle = { + color: black, + 'font-size': '0.9em', + margin: '1.5em 0', + cursor: 'pointer', +}; + +const preStyle = { + display: 'block', + padding: '0.5em', + 'margin-top': '1.5em', + 'margin-bottom': '0px', + 'overflow-x': 'auto', + 'font-size': '1.1em', + 'white-space': 'pre', +}; + +const toggleStyle = { + 'margin-bottom': '1.5em', + color: darkGray, + cursor: 'pointer', +}; + +const codeStyle = { + 'font-family': 'Consolas, Menlo, monospace', +}; + +const hiddenStyle = { + display: 'none', +}; + +const groupStyle = { + 'margin-left': '1em', +}; + +const _groupElemStyle = { + 'background-color': 'inherit', + 'border-color': '#ddd', + 'border-width': '1px', + 'border-radius': '4px', + 'border-style': 'solid', + padding: '3px 6px', + cursor: 'pointer', +}; + +const groupElemLeft = Object.assign({}, _groupElemStyle, { + 'border-top-right-radius': '0px', + 'border-bottom-right-radius': '0px', + 'margin-right': '0px', +}); + +const groupElemRight = Object.assign({}, _groupElemStyle, { + 'border-top-left-radius': '0px', + 'border-bottom-left-radius': '0px', + 'margin-left': '-1px', +}); + +const footerStyle = { + 'text-align': 'center', + color: darkGray, +}; + +export { + iframeStyle, + overlayStyle, + hintsStyle, + hintStyle, + closeButtonStyle, + additionalStyle, + headerStyle, + functionNameStyle, + linkStyle, + anchorStyle, + traceStyle, + depStyle, + primaryErrorStyle, + secondaryErrorStyle, + omittedFramesStyle, + preStyle, + toggleStyle, + codeStyle, + hiddenStyle, + groupStyle, + groupElemLeft, + groupElemRight, + footerStyle, +}; diff --git a/packages/stack-frame-overlay/src/utils/dom/absolutifyCaret.js b/packages/stack-frame-overlay/src/utils/dom/absolutifyCaret.js new file mode 100644 index 00000000000..2adb472d04c --- /dev/null +++ b/packages/stack-frame-overlay/src/utils/dom/absolutifyCaret.js @@ -0,0 +1,34 @@ +/* @flow */ +function removeNextBr(parent, component: ?Element) { + while (component != null && component.tagName.toLowerCase() !== 'br') { + component = component.nextElementSibling; + } + if (component != null) { + parent.removeChild(component); + } +} + +function absolutifyCaret(component: Node) { + const ccn = component.childNodes; + for (let index = 0; index < ccn.length; ++index) { + const c = ccn[index]; + // $FlowFixMe + if (c.tagName.toLowerCase() !== 'span') { + continue; + } + const _text = c.innerText; + if (_text == null) { + continue; + } + const text = _text.replace(/\s/g, ''); + if (text !== '|^') { + continue; + } + // $FlowFixMe + c.style.position = 'absolute'; + // $FlowFixMe + removeNextBr(component, c); + } +} + +export { absolutifyCaret }; diff --git a/packages/stack-frame-overlay/src/utils/dom/consumeEvent.js b/packages/stack-frame-overlay/src/utils/dom/consumeEvent.js new file mode 100644 index 00000000000..70198d2acf1 --- /dev/null +++ b/packages/stack-frame-overlay/src/utils/dom/consumeEvent.js @@ -0,0 +1,9 @@ +/* @flow */ +function consumeEvent(e: Event) { + e.preventDefault(); + if (typeof e.target.blur === 'function') { + e.target.blur(); + } +} + +export { consumeEvent }; diff --git a/packages/stack-frame-overlay/src/utils/dom/css.js b/packages/stack-frame-overlay/src/utils/dom/css.js new file mode 100644 index 00000000000..558a068575a --- /dev/null +++ b/packages/stack-frame-overlay/src/utils/dom/css.js @@ -0,0 +1,40 @@ +/* @flow */ +let injectedCount = 0; +const injectedCache = {}; + +function getHead(document: Document) { + return document.head || document.getElementsByTagName('head')[0]; +} + +function injectCss(document: Document, css: string): number { + const head = getHead(document); + const style = document.createElement('style'); + style.type = 'text/css'; + style.appendChild(document.createTextNode(css)); + head.appendChild(style); + + injectedCache[++injectedCount] = style; + return injectedCount; +} + +function removeCss(document: Document, ref: number) { + if (injectedCache[ref] == null) { + return; + } + const head = getHead(document); + head.removeChild(injectedCache[ref]); + delete injectedCache[ref]; +} + +function applyStyles(element: HTMLElement, styles: Object) { + element.setAttribute('style', ''); + for (const key in styles) { + if (!styles.hasOwnProperty(key)) { + continue; + } + // $FlowFixMe + element.style[key] = styles[key]; + } +} + +export { getHead, injectCss, removeCss, applyStyles }; diff --git a/packages/stack-frame-overlay/src/utils/dom/enableTabClick.js b/packages/stack-frame-overlay/src/utils/dom/enableTabClick.js new file mode 100644 index 00000000000..156b06c2ead --- /dev/null +++ b/packages/stack-frame-overlay/src/utils/dom/enableTabClick.js @@ -0,0 +1,15 @@ +/* @flow */ +function enableTabClick(node: Element) { + node.setAttribute('tabindex', '0'); + node.addEventListener('keydown', function(e: KeyboardEvent) { + const { key, which, keyCode } = e; + if (key === 'Enter' || which === 13 || keyCode === 13) { + e.preventDefault(); + if (typeof e.target.click === 'function') { + e.target.click(); + } + } + }); +} + +export { enableTabClick }; diff --git a/packages/stack-frame-overlay/src/utils/errorRegister.js b/packages/stack-frame-overlay/src/utils/errorRegister.js new file mode 100644 index 00000000000..543e0dd58b9 --- /dev/null +++ b/packages/stack-frame-overlay/src/utils/errorRegister.js @@ -0,0 +1,63 @@ +/* @flow */ +import type { StackFrame } from 'stack-frame'; +import { parse } from 'stack-frame-parser'; +import { map } from 'stack-frame-mapper'; +import { unmap } from 'stack-frame-unmapper'; + +type ErrorRecord = { + error: Error, + unhandledRejection: boolean, + contextSize: number, + enhancedFrames: StackFrame[], +}; +type ErrorRecordReference = number; +const recorded: ErrorRecord[] = []; + +let errorsConsumed: ErrorRecordReference = 0; + +function consume( + error: Error, + unhandledRejection: boolean = false, + contextSize: number = 3 +): Promise { + const parsedFrames = parse(error); + let enhancedFramesPromise; + if (error.__unmap_source) { + enhancedFramesPromise = unmap( + error.__unmap_source, + parsedFrames, + contextSize + ); + } else { + enhancedFramesPromise = map(parsedFrames, contextSize); + } + return enhancedFramesPromise.then(enhancedFrames => { + enhancedFrames = enhancedFrames.filter( + ({ functionName }) => + functionName == null || + functionName.indexOf('__stack_frame_overlay_proxy_console__') === -1 + ); + recorded[++errorsConsumed] = { + error, + unhandledRejection, + contextSize, + enhancedFrames, + }; + return errorsConsumed; + }); +} + +function getErrorRecord(ref: ErrorRecordReference): ErrorRecord { + return recorded[ref]; +} + +function drain() { + // $FlowFixMe + const keys = Object.keys(recorded); + for (let index = 0; index < keys.length; ++index) { + delete recorded[keys[index]]; + } +} + +export { consume, getErrorRecord, drain }; +export type { ErrorRecordReference }; diff --git a/packages/stack-frame-overlay/src/utils/isInternalFile.js b/packages/stack-frame-overlay/src/utils/isInternalFile.js new file mode 100644 index 00000000000..f55123e258c --- /dev/null +++ b/packages/stack-frame-overlay/src/utils/isInternalFile.js @@ -0,0 +1,10 @@ +/* @flow */ +function isInternalFile(url: string, sourceFileName: string | null | void) { + return url.indexOf('/~/') !== -1 || + url.indexOf('/node_modules/') !== -1 || + url.trim().indexOf(' ') !== -1 || + sourceFileName == null || + sourceFileName.length === 0; +} + +export { isInternalFile }; From 2790d0023226898143eed8a3dddf615310a7844f Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 12:40:02 -0400 Subject: [PATCH 02/18] Fix linting --- packages/stack-frame-overlay/src/components/code.js | 2 ++ packages/stack-frame-overlay/src/overlay.js | 4 ++++ tasks/e2e-simple.sh | 9 ++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/stack-frame-overlay/src/components/code.js b/packages/stack-frame-overlay/src/components/code.js index 2f74e40546e..4f7b178accb 100644 --- a/packages/stack-frame-overlay/src/components/code.js +++ b/packages/stack-frame-overlay/src/components/code.js @@ -61,6 +61,7 @@ function createCode( applyStyles(code, codeStyle); const ccn = code.childNodes; + // eslint-disable-next-line oLoop: for (let index = 0; index < ccn.length; ++index) { const node = ccn[index]; const ccn2 = node.childNodes; @@ -75,6 +76,7 @@ function createCode( } // $FlowFixMe applyStyles(node, main ? primaryErrorStyle : secondaryErrorStyle); + // eslint-disable-next-line break oLoop; } } diff --git a/packages/stack-frame-overlay/src/overlay.js b/packages/stack-frame-overlay/src/overlay.js index c9b4e64c8c5..cb96759df21 100644 --- a/packages/stack-frame-overlay/src/overlay.js +++ b/packages/stack-frame-overlay/src/overlay.js @@ -193,6 +193,10 @@ function shortcutHandler(type: string) { switchError(1); break; } + default: { + //TODO: this + break; + } } } diff --git a/tasks/e2e-simple.sh b/tasks/e2e-simple.sh index 14282e1ae09..6003f66cd21 100755 --- a/tasks/e2e-simple.sh +++ b/tasks/e2e-simple.sh @@ -98,7 +98,14 @@ then fi # Lint own code -./node_modules/.bin/eslint --max-warnings 0 . +./node_modules/.bin/eslint --max-warnings 0 packages/babel-preset-react-app/ +./node_modules/.bin/eslint --max-warnings 0 packages/create-react-app/ +./node_modules/.bin/eslint --max-warnings 0 packages/eslint-config-react-app/ +./node_modules/.bin/eslint --max-warnings 0 packages/react-dev-utils/ +./node_modules/.bin/eslint --max-warnings 0 packages/react-scripts/ +cd packages/stack-frame-overlay/ +./node_modules/.bin/eslint --max-warnings 0 src/ +cd ../.. # ****************************************************************************** # First, test the create-react-app development environment. From 211a36b96cc37f4d29fb09572bffd4edbec7439e Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 12:43:25 -0400 Subject: [PATCH 03/18] Remove auto overlay --- package.json | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 0991b82f03b..9ca7cfc03d9 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,13 @@ { "private": true, "scripts": { - "build:overlay": "cd packages/stack-frame-overlay/ && npm run build:prod", - "build:app": "node packages/react-scripts/scripts/build.js", - "build": "run-s build:overlay build:app", + "build": "node packages/react-scripts/scripts/build.js", "changelog": "lerna-changelog", "create-react-app": "tasks/cra.sh", "e2e": "tasks/e2e-simple.sh", "postinstall": "lerna bootstrap", "publish": "tasks/release.sh", - "start:overlay": "cd packages/stack-frame-overlay/ && npm start", - "start:app": "sleep 3 && node packages/react-scripts/scripts/start.js", - "start": "run-p start:overlay start:app", + "start": "node packages/react-scripts/scripts/start.js", "test": "node packages/react-scripts/scripts/test.js --env=jsdom", "format": "prettier --trailing-comma es5 --single-quote --write 'packages/*/*.js' 'packages/*/!(node_modules)/**/*.js'", "precommit": "lint-staged" @@ -22,7 +18,6 @@ "lerna": "2.0.0-beta.38", "lerna-changelog": "^0.2.3", "lint-staged": "^3.3.1", - "npm-run-all": "^4.0.2", "prettier": "^0.21.0" }, "lint-staged": { From a4a1bc33554746b6423a6a462852daf636d570c5 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 12:47:35 -0400 Subject: [PATCH 04/18] Fix e2e --- tasks/e2e-installs.sh | 4 ++++ tasks/e2e-kitchensink.sh | 4 ++++ tasks/e2e-simple.sh | 1 + 3 files changed, 9 insertions(+) diff --git a/tasks/e2e-installs.sh b/tasks/e2e-installs.sh index 89702463304..ef51ffe4642 100755 --- a/tasks/e2e-installs.sh +++ b/tasks/e2e-installs.sh @@ -99,6 +99,10 @@ fi # We removed the postinstall, so do it manually ./node_modules/.bin/lerna bootstrap --concurrency=1 +cd packages/stack-frame-overlay/ +npm run build:prod +cd ../.. + # ****************************************************************************** # First, pack and install create-react-app. # ****************************************************************************** diff --git a/tasks/e2e-kitchensink.sh b/tasks/e2e-kitchensink.sh index 2c2c1eb92c5..0279ea88baf 100755 --- a/tasks/e2e-kitchensink.sh +++ b/tasks/e2e-kitchensink.sh @@ -82,6 +82,10 @@ fi # We removed the postinstall, so do it manually ./node_modules/.bin/lerna bootstrap --concurrency=1 +cd packages/stack-frame-overlay/ +npm run build:prod +cd ../.. + # ****************************************************************************** # First, pack react-scripts and create-react-app so we can use them. # ****************************************************************************** diff --git a/tasks/e2e-simple.sh b/tasks/e2e-simple.sh index 6003f66cd21..a3697af9f5e 100755 --- a/tasks/e2e-simple.sh +++ b/tasks/e2e-simple.sh @@ -105,6 +105,7 @@ fi ./node_modules/.bin/eslint --max-warnings 0 packages/react-scripts/ cd packages/stack-frame-overlay/ ./node_modules/.bin/eslint --max-warnings 0 src/ +npm run build:prod cd ../.. # ****************************************************************************** From 1913cb61959e290a72a67a3c47b23bd0a2bd8f70 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 13:12:46 -0400 Subject: [PATCH 05/18] Pull in the rest --- packages/stack-frame-overlay/package.json | 27 +++- .../src/__tests__/setupJest.js | 0 .../src/utils/errorRegister.js | 7 +- .../src/utils/getLinesAround.js | 30 +++++ .../src/utils/getSourceMap.js | 118 ++++++++++++++++++ .../stack-frame-overlay/src/utils/mapper.js | 59 +++++++++ .../stack-frame-overlay/src/utils/parser.js | 78 ++++++++++++ .../src/utils/stack-frame.js | 101 +++++++++++++++ .../stack-frame-overlay/src/utils/unmapper.js | 97 ++++++++++++++ 9 files changed, 509 insertions(+), 8 deletions(-) create mode 100644 packages/stack-frame-overlay/src/__tests__/setupJest.js create mode 100644 packages/stack-frame-overlay/src/utils/getLinesAround.js create mode 100644 packages/stack-frame-overlay/src/utils/getSourceMap.js create mode 100644 packages/stack-frame-overlay/src/utils/mapper.js create mode 100644 packages/stack-frame-overlay/src/utils/parser.js create mode 100644 packages/stack-frame-overlay/src/utils/stack-frame.js create mode 100644 packages/stack-frame-overlay/src/utils/unmapper.js diff --git a/packages/stack-frame-overlay/package.json b/packages/stack-frame-overlay/package.json index fdbaad41640..2e06585dc38 100644 --- a/packages/stack-frame-overlay/package.json +++ b/packages/stack-frame-overlay/package.json @@ -6,7 +6,7 @@ "scripts": { "prepublishOnly": "npm run build:prod && npm test", "start": "NODE_ENV=development npm run build -- --watch", - "test": "exit 0", + "test": "jest", "build": "babel src/ -d lib/", "build:prod": "NODE_ENV=production babel src/ -d lib/" }, @@ -32,10 +32,8 @@ "dependencies": { "anser": "^1.2.5", "babel-code-frame": "^6.22.0", - "stack-frame": "0.4.0", - "stack-frame-mapper": "0.4.0", - "stack-frame-parser": "0.4.0", - "stack-frame-unmapper": "0.4.0" + "babel-runtime": "^6.23.0", + "settle-promise": "^1.0.0" }, "devDependencies": { "babel-cli": "^6.24.1", @@ -47,6 +45,25 @@ "eslint-plugin-import": "^2.0.1", "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.4.1", + "jest": "19.x", "react-dev-utils": "^0.5.2" + }, + "jest": { + "setupFiles": [ + "./src/__tests__/setupJest.js" + ], + "collectCoverage": true, + "coverageReporters": [ + "json" + ], + "testMatch": [ + "/src/**/__tests__/**/*.js?(x)", + "/src/**/?(*.)(spec|test).js?(x)" + ], + "testPathIgnorePatterns": [ + "/node_modules/", + "/fixtures/", + "setupJest.js" + ] } } diff --git a/packages/stack-frame-overlay/src/__tests__/setupJest.js b/packages/stack-frame-overlay/src/__tests__/setupJest.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/stack-frame-overlay/src/utils/errorRegister.js b/packages/stack-frame-overlay/src/utils/errorRegister.js index 543e0dd58b9..dae981b2f6b 100644 --- a/packages/stack-frame-overlay/src/utils/errorRegister.js +++ b/packages/stack-frame-overlay/src/utils/errorRegister.js @@ -1,8 +1,8 @@ /* @flow */ import type { StackFrame } from 'stack-frame'; -import { parse } from 'stack-frame-parser'; -import { map } from 'stack-frame-mapper'; -import { unmap } from 'stack-frame-unmapper'; +import { parse } from './parser'; +import { map } from './mapper'; +import { unmap } from './unmapper'; type ErrorRecord = { error: Error, @@ -24,6 +24,7 @@ function consume( let enhancedFramesPromise; if (error.__unmap_source) { enhancedFramesPromise = unmap( + // $FlowFixMe error.__unmap_source, parsedFrames, contextSize diff --git a/packages/stack-frame-overlay/src/utils/getLinesAround.js b/packages/stack-frame-overlay/src/utils/getLinesAround.js new file mode 100644 index 00000000000..42406e28d94 --- /dev/null +++ b/packages/stack-frame-overlay/src/utils/getLinesAround.js @@ -0,0 +1,30 @@ +//@flow +import { ScriptLine } from './stack-frame'; + +/** + * + * @param {number} line The line number to provide context around. + * @param {number} count The number of lines you'd like for context. + * @param {string[] | string} lines The source code. + */ +function getLinesAround( + line: number, + count: number, + lines: string[] | string +): ScriptLine[] { + if (typeof lines === 'string') { + lines = lines.split('\n'); + } + const result = []; + for ( + let index = Math.max(0, line - 1 - count); + index <= Math.min(lines.length - 1, line - 1 + count); + ++index + ) { + result.push(new ScriptLine(index + 1, lines[index], index === line - 1)); + } + return result; +} + +export { getLinesAround }; +export default getLinesAround; diff --git a/packages/stack-frame-overlay/src/utils/getSourceMap.js b/packages/stack-frame-overlay/src/utils/getSourceMap.js new file mode 100644 index 00000000000..1a48171452b --- /dev/null +++ b/packages/stack-frame-overlay/src/utils/getSourceMap.js @@ -0,0 +1,118 @@ +//@flow +import { SourceMapConsumer } from 'source-map'; + +/** + * A wrapped instance of a {@link https://github.com/mozilla/source-map SourceMapConsumer}. + * + * This exposes methods which will be indifferent to changes made in {@link https://github.com/mozilla/source-map source-map}. + */ +class SourceMap { + __source_map: SourceMapConsumer; + + constructor(sourceMap) { + this.__source_map = sourceMap; + } + + /** + * Returns the original code position for a generated code position. + * @param {number} line The line of the generated code position. + * @param {number} column The column of the generated code position. + */ + getOriginalPosition( + line: number, + column: number + ): { source: string, line: number, column: number } { + const { + line: l, + column: c, + source: s, + } = this.__source_map.originalPositionFor({ + line, + column, + }); + return { line: l, column: c, source: s }; + } + + /** + * Returns the generated code position for an original position. + * @param {string} source The source file of the original code position. + * @param {number} line The line of the original code position. + * @param {number} column The column of the original code position. + */ + getGeneratedPosition( + source: string, + line: number, + column: number + ): { line: number, column: number } { + const { line: l, column: c } = this.__source_map.generatedPositionFor({ + source, + line, + column, + }); + return { + line: l, + column: c, + }; + } + + /** + * Returns the code for a given source file name. + * @param {string} sourceName The name of the source file. + */ + getSource(sourceName: string): string { + return this.__source_map.sourceContentFor(sourceName); + } + + getSources(): string[] { + return this.__source_map.sources; + } +} + +function extractSourceMapUrl(fileUri: string, fileContents: string) { + const regex = /\/\/[#@] ?sourceMappingURL=([^\s'"]+)\s*$/gm; + let match = null; + for (;;) { + let next = regex.exec(fileContents); + if (next == null) { + break; + } + match = next; + } + if (!(match && match[1])) { + return Promise.reject(`Cannot find a source map directive for ${fileUri}.`); + } + return Promise.resolve(match[1].toString()); +} + +/** + * Returns an instance of {@link SourceMap} for a given fileUri and fileContents. + * @param {string} fileUri The URI of the source file. + * @param {string} fileContents The contents of the source file. + */ +async function getSourceMap( + fileUri: string, + fileContents: string +): Promise { + let sm = await extractSourceMapUrl(fileUri, fileContents); + if (sm.indexOf('data:') === 0) { + const base64 = /^data:application\/json;([\w=:"-]+;)*base64,/; + const match2 = sm.match(base64); + if (!match2) { + throw new Error( + 'Sorry, non-base64 inline source-map encoding is not supported.' + ); + } + sm = sm.substring(match2[0].length); + sm = window.atob(sm); + sm = JSON.parse(sm); + return new SourceMap(new SourceMapConsumer(sm)); + } else { + const index = fileUri.lastIndexOf('/'); + const url = fileUri.substring(0, index + 1) + sm; + const obj = await fetch(url).then(res => res.json()); + return new SourceMap(new SourceMapConsumer(obj)); + } +} + +export { extractSourceMapUrl, getSourceMap }; +export default getSourceMap; diff --git a/packages/stack-frame-overlay/src/utils/mapper.js b/packages/stack-frame-overlay/src/utils/mapper.js new file mode 100644 index 00000000000..39b5499d2db --- /dev/null +++ b/packages/stack-frame-overlay/src/utils/mapper.js @@ -0,0 +1,59 @@ +// @flow +import StackFrame from './stack-frame'; +import { getSourceMap } from './getSourceMap'; +import { getLinesAround } from './getLinesAround'; +import { settle } from 'settle-promise'; + +/** + * Enhances a set of {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s with their original positions and code (when available). + * @param {StackFrame[]} frames A set of {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s which contain (generated) code positions. + * @param {number} [contextLines=3] The number of lines to provide before and after the line specified in the {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}. + */ +async function map( + frames: StackFrame[], + contextLines: number = 3 +): Promise { + const cache = {}; + const files = []; + frames.forEach(frame => { + const { fileName } = frame; + if (files.indexOf(fileName) !== -1) { + return; + } + files.push(fileName); + }); + await settle( + files.map(async fileName => { + const fileSource = await fetch(fileName).then(r => r.text()); + const map = await getSourceMap(fileName, fileSource); + cache[fileName] = { fileSource, map }; + }) + ); + return frames.map(frame => { + const { functionName, fileName, lineNumber, columnNumber } = frame; + let { map, fileSource } = cache[fileName] || {}; + if (map == null) { + return frame; + } + const { source, line, column } = map.getOriginalPosition( + lineNumber, + columnNumber + ); + const originalSource = source == null ? [] : map.getSource(source); + return new StackFrame( + functionName, + fileName, + lineNumber, + columnNumber, + getLinesAround(lineNumber, contextLines, fileSource), + functionName, + source, + line, + column, + getLinesAround(line, contextLines, originalSource) + ); + }); +} + +export { map }; +export default map; diff --git a/packages/stack-frame-overlay/src/utils/parser.js b/packages/stack-frame-overlay/src/utils/parser.js new file mode 100644 index 00000000000..884b53d54dd --- /dev/null +++ b/packages/stack-frame-overlay/src/utils/parser.js @@ -0,0 +1,78 @@ +// @flow +import StackFrame from './stack-frame'; + +const regexExtractLocation = /\(?(.+?)(?::(\d+))?(?::(\d+))?\)?$/; + +function extractLocation(token: string): [string, number, number] { + return regexExtractLocation.exec(token).slice(1).map(v => { + const p = Number(v); + if (!isNaN(p)) { + return p; + } + return v; + }); +} + +const regexValidFrame_Chrome = /^\s*(at|in)\s.+(:\d+)/; +const regexValidFrame_FireFox = /(^|@)\S+:\d+|.+line\s+\d+\s+>\s+(eval|Function).+/; + +function parseStack(stack: string[]): StackFrame[] { + const frames = stack + .filter( + e => regexValidFrame_Chrome.test(e) || regexValidFrame_FireFox.test(e) + ) + .map(e => { + if (regexValidFrame_FireFox.test(e)) { + // Strip eval, we don't care about it + let isEval = false; + if (/ > (eval|Function)/.test(e)) { + e = e.replace( + / line (\d+)(?: > eval line \d+)* > (eval|Function):\d+:\d+/g, + ':$1' + ); + isEval = true; + } + const data = e.split(/[@]/g); + const last = data.pop(); + return new StackFrame( + data.join('@') || (isEval ? 'eval' : null), + ...extractLocation(last) + ); + } else { + // Strip eval, we don't care about it + if (e.indexOf('(eval ') !== -1) { + e = e.replace(/(\(eval at [^()]*)|(\),.*$)/g, ''); + } + if (e.indexOf('(at ') !== -1) { + e = e.replace(/\(at /, '('); + } + const data = e.trim().split(/\s+/g).slice(1); + const last = data.pop(); + return new StackFrame(data.join(' ') || null, ...extractLocation(last)); + } + }); + return frames; +} + +/** + * Turns an Error, or similar object, into a set of {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s. + * @alias parse + */ +function parseError(error: Error | string | string[]): StackFrame[] { + if (error == null) { + throw new Error('You cannot pass a null object.'); + } + if (typeof error === 'string') { + return parseStack(error.split('\n')); + } + if (Array.isArray(error)) { + return parseStack(error); + } + if (typeof error.stack === 'string') { + return parseStack(error.stack.split('\n')); + } + throw new Error('The error you provided does not contain a stack trace.'); +} + +export { parseError as parse }; +export default parseError; diff --git a/packages/stack-frame-overlay/src/utils/stack-frame.js b/packages/stack-frame-overlay/src/utils/stack-frame.js new file mode 100644 index 00000000000..f928cf9c8f6 --- /dev/null +++ b/packages/stack-frame-overlay/src/utils/stack-frame.js @@ -0,0 +1,101 @@ +//@flow + +/** A container holding a script line. */ +class ScriptLine { + /** The line number of this line of source. */ + lineNumber: number; + /** The content (or value) of this line of source. */ + content: string; + /** Whether or not this line should be highlighted. Particularly useful for error reporting with context. */ + highlight: boolean; + + constructor(lineNumber: number, content: string, highlight: boolean = false) { + this.lineNumber = lineNumber; + this.content = content; + this.highlight = highlight; + } +} + +/** + * A representation of a stack frame. + */ +class StackFrame { + functionName: string | null; + fileName: string | null; + lineNumber: number | null; + columnNumber: number | null; + + _originalFunctionName: string | null; + _originalFileName: string | null; + _originalLineNumber: number | null; + _originalColumnNumber: number | null; + + _scriptCode: ScriptLine[] | null; + _originalScriptCode: ScriptLine[] | null; + + constructor( + functionName: string | null = null, + fileName: string | null = null, + lineNumber: number | null = null, + columnNumber: number | null = null, + scriptCode: ScriptLine[] | null = null, + sourceFunctionName: string | null = null, + sourceFileName: string | null = null, + sourceLineNumber: number | null = null, + sourceColumnNumber: number | null = null, + sourceScriptCode: ScriptLine[] | null = null + ) { + this.functionName = functionName; + + this.fileName = fileName; + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; + + this._originalFunctionName = sourceFunctionName; + this._originalFileName = sourceFileName; + this._originalLineNumber = sourceLineNumber; + this._originalColumnNumber = sourceColumnNumber; + + this._scriptCode = scriptCode; + this._originalScriptCode = sourceScriptCode; + } + + /** + * Returns the name of this function. + */ + getFunctionName(): string | null { + return this.functionName; + } + + /** + * Returns the source of the frame. + * This contains the file name, line number, and column number when available. + */ + getSource(): string { + let str = ''; + if (this.fileName != null) { + str += this.fileName + ':'; + } + if (this.lineNumber != null) { + str += this.lineNumber + ':'; + } + if (this.columnNumber != null) { + str += this.columnNumber + ':'; + } + return str.slice(0, -1); + } + + /** + * Returns a pretty version of this stack frame. + */ + toString(): string { + const f = this.getFunctionName(); + if (f == null) { + return this.getSource(); + } + return `${f} (${this.getSource()})`; + } +} + +export { StackFrame, ScriptLine }; +export default StackFrame; diff --git a/packages/stack-frame-overlay/src/utils/unmapper.js b/packages/stack-frame-overlay/src/utils/unmapper.js new file mode 100644 index 00000000000..5f15706747d --- /dev/null +++ b/packages/stack-frame-overlay/src/utils/unmapper.js @@ -0,0 +1,97 @@ +// @flow +import StackFrame from './stack-frame'; +import { getSourceMap } from './getSourceMap'; +import { getLinesAround } from './getLinesAround'; +import path from 'path'; + +/** + * Turns a set of mapped {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s back into their generated code position and enhances them with code. + * @param {string} fileUri The URI of the bundle.js file. + * @param {StackFrame[]} frames A set of {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s which are already mapped and missing their generated positions. + * @param {number} [fileContents=3] The number of lines to provide before and after the line specified in the {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}. + */ +async function unmap( + fileUri: string | { uri: string, contents: string }, + frames: StackFrame[], + contextLines: number = 3 +): Promise { + let fileContents = typeof fileUri === 'object' ? fileUri.contents : null; + fileUri = typeof fileUri === 'object' ? fileUri.uri : fileUri; + if (fileContents == null) { + fileContents = await fetch(fileUri).then(res => res.text()); + } + const map = await getSourceMap(fileUri, fileContents); + return frames.map(frame => { + const { + functionName, + lineNumber, + columnNumber, + _originalLineNumber, + } = frame; + if (_originalLineNumber != null) { + return frame; + } + let { fileName } = frame; + if (fileName) { + fileName = path.normalize(fileName); + } + const splitCache1 = {}, splitCache2 = {}, splitCache3 = {}; + const source = map + .getSources() + .map(s => s.replace(/[\\]+/g, '/')) + .filter(s => { + s = path.normalize(s); + return s.indexOf(fileName) === s.length - fileName.length; + }) + .sort((a, b) => { + a = splitCache1[a] || (splitCache1[a] = a.split(path.sep)); + b = splitCache1[b] || (splitCache1[b] = b.split(path.sep)); + return Math.sign(a.length - b.length); + }) + .sort((a, b) => { + a = splitCache2[a] || (splitCache2[a] = a.split('node_modules')); + b = splitCache2[b] || (splitCache2[b] = b.split('node_modules')); + return Math.sign(a.length - b.length); + }) + .sort((a, b) => { + a = splitCache3[a] || (splitCache3[a] = a.split('~')); + b = splitCache3[b] || (splitCache3[b] = b.split('~')); + return Math.sign(a.length - b.length); + }); + if (source.length < 1) { + return new StackFrame( + null, + null, + null, + null, + null, + functionName, + fileName, + lineNumber, + columnNumber, + null + ); + } + const { line, column } = map.getGeneratedPosition( + source[0], + lineNumber, + columnNumber + ); + const originalSource = map.getSource(source[0]); + return new StackFrame( + functionName, + fileUri, + line, + column || null, + getLinesAround(line, contextLines, fileContents), + functionName, + fileName, + lineNumber, + columnNumber, + getLinesAround(lineNumber, contextLines, originalSource) + ); + }); +} + +export { unmap }; +export default unmap; From 42054067a29dba269f2ffaca1d1d7291b0d5fd09 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 13:30:54 -0400 Subject: [PATCH 06/18] Appease flow --- .../src/components/code.js | 8 ++-- .../src/components/frame.js | 19 ++++++--- .../src/components/frames.js | 2 +- .../src/components/overlay.js | 2 +- packages/stack-frame-overlay/src/overlay.js | 2 +- .../src/utils/errorRegister.js | 2 +- .../stack-frame-overlay/src/utils/mapper.js | 7 ++-- .../stack-frame-overlay/src/utils/unmapper.js | 41 +++++++++++-------- 8 files changed, 49 insertions(+), 34 deletions(-) diff --git a/packages/stack-frame-overlay/src/components/code.js b/packages/stack-frame-overlay/src/components/code.js index 4f7b178accb..f25ed83780c 100644 --- a/packages/stack-frame-overlay/src/components/code.js +++ b/packages/stack-frame-overlay/src/components/code.js @@ -1,5 +1,5 @@ /* @flow */ -import type { ScriptLines } from 'stack-frame'; +import type { ScriptLine } from '../utils/stack-frame'; import { applyStyles } from '../utils/dom/css'; import { absolutifyCaret } from '../utils/dom/absolutifyCaret'; import { @@ -15,9 +15,9 @@ import codeFrame from 'babel-code-frame'; function createCode( document: Document, - sourceLines: ScriptLines[], + sourceLines: ScriptLine[], lineNum: number, - columnNum: number, + columnNum: number | null, contextSize: number, main: boolean = false ) { @@ -47,7 +47,7 @@ function createCode( const ansiHighlight = codeFrame( sourceCode.join('\n'), lineNum, - columnNum - (isFinite(whiteSpace) ? whiteSpace : 0), + columnNum == null ? 0 : columnNum - (isFinite(whiteSpace) ? whiteSpace : 0), { forceColor: true, linesAbove: contextSize, diff --git a/packages/stack-frame-overlay/src/components/frame.js b/packages/stack-frame-overlay/src/components/frame.js index 94fea29adf1..a3f845cf0b9 100644 --- a/packages/stack-frame-overlay/src/components/frame.js +++ b/packages/stack-frame-overlay/src/components/frame.js @@ -2,7 +2,7 @@ import { enableTabClick } from '../utils/dom/enableTabClick'; import { createCode } from './code'; import { isInternalFile } from '../utils/isInternalFile'; -import type { StackFrame } from 'stack-frame'; +import type { StackFrame } from '../utils/stack-frame'; import type { FrameSetting, OmitsObject } from './frames'; import { applyStyles } from '../utils/dom/css'; import { @@ -140,16 +140,18 @@ function createFrame( } = frame; let url; - if (!compiled && sourceFileName) { + if (!compiled && sourceFileName && sourceLineNumber) { url = sourceFileName + ':' + sourceLineNumber; if (sourceColumnNumber) { url += ':' + sourceColumnNumber; } - } else { + } else if (fileName && lineNumber) { url = fileName + ':' + lineNumber; if (columnNumber) { url += ':' + columnNumber; } + } else { + url = 'unknown'; } let needsHidden = false; @@ -190,7 +192,9 @@ function createFrame( let hasSource = false; if (!internalUrl) { - if (compiled && scriptLines.length !== 0) { + if ( + compiled && scriptLines && scriptLines.length !== 0 && lineNumber != null + ) { elem.appendChild( createCode( document, @@ -202,7 +206,12 @@ function createFrame( ) ); hasSource = true; - } else if (!compiled && sourceLines.length !== 0) { + } else if ( + !compiled && + sourceLines && + sourceLines.length !== 0 && + sourceLineNumber != null + ) { elem.appendChild( createCode( document, diff --git a/packages/stack-frame-overlay/src/components/frames.js b/packages/stack-frame-overlay/src/components/frames.js index 9300de30173..ba3b7effdf8 100644 --- a/packages/stack-frame-overlay/src/components/frames.js +++ b/packages/stack-frame-overlay/src/components/frames.js @@ -1,5 +1,5 @@ /* @flow */ -import type { StackFrame } from 'stack-frame'; +import type { StackFrame } from '../utils/stack-frame'; import { applyStyles } from '../utils/dom/css'; import { traceStyle, toggleStyle } from '../styles'; import { enableTabClick } from '../utils/dom/enableTabClick'; diff --git a/packages/stack-frame-overlay/src/components/overlay.js b/packages/stack-frame-overlay/src/components/overlay.js index 3a537de3e06..58d6705ec8b 100644 --- a/packages/stack-frame-overlay/src/components/overlay.js +++ b/packages/stack-frame-overlay/src/components/overlay.js @@ -5,7 +5,7 @@ import { createClose } from './close'; import { createFrames } from './frames'; import { createFooter } from './footer'; import type { CloseCallback } from './close'; -import type { StackFrame } from 'stack-frame'; +import type { StackFrame } from '../utils/stack-frame'; import { updateAdditional } from './additional'; import type { FrameSetting } from './frames'; import type { SwitchCallback } from './additional'; diff --git a/packages/stack-frame-overlay/src/overlay.js b/packages/stack-frame-overlay/src/overlay.js index cb96759df21..63c919409c2 100644 --- a/packages/stack-frame-overlay/src/overlay.js +++ b/packages/stack-frame-overlay/src/overlay.js @@ -27,7 +27,7 @@ import { } from './utils/errorRegister'; import type { ErrorRecordReference } from './utils/errorRegister'; -import type { StackFrame } from 'stack-frame'; +import type { StackFrame } from './utils/stack-frame'; import { iframeStyle } from './styles'; import { injectCss, applyStyles } from './utils/dom/css'; import { createOverlay } from './components/overlay'; diff --git a/packages/stack-frame-overlay/src/utils/errorRegister.js b/packages/stack-frame-overlay/src/utils/errorRegister.js index dae981b2f6b..f14ff9d923e 100644 --- a/packages/stack-frame-overlay/src/utils/errorRegister.js +++ b/packages/stack-frame-overlay/src/utils/errorRegister.js @@ -1,5 +1,5 @@ /* @flow */ -import type { StackFrame } from 'stack-frame'; +import type { StackFrame } from './stack-frame'; import { parse } from './parser'; import { map } from './mapper'; import { unmap } from './unmapper'; diff --git a/packages/stack-frame-overlay/src/utils/mapper.js b/packages/stack-frame-overlay/src/utils/mapper.js index 39b5499d2db..86f6e83dc6a 100644 --- a/packages/stack-frame-overlay/src/utils/mapper.js +++ b/packages/stack-frame-overlay/src/utils/mapper.js @@ -13,10 +13,11 @@ async function map( frames: StackFrame[], contextLines: number = 3 ): Promise { - const cache = {}; - const files = []; + const cache: any = {}; + const files: string[] = []; frames.forEach(frame => { const { fileName } = frame; + if (fileName == null) return; if (files.indexOf(fileName) !== -1) { return; } @@ -32,7 +33,7 @@ async function map( return frames.map(frame => { const { functionName, fileName, lineNumber, columnNumber } = frame; let { map, fileSource } = cache[fileName] || {}; - if (map == null) { + if (map == null || lineNumber == null) { return frame; } const { source, line, column } = map.getOriginalPosition( diff --git a/packages/stack-frame-overlay/src/utils/unmapper.js b/packages/stack-frame-overlay/src/utils/unmapper.js index 5f15706747d..297e58e8fb6 100644 --- a/packages/stack-frame-overlay/src/utils/unmapper.js +++ b/packages/stack-frame-overlay/src/utils/unmapper.js @@ -11,12 +11,12 @@ import path from 'path'; * @param {number} [fileContents=3] The number of lines to provide before and after the line specified in the {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}. */ async function unmap( - fileUri: string | { uri: string, contents: string }, + _fileUri: string | { uri: string, contents: string }, frames: StackFrame[], contextLines: number = 3 ): Promise { - let fileContents = typeof fileUri === 'object' ? fileUri.contents : null; - fileUri = typeof fileUri === 'object' ? fileUri.uri : fileUri; + let fileContents = typeof _fileUri === 'object' ? _fileUri.contents : null; + let fileUri = typeof _fileUri === 'object' ? _fileUri.uri : _fileUri; if (fileContents == null) { fileContents = await fetch(fileUri).then(res => res.text()); } @@ -35,30 +35,34 @@ async function unmap( if (fileName) { fileName = path.normalize(fileName); } - const splitCache1 = {}, splitCache2 = {}, splitCache3 = {}; + if (fileName == null) { + 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(fileName) === s.length - fileName.length; + return s.indexOf(fN) === s.length - fN.length; }) .sort((a, b) => { - a = splitCache1[a] || (splitCache1[a] = a.split(path.sep)); - b = splitCache1[b] || (splitCache1[b] = b.split(path.sep)); - return Math.sign(a.length - b.length); + 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) => { - a = splitCache2[a] || (splitCache2[a] = a.split('node_modules')); - b = splitCache2[b] || (splitCache2[b] = b.split('node_modules')); - return Math.sign(a.length - b.length); + 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); }) .sort((a, b) => { - a = splitCache3[a] || (splitCache3[a] = a.split('~')); - b = splitCache3[b] || (splitCache3[b] = b.split('~')); - return Math.sign(a.length - b.length); + let a2 = splitCache3[a] || (splitCache3[a] = a.split('~')), + b2 = splitCache3[b] || (splitCache3[b] = b.split('~')); + return Math.sign(a2.length - b2.length); }); - if (source.length < 1) { + if (source.length < 1 || lineNumber == null) { return new StackFrame( null, null, @@ -66,7 +70,7 @@ async function unmap( null, null, functionName, - fileName, + fN, lineNumber, columnNumber, null @@ -75,6 +79,7 @@ async function unmap( const { line, column } = map.getGeneratedPosition( source[0], lineNumber, + // $FlowFixMe columnNumber ); const originalSource = map.getSource(source[0]); @@ -83,9 +88,9 @@ async function unmap( fileUri, line, column || null, - getLinesAround(line, contextLines, fileContents), + getLinesAround(line, contextLines, fileContents || []), functionName, - fileName, + fN, lineNumber, columnNumber, getLinesAround(lineNumber, contextLines, originalSource) From 9d260fd4d20f3bce3c741c302b7105c1080053fb Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 13:33:18 -0400 Subject: [PATCH 07/18] Correct dep --- packages/stack-frame-overlay/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stack-frame-overlay/package.json b/packages/stack-frame-overlay/package.json index 2e06585dc38..a069946d3f9 100644 --- a/packages/stack-frame-overlay/package.json +++ b/packages/stack-frame-overlay/package.json @@ -33,6 +33,7 @@ "anser": "^1.2.5", "babel-code-frame": "^6.22.0", "babel-runtime": "^6.23.0", + "react-dev-utils": "^0.5.2", "settle-promise": "^1.0.0" }, "devDependencies": { @@ -45,8 +46,7 @@ "eslint-plugin-import": "^2.0.1", "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.4.1", - "jest": "19.x", - "react-dev-utils": "^0.5.2" + "jest": "19.x" }, "jest": { "setupFiles": [ From d9762800a5f540dbf6cd19395a5ea83d71285465 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 13:36:14 -0400 Subject: [PATCH 08/18] Remove old repo references --- packages/stack-frame-overlay/src/utils/mapper.js | 6 +++--- packages/stack-frame-overlay/src/utils/parser.js | 2 +- packages/stack-frame-overlay/src/utils/unmapper.js | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/stack-frame-overlay/src/utils/mapper.js b/packages/stack-frame-overlay/src/utils/mapper.js index 86f6e83dc6a..17a6bd1c439 100644 --- a/packages/stack-frame-overlay/src/utils/mapper.js +++ b/packages/stack-frame-overlay/src/utils/mapper.js @@ -5,9 +5,9 @@ import { getLinesAround } from './getLinesAround'; import { settle } from 'settle-promise'; /** - * Enhances a set of {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s with their original positions and code (when available). - * @param {StackFrame[]} frames A set of {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s which contain (generated) code positions. - * @param {number} [contextLines=3] The number of lines to provide before and after the line specified in the {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}. + * Enhances a set of StackFrames with their original positions and code (when available). + * @param {StackFrame[]} frames A set of StackFrames which contain (generated) code positions. + * @param {number} [contextLines=3] The number of lines to provide before and after the line specified in the StackFrame. */ async function map( frames: StackFrame[], diff --git a/packages/stack-frame-overlay/src/utils/parser.js b/packages/stack-frame-overlay/src/utils/parser.js index 884b53d54dd..442e6578ce6 100644 --- a/packages/stack-frame-overlay/src/utils/parser.js +++ b/packages/stack-frame-overlay/src/utils/parser.js @@ -55,7 +55,7 @@ function parseStack(stack: string[]): StackFrame[] { } /** - * Turns an Error, or similar object, into a set of {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s. + * Turns an Error, or similar object, into a set of StackFrames. * @alias parse */ function parseError(error: Error | string | string[]): StackFrame[] { diff --git a/packages/stack-frame-overlay/src/utils/unmapper.js b/packages/stack-frame-overlay/src/utils/unmapper.js index 297e58e8fb6..415d18dc1a0 100644 --- a/packages/stack-frame-overlay/src/utils/unmapper.js +++ b/packages/stack-frame-overlay/src/utils/unmapper.js @@ -5,10 +5,10 @@ import { getLinesAround } from './getLinesAround'; import path from 'path'; /** - * Turns a set of mapped {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s back into their generated code position and enhances them with code. + * 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. - * @param {StackFrame[]} frames A set of {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s which are already mapped and missing their generated positions. - * @param {number} [fileContents=3] The number of lines to provide before and after the line specified in the {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}. + * @param {StackFrame[]} frames A set of StackFrames which are already mapped and missing their generated positions. + * @param {number} [fileContents=3] The number of lines to provide before and after the line specified in the StackFrame. */ async function unmap( _fileUri: string | { uri: string, contents: string }, From 6d8b31d0eda8ec29cdec916b7ab2f57b35330db3 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 13:37:45 -0400 Subject: [PATCH 09/18] Check flow on test --- packages/stack-frame-overlay/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/stack-frame-overlay/package.json b/packages/stack-frame-overlay/package.json index a069946d3f9..e24dc924f12 100644 --- a/packages/stack-frame-overlay/package.json +++ b/packages/stack-frame-overlay/package.json @@ -6,7 +6,7 @@ "scripts": { "prepublishOnly": "npm run build:prod && npm test", "start": "NODE_ENV=development npm run build -- --watch", - "test": "jest", + "test": "flow && jest", "build": "babel src/ -d lib/", "build:prod": "NODE_ENV=production babel src/ -d lib/" }, @@ -46,6 +46,7 @@ "eslint-plugin-import": "^2.0.1", "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.4.1", + "flow-bin": "^0.46.0", "jest": "19.x" }, "jest": { From 7220e2153e5881e208665e916ada4bd34dc79b8f Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 13:38:31 -0400 Subject: [PATCH 10/18] Test overlay in e2e --- tasks/e2e-simple.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks/e2e-simple.sh b/tasks/e2e-simple.sh index a3697af9f5e..63edd253cd9 100755 --- a/tasks/e2e-simple.sh +++ b/tasks/e2e-simple.sh @@ -105,6 +105,7 @@ fi ./node_modules/.bin/eslint --max-warnings 0 packages/react-scripts/ cd packages/stack-frame-overlay/ ./node_modules/.bin/eslint --max-warnings 0 src/ +npm test npm run build:prod cd ../.. From 834346148853aae9f639e090cc715608810ddae2 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 13:43:24 -0400 Subject: [PATCH 11/18] Add cross env --- packages/stack-frame-overlay/package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/stack-frame-overlay/package.json b/packages/stack-frame-overlay/package.json index e24dc924f12..588814cb7b3 100644 --- a/packages/stack-frame-overlay/package.json +++ b/packages/stack-frame-overlay/package.json @@ -5,10 +5,10 @@ "main": "lib/index.js", "scripts": { "prepublishOnly": "npm run build:prod && npm test", - "start": "NODE_ENV=development npm run build -- --watch", + "start": "cross-env NODE_ENV=development npm run build -- --watch", "test": "flow && jest", "build": "babel src/ -d lib/", - "build:prod": "NODE_ENV=production babel src/ -d lib/" + "build:prod": "cross-env NODE_ENV=production babel src/ -d lib/" }, "repository": "facebookincubator/create-react-app", "license": "BSD-3-Clause", @@ -40,6 +40,7 @@ "babel-cli": "^6.24.1", "babel-eslint": "7.x", "babel-preset-react-app": "^2.2.0", + "cross-env": "^4.0.0", "eslint": "^3.16.1", "eslint-config-react-app": "^0.6.2", "eslint-plugin-flowtype": "^2.21.0", From a40a67b9172b8914b99ed29c6ff1ae8db14f685b Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 19:01:01 -0400 Subject: [PATCH 12/18] Rename package --- .../{stack-frame-overlay => react-error-overlay}/.babelrc | 0 .../{stack-frame-overlay => react-error-overlay}/.eslintrc | 0 .../{stack-frame-overlay => react-error-overlay}/.flowconfig | 0 .../{stack-frame-overlay => react-error-overlay}/.gitignore | 0 .../{stack-frame-overlay => react-error-overlay}/.npmignore | 0 .../{stack-frame-overlay => react-error-overlay}/README.md | 2 +- .../{stack-frame-overlay => react-error-overlay}/package.json | 4 ++-- .../src/__tests__/setupJest.js | 0 .../src/components/additional.js | 0 .../src/components/close.js | 0 .../src/components/code.js | 0 .../src/components/footer.js | 0 .../src/components/frame.js | 0 .../src/components/frames.js | 0 .../src/components/overlay.js | 0 .../src/effects/proxyConsole.js | 0 .../src/effects/shortcuts.js | 0 .../src/effects/stackTraceLimit.js | 0 .../src/effects/unhandledError.js | 0 .../src/effects/unhandledRejection.js | 0 .../{stack-frame-overlay => react-error-overlay}/src/index.js | 0 .../src/overlay.js | 0 .../src/styles.js | 0 .../src/utils/dom/absolutifyCaret.js | 0 .../src/utils/dom/consumeEvent.js | 0 .../src/utils/dom/css.js | 0 .../src/utils/dom/enableTabClick.js | 0 .../src/utils/errorRegister.js | 0 .../src/utils/getLinesAround.js | 0 .../src/utils/getSourceMap.js | 0 .../src/utils/isInternalFile.js | 0 .../src/utils/mapper.js | 0 .../src/utils/parser.js | 0 .../src/utils/stack-frame.js | 0 .../src/utils/unmapper.js | 0 packages/react-scripts/config/webpack.config.dev.js | 2 +- packages/react-scripts/package.json | 2 +- tasks/e2e-installs.sh | 2 +- tasks/e2e-kitchensink.sh | 2 +- tasks/e2e-simple.sh | 2 +- 40 files changed, 8 insertions(+), 8 deletions(-) rename packages/{stack-frame-overlay => react-error-overlay}/.babelrc (100%) rename packages/{stack-frame-overlay => react-error-overlay}/.eslintrc (100%) rename packages/{stack-frame-overlay => react-error-overlay}/.flowconfig (100%) rename packages/{stack-frame-overlay => react-error-overlay}/.gitignore (100%) rename packages/{stack-frame-overlay => react-error-overlay}/.npmignore (100%) rename packages/{stack-frame-overlay => react-error-overlay}/README.md (85%) rename packages/{stack-frame-overlay => react-error-overlay}/package.json (96%) rename packages/{stack-frame-overlay => react-error-overlay}/src/__tests__/setupJest.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/components/additional.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/components/close.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/components/code.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/components/footer.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/components/frame.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/components/frames.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/components/overlay.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/effects/proxyConsole.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/effects/shortcuts.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/effects/stackTraceLimit.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/effects/unhandledError.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/effects/unhandledRejection.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/index.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/overlay.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/styles.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/utils/dom/absolutifyCaret.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/utils/dom/consumeEvent.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/utils/dom/css.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/utils/dom/enableTabClick.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/utils/errorRegister.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/utils/getLinesAround.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/utils/getSourceMap.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/utils/isInternalFile.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/utils/mapper.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/utils/parser.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/utils/stack-frame.js (100%) rename packages/{stack-frame-overlay => react-error-overlay}/src/utils/unmapper.js (100%) diff --git a/packages/stack-frame-overlay/.babelrc b/packages/react-error-overlay/.babelrc similarity index 100% rename from packages/stack-frame-overlay/.babelrc rename to packages/react-error-overlay/.babelrc diff --git a/packages/stack-frame-overlay/.eslintrc b/packages/react-error-overlay/.eslintrc similarity index 100% rename from packages/stack-frame-overlay/.eslintrc rename to packages/react-error-overlay/.eslintrc diff --git a/packages/stack-frame-overlay/.flowconfig b/packages/react-error-overlay/.flowconfig similarity index 100% rename from packages/stack-frame-overlay/.flowconfig rename to packages/react-error-overlay/.flowconfig diff --git a/packages/stack-frame-overlay/.gitignore b/packages/react-error-overlay/.gitignore similarity index 100% rename from packages/stack-frame-overlay/.gitignore rename to packages/react-error-overlay/.gitignore diff --git a/packages/stack-frame-overlay/.npmignore b/packages/react-error-overlay/.npmignore similarity index 100% rename from packages/stack-frame-overlay/.npmignore rename to packages/react-error-overlay/.npmignore diff --git a/packages/stack-frame-overlay/README.md b/packages/react-error-overlay/README.md similarity index 85% rename from packages/stack-frame-overlay/README.md rename to packages/react-error-overlay/README.md index b55d53eb405..fca2d178852 100644 --- a/packages/stack-frame-overlay/README.md +++ b/packages/react-error-overlay/README.md @@ -1,4 +1,4 @@ -# `stack-frame-overlay` +# `react-error-overlay` An overlay for displaying stack frames. diff --git a/packages/stack-frame-overlay/package.json b/packages/react-error-overlay/package.json similarity index 96% rename from packages/stack-frame-overlay/package.json rename to packages/react-error-overlay/package.json index 588814cb7b3..be3f7cf393f 100644 --- a/packages/stack-frame-overlay/package.json +++ b/packages/react-error-overlay/package.json @@ -1,6 +1,6 @@ { - "name": "stack-frame-overlay", - "version": "0.4.0", + "name": "react-error-overlay", + "version": "0.0.0", "description": "An overlay for displaying stack frames.", "main": "lib/index.js", "scripts": { diff --git a/packages/stack-frame-overlay/src/__tests__/setupJest.js b/packages/react-error-overlay/src/__tests__/setupJest.js similarity index 100% rename from packages/stack-frame-overlay/src/__tests__/setupJest.js rename to packages/react-error-overlay/src/__tests__/setupJest.js diff --git a/packages/stack-frame-overlay/src/components/additional.js b/packages/react-error-overlay/src/components/additional.js similarity index 100% rename from packages/stack-frame-overlay/src/components/additional.js rename to packages/react-error-overlay/src/components/additional.js diff --git a/packages/stack-frame-overlay/src/components/close.js b/packages/react-error-overlay/src/components/close.js similarity index 100% rename from packages/stack-frame-overlay/src/components/close.js rename to packages/react-error-overlay/src/components/close.js diff --git a/packages/stack-frame-overlay/src/components/code.js b/packages/react-error-overlay/src/components/code.js similarity index 100% rename from packages/stack-frame-overlay/src/components/code.js rename to packages/react-error-overlay/src/components/code.js diff --git a/packages/stack-frame-overlay/src/components/footer.js b/packages/react-error-overlay/src/components/footer.js similarity index 100% rename from packages/stack-frame-overlay/src/components/footer.js rename to packages/react-error-overlay/src/components/footer.js diff --git a/packages/stack-frame-overlay/src/components/frame.js b/packages/react-error-overlay/src/components/frame.js similarity index 100% rename from packages/stack-frame-overlay/src/components/frame.js rename to packages/react-error-overlay/src/components/frame.js diff --git a/packages/stack-frame-overlay/src/components/frames.js b/packages/react-error-overlay/src/components/frames.js similarity index 100% rename from packages/stack-frame-overlay/src/components/frames.js rename to packages/react-error-overlay/src/components/frames.js diff --git a/packages/stack-frame-overlay/src/components/overlay.js b/packages/react-error-overlay/src/components/overlay.js similarity index 100% rename from packages/stack-frame-overlay/src/components/overlay.js rename to packages/react-error-overlay/src/components/overlay.js diff --git a/packages/stack-frame-overlay/src/effects/proxyConsole.js b/packages/react-error-overlay/src/effects/proxyConsole.js similarity index 100% rename from packages/stack-frame-overlay/src/effects/proxyConsole.js rename to packages/react-error-overlay/src/effects/proxyConsole.js diff --git a/packages/stack-frame-overlay/src/effects/shortcuts.js b/packages/react-error-overlay/src/effects/shortcuts.js similarity index 100% rename from packages/stack-frame-overlay/src/effects/shortcuts.js rename to packages/react-error-overlay/src/effects/shortcuts.js diff --git a/packages/stack-frame-overlay/src/effects/stackTraceLimit.js b/packages/react-error-overlay/src/effects/stackTraceLimit.js similarity index 100% rename from packages/stack-frame-overlay/src/effects/stackTraceLimit.js rename to packages/react-error-overlay/src/effects/stackTraceLimit.js diff --git a/packages/stack-frame-overlay/src/effects/unhandledError.js b/packages/react-error-overlay/src/effects/unhandledError.js similarity index 100% rename from packages/stack-frame-overlay/src/effects/unhandledError.js rename to packages/react-error-overlay/src/effects/unhandledError.js diff --git a/packages/stack-frame-overlay/src/effects/unhandledRejection.js b/packages/react-error-overlay/src/effects/unhandledRejection.js similarity index 100% rename from packages/stack-frame-overlay/src/effects/unhandledRejection.js rename to packages/react-error-overlay/src/effects/unhandledRejection.js diff --git a/packages/stack-frame-overlay/src/index.js b/packages/react-error-overlay/src/index.js similarity index 100% rename from packages/stack-frame-overlay/src/index.js rename to packages/react-error-overlay/src/index.js diff --git a/packages/stack-frame-overlay/src/overlay.js b/packages/react-error-overlay/src/overlay.js similarity index 100% rename from packages/stack-frame-overlay/src/overlay.js rename to packages/react-error-overlay/src/overlay.js diff --git a/packages/stack-frame-overlay/src/styles.js b/packages/react-error-overlay/src/styles.js similarity index 100% rename from packages/stack-frame-overlay/src/styles.js rename to packages/react-error-overlay/src/styles.js diff --git a/packages/stack-frame-overlay/src/utils/dom/absolutifyCaret.js b/packages/react-error-overlay/src/utils/dom/absolutifyCaret.js similarity index 100% rename from packages/stack-frame-overlay/src/utils/dom/absolutifyCaret.js rename to packages/react-error-overlay/src/utils/dom/absolutifyCaret.js diff --git a/packages/stack-frame-overlay/src/utils/dom/consumeEvent.js b/packages/react-error-overlay/src/utils/dom/consumeEvent.js similarity index 100% rename from packages/stack-frame-overlay/src/utils/dom/consumeEvent.js rename to packages/react-error-overlay/src/utils/dom/consumeEvent.js diff --git a/packages/stack-frame-overlay/src/utils/dom/css.js b/packages/react-error-overlay/src/utils/dom/css.js similarity index 100% rename from packages/stack-frame-overlay/src/utils/dom/css.js rename to packages/react-error-overlay/src/utils/dom/css.js diff --git a/packages/stack-frame-overlay/src/utils/dom/enableTabClick.js b/packages/react-error-overlay/src/utils/dom/enableTabClick.js similarity index 100% rename from packages/stack-frame-overlay/src/utils/dom/enableTabClick.js rename to packages/react-error-overlay/src/utils/dom/enableTabClick.js diff --git a/packages/stack-frame-overlay/src/utils/errorRegister.js b/packages/react-error-overlay/src/utils/errorRegister.js similarity index 100% rename from packages/stack-frame-overlay/src/utils/errorRegister.js rename to packages/react-error-overlay/src/utils/errorRegister.js diff --git a/packages/stack-frame-overlay/src/utils/getLinesAround.js b/packages/react-error-overlay/src/utils/getLinesAround.js similarity index 100% rename from packages/stack-frame-overlay/src/utils/getLinesAround.js rename to packages/react-error-overlay/src/utils/getLinesAround.js diff --git a/packages/stack-frame-overlay/src/utils/getSourceMap.js b/packages/react-error-overlay/src/utils/getSourceMap.js similarity index 100% rename from packages/stack-frame-overlay/src/utils/getSourceMap.js rename to packages/react-error-overlay/src/utils/getSourceMap.js diff --git a/packages/stack-frame-overlay/src/utils/isInternalFile.js b/packages/react-error-overlay/src/utils/isInternalFile.js similarity index 100% rename from packages/stack-frame-overlay/src/utils/isInternalFile.js rename to packages/react-error-overlay/src/utils/isInternalFile.js diff --git a/packages/stack-frame-overlay/src/utils/mapper.js b/packages/react-error-overlay/src/utils/mapper.js similarity index 100% rename from packages/stack-frame-overlay/src/utils/mapper.js rename to packages/react-error-overlay/src/utils/mapper.js diff --git a/packages/stack-frame-overlay/src/utils/parser.js b/packages/react-error-overlay/src/utils/parser.js similarity index 100% rename from packages/stack-frame-overlay/src/utils/parser.js rename to packages/react-error-overlay/src/utils/parser.js diff --git a/packages/stack-frame-overlay/src/utils/stack-frame.js b/packages/react-error-overlay/src/utils/stack-frame.js similarity index 100% rename from packages/stack-frame-overlay/src/utils/stack-frame.js rename to packages/react-error-overlay/src/utils/stack-frame.js diff --git a/packages/stack-frame-overlay/src/utils/unmapper.js b/packages/react-error-overlay/src/utils/unmapper.js similarity index 100% rename from packages/stack-frame-overlay/src/utils/unmapper.js rename to packages/react-error-overlay/src/utils/unmapper.js diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index 5079854d811..9534293e3f2 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -55,7 +55,7 @@ module.exports = { // We ship a few polyfills by default: require.resolve('./polyfills'), // Errors should be considered fatal in development - require.resolve('stack-frame-overlay'), + require.resolve('react-error-overlay'), // Finally, this is your app's code: paths.appIndexJs, // We include the app code last so that if there is a runtime error during diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index a1077442a5a..af7e2c042b2 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -52,7 +52,7 @@ "postcss-loader": "1.3.3", "promise": "7.1.1", "react-dev-utils": "^0.5.2", - "stack-frame-overlay": "^0.4.0", + "react-error-overlay": "^0.0.0", "style-loader": "0.16.1", "url-loader": "0.5.8", "webpack": "2.4.1", diff --git a/tasks/e2e-installs.sh b/tasks/e2e-installs.sh index ef51ffe4642..b27b1239eda 100755 --- a/tasks/e2e-installs.sh +++ b/tasks/e2e-installs.sh @@ -99,7 +99,7 @@ fi # We removed the postinstall, so do it manually ./node_modules/.bin/lerna bootstrap --concurrency=1 -cd packages/stack-frame-overlay/ +cd packages/react-error-overlay/ npm run build:prod cd ../.. diff --git a/tasks/e2e-kitchensink.sh b/tasks/e2e-kitchensink.sh index 0279ea88baf..9df78a89c4e 100755 --- a/tasks/e2e-kitchensink.sh +++ b/tasks/e2e-kitchensink.sh @@ -82,7 +82,7 @@ fi # We removed the postinstall, so do it manually ./node_modules/.bin/lerna bootstrap --concurrency=1 -cd packages/stack-frame-overlay/ +cd packages/react-error-overlay/ npm run build:prod cd ../.. diff --git a/tasks/e2e-simple.sh b/tasks/e2e-simple.sh index 63edd253cd9..de0738ce20d 100755 --- a/tasks/e2e-simple.sh +++ b/tasks/e2e-simple.sh @@ -103,7 +103,7 @@ fi ./node_modules/.bin/eslint --max-warnings 0 packages/eslint-config-react-app/ ./node_modules/.bin/eslint --max-warnings 0 packages/react-dev-utils/ ./node_modules/.bin/eslint --max-warnings 0 packages/react-scripts/ -cd packages/stack-frame-overlay/ +cd packages/react-error-overlay/ ./node_modules/.bin/eslint --max-warnings 0 src/ npm test npm run build:prod From a5dc84dab4730d79cc1cd0829e771634c8812339 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 19:02:40 -0400 Subject: [PATCH 13/18] Make sure it gets built post-install --- packages/react-error-overlay/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-error-overlay/package.json b/packages/react-error-overlay/package.json index be3f7cf393f..d818f7e99e5 100644 --- a/packages/react-error-overlay/package.json +++ b/packages/react-error-overlay/package.json @@ -8,7 +8,8 @@ "start": "cross-env NODE_ENV=development npm run build -- --watch", "test": "flow && jest", "build": "babel src/ -d lib/", - "build:prod": "cross-env NODE_ENV=production babel src/ -d lib/" + "build:prod": "cross-env NODE_ENV=production babel src/ -d lib/", + "postinstall": "npm run build:prod" }, "repository": "facebookincubator/create-react-app", "license": "BSD-3-Clause", From d32d2f3cfde085eefb7e58b4dff250db0f3a7b63 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 19:08:01 -0400 Subject: [PATCH 14/18] Update the README --- packages/react-error-overlay/README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/react-error-overlay/README.md b/packages/react-error-overlay/README.md index fca2d178852..0a15dbf3f54 100644 --- a/packages/react-error-overlay/README.md +++ b/packages/react-error-overlay/README.md @@ -1,7 +1,11 @@ # `react-error-overlay` -An overlay for displaying stack frames. +`react-error-overlay` is an overlay which displays when there is a runtime error. -# API +## Development - +When developing within this package, make sure you run `npm start` (or `yarn start`) so that the files are compiled as you work. +This is ran in watch mode by default. + +If you would like to build this for production, run `npm run build:prod` (or `yarn build:prod`).
+If you would like to build this one-off for development, you can run `NODE_ENV=development npm run build` (or `NODE_ENV=development yarn build`). From 8bf601dbd36c1e0da7f785fa9ade70ab08ed8772 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 19:09:54 -0400 Subject: [PATCH 15/18] Remove extra builds now that there's a postinstall script --- tasks/e2e-installs.sh | 4 ---- tasks/e2e-kitchensink.sh | 4 ---- tasks/e2e-simple.sh | 1 - 3 files changed, 9 deletions(-) diff --git a/tasks/e2e-installs.sh b/tasks/e2e-installs.sh index b27b1239eda..89702463304 100755 --- a/tasks/e2e-installs.sh +++ b/tasks/e2e-installs.sh @@ -99,10 +99,6 @@ fi # We removed the postinstall, so do it manually ./node_modules/.bin/lerna bootstrap --concurrency=1 -cd packages/react-error-overlay/ -npm run build:prod -cd ../.. - # ****************************************************************************** # First, pack and install create-react-app. # ****************************************************************************** diff --git a/tasks/e2e-kitchensink.sh b/tasks/e2e-kitchensink.sh index 9df78a89c4e..2c2c1eb92c5 100755 --- a/tasks/e2e-kitchensink.sh +++ b/tasks/e2e-kitchensink.sh @@ -82,10 +82,6 @@ fi # We removed the postinstall, so do it manually ./node_modules/.bin/lerna bootstrap --concurrency=1 -cd packages/react-error-overlay/ -npm run build:prod -cd ../.. - # ****************************************************************************** # First, pack react-scripts and create-react-app so we can use them. # ****************************************************************************** diff --git a/tasks/e2e-simple.sh b/tasks/e2e-simple.sh index de0738ce20d..f73ce6bc164 100755 --- a/tasks/e2e-simple.sh +++ b/tasks/e2e-simple.sh @@ -106,7 +106,6 @@ fi cd packages/react-error-overlay/ ./node_modules/.bin/eslint --max-warnings 0 src/ npm test -npm run build:prod cd ../.. # ****************************************************************************** From 060582962854f1186cea5956d62fbc4e7e39241a Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 19:20:41 -0400 Subject: [PATCH 16/18] Revert "Remove extra builds now that there's a postinstall script" This reverts commit 8bf601dbd36c1e0da7f785fa9ade70ab08ed8772. --- tasks/e2e-installs.sh | 4 ++++ tasks/e2e-kitchensink.sh | 4 ++++ tasks/e2e-simple.sh | 1 + 3 files changed, 9 insertions(+) diff --git a/tasks/e2e-installs.sh b/tasks/e2e-installs.sh index 89702463304..b27b1239eda 100755 --- a/tasks/e2e-installs.sh +++ b/tasks/e2e-installs.sh @@ -99,6 +99,10 @@ fi # We removed the postinstall, so do it manually ./node_modules/.bin/lerna bootstrap --concurrency=1 +cd packages/react-error-overlay/ +npm run build:prod +cd ../.. + # ****************************************************************************** # First, pack and install create-react-app. # ****************************************************************************** diff --git a/tasks/e2e-kitchensink.sh b/tasks/e2e-kitchensink.sh index 2c2c1eb92c5..9df78a89c4e 100755 --- a/tasks/e2e-kitchensink.sh +++ b/tasks/e2e-kitchensink.sh @@ -82,6 +82,10 @@ fi # We removed the postinstall, so do it manually ./node_modules/.bin/lerna bootstrap --concurrency=1 +cd packages/react-error-overlay/ +npm run build:prod +cd ../.. + # ****************************************************************************** # First, pack react-scripts and create-react-app so we can use them. # ****************************************************************************** diff --git a/tasks/e2e-simple.sh b/tasks/e2e-simple.sh index f73ce6bc164..de0738ce20d 100755 --- a/tasks/e2e-simple.sh +++ b/tasks/e2e-simple.sh @@ -106,6 +106,7 @@ fi cd packages/react-error-overlay/ ./node_modules/.bin/eslint --max-warnings 0 src/ npm test +npm run build:prod cd ../.. # ****************************************************************************** From ed02ad4271ec27e2e28655ef10a93a8cfa1dffe8 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 19:21:04 -0400 Subject: [PATCH 17/18] Remove broken script --- packages/react-error-overlay/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-error-overlay/package.json b/packages/react-error-overlay/package.json index d818f7e99e5..be3f7cf393f 100644 --- a/packages/react-error-overlay/package.json +++ b/packages/react-error-overlay/package.json @@ -8,8 +8,7 @@ "start": "cross-env NODE_ENV=development npm run build -- --watch", "test": "flow && jest", "build": "babel src/ -d lib/", - "build:prod": "cross-env NODE_ENV=production babel src/ -d lib/", - "postinstall": "npm run build:prod" + "build:prod": "cross-env NODE_ENV=production babel src/ -d lib/" }, "repository": "facebookincubator/create-react-app", "license": "BSD-3-Clause", From 91c71bf96d1e8ced3e6ff6ed555c1d27b0e48d5c Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 10 May 2017 19:22:50 -0400 Subject: [PATCH 18/18] Fix some dev ergo --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ca7cfc03d9..15f94623f2e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "changelog": "lerna-changelog", "create-react-app": "tasks/cra.sh", "e2e": "tasks/e2e-simple.sh", - "postinstall": "lerna bootstrap", + "postinstall": "lerna bootstrap && cd packages/react-error-overlay/ && npm run build:prod", "publish": "tasks/release.sh", "start": "node packages/react-scripts/scripts/start.js", "test": "node packages/react-scripts/scripts/test.js --env=jsdom",