diff --git a/packages/react-error-overlay/middleware.js b/packages/react-dev-utils/errorOverlayMiddleware.js similarity index 76% rename from packages/react-error-overlay/middleware.js rename to packages/react-dev-utils/errorOverlayMiddleware.js index d4fd0d399f1..b2a857d6e37 100644 --- a/packages/react-error-overlay/middleware.js +++ b/packages/react-dev-utils/errorOverlayMiddleware.js @@ -8,12 +8,12 @@ */ 'use strict'; -const launchEditor = require('react-dev-utils/launchEditor'); +const launchEditor = require('./launchEditor'); +const launchEditorEndpoint = require('./launchEditorEndpoint'); module.exports = function createLaunchEditorMiddleware() { return function launchEditorMiddleware(req, res, next) { - // Keep this in sync with react-error-overlay - if (req.url.startsWith('/__open-stack-frame-in-editor')) { + if (req.url.startsWith(launchEditorEndpoint)) { launchEditor(req.query.fileName, req.query.lineNumber); res.end(); } else { diff --git a/packages/react-error-overlay/src/utils/dom/consumeEvent.js b/packages/react-dev-utils/launchEditorEndpoint.js similarity index 65% rename from packages/react-error-overlay/src/utils/dom/consumeEvent.js rename to packages/react-dev-utils/launchEditorEndpoint.js index 90bb9d1e815..e21870be9c4 100644 --- a/packages/react-error-overlay/src/utils/dom/consumeEvent.js +++ b/packages/react-dev-utils/launchEditorEndpoint.js @@ -6,13 +6,7 @@ * 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'; -/* @flow */ -function consumeEvent(e: Event) { - e.preventDefault(); - if (typeof e.target.blur === 'function') { - e.target.blur(); - } -} - -export { consumeEvent }; +// TODO: we might want to make this injectable to support DEV-time non-root URLs. +module.exports = '/__open-stack-frame-in-editor'; diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index 8816e4009a6..ccc22bf6332 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -11,12 +11,12 @@ "node": ">=6" }, "files": [ - "ansiHTML.js", "checkRequiredFiles.js", "clearConsole.js", "crashOverlay.js", "crossSpawn.js", "eslintFormatter.js", + "errorOverlayMiddleware.js", "FileSizeReporter.js", "printBuildError.js", "formatWebpackMessages.js", @@ -24,6 +24,7 @@ "inquirer.js", "InterpolateHtmlPlugin.js", "launchEditor.js", + "launchEditorEndpoint.js", "ModuleScopePlugin.js", "noopServiceWorkerMiddleware.js", "openBrowser.js", @@ -35,7 +36,6 @@ ], "dependencies": { "address": "1.0.2", - "anser": "1.4.1", "babel-code-frame": "6.22.0", "chalk": "1.1.3", "cross-spawn": "5.1.0", @@ -44,10 +44,10 @@ "filesize": "3.5.10", "global-modules": "1.0.0", "gzip-size": "3.0.0", - "html-entities": "1.2.1", "inquirer": "3.2.1", "is-root": "1.0.0", "opn": "5.1.0", + "react-error-overlay": "^1.0.9", "recursive-readdir": "2.2.1", "shell-quote": "1.6.1", "sockjs-client": "1.1.4", diff --git a/packages/react-dev-utils/webpackHotDevClient.js b/packages/react-dev-utils/webpackHotDevClient.js index f2f206a5cd2..b6effe718e5 100644 --- a/packages/react-dev-utils/webpackHotDevClient.js +++ b/packages/react-dev-utils/webpackHotDevClient.js @@ -21,143 +21,27 @@ var SockJS = require('sockjs-client'); var stripAnsi = require('strip-ansi'); var url = require('url'); +var launchEditorEndpoint = require('./launchEditorEndpoint'); var formatWebpackMessages = require('./formatWebpackMessages'); -var ansiHTML = require('./ansiHTML'); - -function createOverlayIframe(onIframeLoad) { - var iframe = document.createElement('iframe'); - iframe.id = 'react-dev-utils-webpack-hot-dev-client-overlay'; - iframe.src = 'about:blank'; - iframe.style.position = 'fixed'; - iframe.style.left = 0; - iframe.style.top = 0; - iframe.style.right = 0; - iframe.style.bottom = 0; - iframe.style.width = '100vw'; - iframe.style.height = '100vh'; - iframe.style.border = 'none'; - iframe.style.zIndex = 2147483647; - iframe.onload = onIframeLoad; - return iframe; -} - -function addOverlayDivTo(iframe) { - // TODO: unify these styles with react-error-overlay - iframe.contentDocument.body.style.margin = 0; - iframe.contentDocument.body.style.maxWidth = '100vw'; - - var outerDiv = iframe.contentDocument.createElement('div'); - outerDiv.id = 'react-dev-utils-webpack-hot-dev-client-overlay-div'; - outerDiv.style.width = '100%'; - outerDiv.style.height = '100%'; - outerDiv.style.boxSizing = 'border-box'; - outerDiv.style.textAlign = 'center'; - outerDiv.style.backgroundColor = 'rgb(255, 255, 255)'; - - var div = iframe.contentDocument.createElement('div'); - div.style.position = 'relative'; - div.style.display = 'inline-flex'; - div.style.flexDirection = 'column'; - div.style.height = '100%'; - div.style.width = '1024px'; - div.style.maxWidth = '100%'; - div.style.overflowX = 'hidden'; - div.style.overflowY = 'auto'; - div.style.padding = '0.5rem'; - div.style.boxSizing = 'border-box'; - div.style.textAlign = 'left'; - div.style.fontFamily = 'Consolas, Menlo, monospace'; - div.style.fontSize = '11px'; - div.style.whiteSpace = 'pre-wrap'; - div.style.wordBreak = 'break-word'; - div.style.lineHeight = '1.5'; - div.style.color = 'rgb(41, 50, 56)'; - - outerDiv.appendChild(div); - iframe.contentDocument.body.appendChild(outerDiv); - return div; -} - -function overlayHeaderStyle() { - return ( - 'font-size: 2em;' + - 'font-family: sans-serif;' + - 'color: rgb(206, 17, 38);' + - 'white-space: pre-wrap;' + - 'margin: 0 2rem 0.75rem 0px;' + - 'flex: 0 0 auto;' + - 'max-height: 35%;' + - 'overflow: auto;' - ); -} - -var overlayIframe = null; -var overlayDiv = null; -var lastOnOverlayDivReady = null; - -function ensureOverlayDivExists(onOverlayDivReady) { - if (overlayDiv) { - // Everything is ready, call the callback right away. - onOverlayDivReady(overlayDiv); - return; - } - - // Creating an iframe may be asynchronous so we'll schedule the callback. - // In case of multiple calls, last callback wins. - lastOnOverlayDivReady = onOverlayDivReady; - - if (overlayIframe) { - // We're already creating it. - return; - } - - // Create iframe and, when it is ready, a div inside it. - overlayIframe = createOverlayIframe(function onIframeLoad() { - overlayDiv = addOverlayDivTo(overlayIframe); - // Now we can talk! - lastOnOverlayDivReady(overlayDiv); - }); - - // Zalgo alert: onIframeLoad() will be called either synchronously - // or asynchronously depending on the browser. - // We delay adding it so `overlayIframe` is set when `onIframeLoad` fires. - document.body.appendChild(overlayIframe); -} +var ErrorOverlay = require('react-error-overlay'); + +ErrorOverlay.startReportingRuntimeErrors({ + launchEditorEndpoint: launchEditorEndpoint, + onError: function() { + // TODO: why do we need this? + if (module.hot && typeof module.hot.decline === 'function') { + module.hot.decline(); + } + }, +}); -function showErrorOverlay(message) { - ensureOverlayDivExists(function onOverlayDivReady(overlayDiv) { - // TODO: unify this with our runtime overlay - overlayDiv.innerHTML = - '
Failed to compile
' + - '
' +
-      '' +
-      ansiHTML(message) +
-      '
' + - '
' + - 'This error occurred during the build time and cannot be dismissed.
'; +if (module.hot && typeof module.hot.dispose === 'function') { + module.hot.dispose(function() { + // TODO: why do we need this? + ErrorOverlay.stopReportingRuntimeErrors(); }); } -function destroyErrorOverlay() { - if (!overlayDiv) { - // It is not there in the first place. - return; - } - - // Clean up and reset internal state. - document.body.removeChild(overlayIframe); - overlayDiv = null; - overlayIframe = null; - lastOnOverlayDivReady = null; -} - // Connect to WebpackDevServer via a socket. var connection = new SockJS( url.format({ @@ -205,9 +89,9 @@ function handleSuccess() { // Attempt to apply hot updates or reload. if (isHotUpdate) { tryApplyUpdates(function onHotUpdateSuccess() { - // Only destroy it when we're sure it's a hot update. + // Only dismiss it when we're sure it's a hot update. // Otherwise it would flicker right before the reload. - destroyErrorOverlay(); + ErrorOverlay.dismissBuildError(); }); } } @@ -247,9 +131,9 @@ function handleWarnings(warnings) { // Only print warnings if we aren't refreshing the page. // Otherwise they'll disappear right away anyway. printWarnings(); - // Only destroy it when we're sure it's a hot update. + // Only dismiss it when we're sure it's a hot update. // Otherwise it would flicker right before the reload. - destroyErrorOverlay(); + ErrorOverlay.dismissBuildError(); }); } else { // Print initial warnings immediately. @@ -271,7 +155,7 @@ function handleErrors(errors) { }); // Only show the first error. - showErrorOverlay(formatted.errors[0]); + ErrorOverlay.reportBuildError(formatted.errors[0]); // Also log them to the console. if (typeof console !== 'undefined' && typeof console.error === 'function') { diff --git a/packages/react-error-overlay/.flowconfig b/packages/react-error-overlay/.flowconfig index 261b8646fc3..8d7de784e29 100644 --- a/packages/react-error-overlay/.flowconfig +++ b/packages/react-error-overlay/.flowconfig @@ -1,4 +1,5 @@ [ignore] +.*/node_modules/eslint-plugin-jsx-a11y/.* [include] src/**/*.js diff --git a/packages/react-error-overlay/fixtures/bundle.json b/packages/react-error-overlay/fixtures/bundle.json index 7dfd31f5863..16670f6231f 100644 --- a/packages/react-error-overlay/fixtures/bundle.json +++ b/packages/react-error-overlay/fixtures/bundle.json @@ -240,11 +240,11 @@ ] }, { - "functionName": "Object.batchedUpdates", + "functionName": "batchedUpdates", "fileName": "http://localhost:3000/static/js/bundle.js", "lineNumber": 33900, "columnNumber": 26, - "_originalFunctionName": "Object.batchedUpdates", + "_originalFunctionName": "batchedUpdates", "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactDefaultBatchingStrategy.js", "_originalLineNumber": 62, "_originalColumnNumber": 0, @@ -264,11 +264,11 @@ ] }, { - "functionName": "Object.batchedUpdates", + "functionName": "batchedUpdates", "fileName": "http://localhost:3000/static/js/bundle.js", "lineNumber": 2181, "columnNumber": 27, - "_originalFunctionName": "Object.batchedUpdates", + "_originalFunctionName": "batchedUpdates", "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactUpdates.js", "_originalLineNumber": 97, "_originalColumnNumber": 0, @@ -288,11 +288,11 @@ ] }, { - "functionName": "Object._renderNewRootComponent", + "functionName": "_renderNewRootComponent", "fileName": "http://localhost:3000/static/js/bundle.js", "lineNumber": 14398, "columnNumber": 18, - "_originalFunctionName": "Object._renderNewRootComponent", + "_originalFunctionName": "_renderNewRootComponent", "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactMount.js", "_originalLineNumber": 320, "_originalColumnNumber": 0, @@ -312,11 +312,11 @@ ] }, { - "functionName": "Object._renderSubtreeIntoContainer", + "functionName": "_renderSubtreeIntoContainer", "fileName": "http://localhost:3000/static/js/bundle.js", "lineNumber": 14479, "columnNumber": 32, - "_originalFunctionName": "Object._renderSubtreeIntoContainer", + "_originalFunctionName": "_renderSubtreeIntoContainer", "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactMount.js", "_originalLineNumber": 401, "_originalColumnNumber": 0, @@ -336,11 +336,11 @@ ] }, { - "functionName": "Object.render", + "functionName": "render", "fileName": "http://localhost:3000/static/js/bundle.js", "lineNumber": 14500, "columnNumber": 23, - "_originalFunctionName": "Object.render", + "_originalFunctionName": "render", "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactMount.js", "_originalLineNumber": 422, "_originalColumnNumber": 0, @@ -360,11 +360,11 @@ ] }, { - "functionName": "Object.friendlySyntaxErrorLabel", + "functionName": null, "fileName": "http://localhost:3000/static/js/bundle.js", "lineNumber": 17287, "columnNumber": 20, - "_originalFunctionName": "Object.friendlySyntaxErrorLabel", + "_originalFunctionName": null, "_originalFileName": "webpack:///packages/react-scripts/template/src/index.js", "_originalLineNumber": 6, "_originalColumnNumber": 0, @@ -432,11 +432,11 @@ ] }, { - "functionName": "Object.", + "functionName": null, "fileName": "http://localhost:3000/static/js/bundle.js", "lineNumber": 41219, "columnNumber": 18, - "_originalFunctionName": "Object.", + "_originalFunctionName": null, "_originalFileName": null, "_originalLineNumber": null, "_originalColumnNumber": null, diff --git a/packages/react-error-overlay/package.json b/packages/react-error-overlay/package.json index 5a9e864c50f..65c9b5efa0a 100644 --- a/packages/react-error-overlay/package.json +++ b/packages/react-error-overlay/package.json @@ -34,7 +34,9 @@ "anser": "1.4.1", "babel-code-frame": "6.22.0", "babel-runtime": "6.23.0", - "react-dev-utils": "^3.1.0", + "html-entities": "1.2.1", + "react": "^15.5.4", + "react-dom": "^15.5.4", "settle-promise": "1.0.0", "source-map": "0.5.6" }, diff --git a/packages/react-error-overlay/src/__tests__/stack-frame.js b/packages/react-error-overlay/src/__tests__/stack-frame.js index dc6a01b4a06..5a015260ab2 100644 --- a/packages/react-error-overlay/src/__tests__/stack-frame.js +++ b/packages/react-error-overlay/src/__tests__/stack-frame.js @@ -13,9 +13,9 @@ test('proper empty shape', () => { const empty = new StackFrame(); expect(empty).toMatchSnapshot(); - expect(empty.getFunctionName()).toBe(null); + expect(empty.getFunctionName()).toBe('(anonymous function)'); expect(empty.getSource()).toBe(''); - expect(empty.toString()).toBe(''); + expect(empty.toString()).toBe('(anonymous function)'); }); test('proper full shape', () => { diff --git a/packages/react-error-overlay/src/components/CloseButton.js b/packages/react-error-overlay/src/components/CloseButton.js new file mode 100644 index 00000000000..503b1198c3f --- /dev/null +++ b/packages/react-error-overlay/src/components/CloseButton.js @@ -0,0 +1,38 @@ +/** + * 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. + */ + +/* @flow */ +import React from 'react'; +import { black } from '../styles'; + +const closeButtonStyle = { + color: black, + lineHeight: '1rem', + fontSize: '1.5rem', + padding: '1rem', + cursor: 'pointer', + position: 'absolute', + right: 0, + top: 0, +}; + +type CloseCallback = () => void; +function CloseButton({ close }: {| close: CloseCallback |}) { + return ( + + × + + ); +} + +export default CloseButton; diff --git a/packages/react-error-overlay/src/components/CodeBlock.js b/packages/react-error-overlay/src/components/CodeBlock.js new file mode 100644 index 00000000000..478f0111b9b --- /dev/null +++ b/packages/react-error-overlay/src/components/CodeBlock.js @@ -0,0 +1,54 @@ +/** + * 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. + */ + +/* @flow */ +import React from 'react'; +import { redTransparent, yellowTransparent } from '../styles'; + +const _preStyle = { + display: 'block', + padding: '0.5em', + marginTop: '0.5em', + marginBottom: '0.5em', + overflowX: 'auto', + whiteSpace: 'pre-wrap', + borderRadius: '0.25rem', +}; + +const primaryPreStyle = { + ..._preStyle, + backgroundColor: redTransparent, +}; + +const secondaryPreStyle = { + ..._preStyle, + backgroundColor: yellowTransparent, +}; + +const codeStyle = { + fontFamily: 'Consolas, Menlo, monospace', +}; + +type CodeBlockPropsType = {| + main: boolean, + codeHTML: string, +|}; + +function CodeBlock(props: CodeBlockPropsType) { + const preStyle = props.main ? primaryPreStyle : secondaryPreStyle; + const codeBlock = { __html: props.codeHTML }; + + return ( +
+      
+    
+ ); +} + +export default CodeBlock; diff --git a/packages/react-error-overlay/src/components/Collapsible.js b/packages/react-error-overlay/src/components/Collapsible.js new file mode 100644 index 00000000000..92f1de4295c --- /dev/null +++ b/packages/react-error-overlay/src/components/Collapsible.js @@ -0,0 +1,78 @@ +/** + * 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. + */ + +/* @flow */ +import React, { Component } from 'react'; +import { black } from '../styles'; + +const _collapsibleStyle = { + color: black, + cursor: 'pointer', + border: 'none', + display: 'block', + width: '100%', + textAlign: 'left', + background: '#fff', + fontFamily: 'Consolas, Menlo, monospace', + fontSize: '1em', + padding: '0px', + lineHeight: '1.5', +}; + +const collapsibleCollapsedStyle = { + ..._collapsibleStyle, + marginBottom: '1.5em', +}; + +const collapsibleExpandedStyle = { + ..._collapsibleStyle, + marginBottom: '0.6em', +}; + +class Collapsible extends Component { + state = { + collapsed: true, + }; + + toggleCollaped = () => { + this.setState(state => ({ + collapsed: !state.collapsed, + })); + }; + + render() { + const count = this.props.children.length; + const collapsed = this.state.collapsed; + return ( +
+ +
+ {this.props.children} + +
+
+ ); + } +} + +export default Collapsible; diff --git a/packages/react-error-overlay/src/components/Footer.js b/packages/react-error-overlay/src/components/Footer.js new file mode 100644 index 00000000000..68eb8465674 --- /dev/null +++ b/packages/react-error-overlay/src/components/Footer.js @@ -0,0 +1,36 @@ +/** + * 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. + */ + +/* @flow */ +import React from 'react'; +import { darkGray } from '../styles'; + +const footerStyle = { + fontFamily: 'sans-serif', + color: darkGray, + marginTop: '0.5rem', + flex: '0 0 auto', +}; + +type FooterPropsType = {| + line1: string, + line2?: string, +|}; + +function Footer(props: FooterPropsType) { + return ( +
+ {props.line1} +
+ {props.line2} +
+ ); +} + +export default Footer; diff --git a/packages/react-error-overlay/src/components/Header.js b/packages/react-error-overlay/src/components/Header.js new file mode 100644 index 00000000000..a2f40973d00 --- /dev/null +++ b/packages/react-error-overlay/src/components/Header.js @@ -0,0 +1,39 @@ +/** + * 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. + */ + +/* @flow */ +import React from 'react'; +import { red } from '../styles'; + +const headerStyle = { + fontSize: '2em', + fontFamily: 'sans-serif', + color: red, + whiteSpace: 'pre-wrap', + // Top bottom margin spaces header + // Right margin revents overlap with close button + margin: '0 2rem 0.75rem 0', + flex: '0 0 auto', + maxHeight: '50%', + overflow: 'auto', +}; + +type HeaderPropType = {| + headerText: string, +|}; + +function Header(props: HeaderPropType) { + return ( +
+ {props.headerText} +
+ ); +} + +export default Header; diff --git a/packages/react-error-overlay/src/components/NavigationBar.js b/packages/react-error-overlay/src/components/NavigationBar.js new file mode 100644 index 00000000000..4eba743cef7 --- /dev/null +++ b/packages/react-error-overlay/src/components/NavigationBar.js @@ -0,0 +1,70 @@ +/** + * 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. + */ + +/* @flow */ +import React from 'react'; +import { red, redTransparent } from '../styles'; + +const navigationBarStyle = { + marginBottom: '0.5rem', +}; + +const buttonContainerStyle = { + marginRight: '1em', +}; + +const _navButtonStyle = { + backgroundColor: redTransparent, + color: red, + border: 'none', + borderRadius: '4px', + padding: '3px 6px', + cursor: 'pointer', +}; + +const leftButtonStyle = { + ..._navButtonStyle, + borderTopRightRadius: '0px', + borderBottomRightRadius: '0px', + marginRight: '1px', +}; + +const rightButtonStyle = { + ..._navButtonStyle, + borderTopLeftRadius: '0px', + borderBottomLeftRadius: '0px', +}; + +type Callback = () => void; + +type NavigationBarPropsType = {| + currentError: number, + totalErrors: number, + previous: Callback, + next: Callback, +|}; + +function NavigationBar(props: NavigationBarPropsType) { + const { currentError, totalErrors, previous, next } = props; + return ( +
+ + + + + {`${currentError} of ${totalErrors} errors on the page`} +
+ ); +} + +export default NavigationBar; diff --git a/packages/react-error-overlay/src/components/Overlay.js b/packages/react-error-overlay/src/components/Overlay.js new file mode 100644 index 00000000000..4fe530b6fee --- /dev/null +++ b/packages/react-error-overlay/src/components/Overlay.js @@ -0,0 +1,74 @@ +/** + * 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. + */ + +/* @flow */ +import React, { Component } from 'react'; +import { black } from '../styles'; + +const overlayStyle = { + position: 'relative', + display: 'inline-flex', + flexDirection: 'column', + height: '100%', + width: '1024px', + maxWidth: '100%', + overflowX: 'hidden', + overflowY: 'auto', + padding: '0.5rem', + boxSizing: 'border-box', + textAlign: 'left', + fontFamily: 'Consolas, Menlo, monospace', + fontSize: '11px', + whiteSpace: 'pre-wrap', + wordBreak: 'break-word', + lineHeight: 1.5, + color: black, +}; + +class Overlay extends Component { + iframeWindow: window = null; + + getIframeWindow = (element: HTMLDivElement) => { + if (element) { + const document = element.ownerDocument; + this.iframeWindow = document.defaultView; + } + }; + + onKeyDown = (e: KeyboardEvent) => { + const { shortcutHandler } = this.props; + if (shortcutHandler) { + shortcutHandler(e.key); + } + }; + + componentDidMount() { + window.addEventListener('keydown', this.onKeyDown); + if (this.iframeWindow) { + this.iframeWindow.addEventListener('keydown', this.onKeyDown); + } + } + + componentWillUnmount() { + window.removeEventListener('keydown', this.onKeyDown); + if (this.iframeWindow) { + this.iframeWindow.removeEventListener('keydown', this.onKeyDown); + } + } + + render() { + return ( +
+ {this.props.children} +
+ ); + } +} + +export default Overlay; diff --git a/packages/react-error-overlay/src/components/additional.js b/packages/react-error-overlay/src/components/additional.js deleted file mode 100644 index b573c740634..00000000000 --- a/packages/react-error-overlay/src/components/additional.js +++ /dev/null @@ -1,72 +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. - */ - -/* @flow */ -import { applyStyles } from '../utils/dom/css'; -import { - additionalChildStyle, - 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); - } - - if (totalErrors <= 1) { - return; - } - - const div = document.createElement('div'); - applyStyles(div, additionalChildStyle); - - 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); - div.appendChild(group); - - const text = `${currentError} of ${totalErrors} errors on the page`; - div.appendChild(document.createTextNode(text)); - - additionalReference.appendChild(div); -} - -export type { SwitchCallback }; -export { updateAdditional }; diff --git a/packages/react-error-overlay/src/components/close.js b/packages/react-error-overlay/src/components/close.js deleted file mode 100644 index 2ced8d0ce92..00000000000 --- a/packages/react-error-overlay/src/components/close.js +++ /dev/null @@ -1,35 +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. - */ - -/* @flow */ -import { applyStyles } from '../utils/dom/css'; -import { hintsStyle, hintStyle, closeButtonStyle } from '../styles'; - -function createHint(document: Document, hint: string, title: string) { - const span = document.createElement('span'); - span.appendChild(document.createTextNode(hint)); - span.setAttribute('title', title); - 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, '×', 'Click or press Escape to dismiss.'); - close.addEventListener('click', () => callback()); - applyStyles(close, closeButtonStyle); - hints.appendChild(close); - return hints; -} - -export type { CloseCallback }; -export { createClose }; diff --git a/packages/react-error-overlay/src/components/footer.js b/packages/react-error-overlay/src/components/footer.js deleted file mode 100644 index 4586a04ff2b..00000000000 --- a/packages/react-error-overlay/src/components/footer.js +++ /dev/null @@ -1,31 +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. - */ - -/* @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 if 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/react-error-overlay/src/components/frame.js b/packages/react-error-overlay/src/components/frame.js deleted file mode 100644 index 4087f4d9c9d..00000000000 --- a/packages/react-error-overlay/src/components/frame.js +++ /dev/null @@ -1,355 +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. - */ - -/* @flow */ -import { enableTabClick } from '../utils/dom/enableTabClick'; -import { createCode } from './code'; -import { isInternalFile } from '../utils/isInternalFile'; -import type { StackFrame } from '../utils/stack-frame'; -import type { FrameSetting, OmitsObject } from './frames'; -import { applyStyles } from '../utils/dom/css'; -import { - omittedFramesExpandedStyle, - omittedFramesCollapsedStyle, - 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'); - applyStyles(omittedFrames, omittedFramesCollapsedStyle); - } else { - text1.textContent = text1.textContent.replace(/▶/, '▲'); - text1.textContent = text1.textContent.replace(/collapsed/, 'expanded'); - applyStyles(omittedFrames, omittedFramesExpandedStyle); - } - }); - applyStyles(omittedFrames, omittedFramesCollapsedStyle); - 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, omittedFramesExpandedStyle); - div.style.display = 'none'; - - parent.insertBefore(div, first); -} - -function frameDiv( - document: Document, - functionName, - url, - internalUrl, - onSourceClick: ?Function -) { - const frame = document.createElement('div'); - const frameFunctionName = document.createElement('div'); - - if (functionName && functionName.indexOf('Object.') === 0) { - functionName = functionName.slice('Object.'.length); - } - if (functionName === '') { - functionName = null; - } - const cleanedFunctionName = functionName || '(anonymous function)'; - 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); - - if (typeof onSourceClick === 'function') { - let handler = onSourceClick; - enableTabClick(frameAnchor); - frameAnchor.style.cursor = 'pointer'; - frameAnchor.addEventListener('click', function() { - handler(); - }); - } - - return frame; -} - -function isBultinErrorName(errorName: ?string) { - switch (errorName) { - case 'EvalError': - case 'InternalError': - case 'RangeError': - case 'ReferenceError': - case 'SyntaxError': - case 'TypeError': - case 'URIError': - return true; - default: - return false; - } -} - -function getPrettyURL( - sourceFileName: ?string, - sourceLineNumber: ?number, - sourceColumnNumber: ?number, - fileName: ?string, - lineNumber: ?number, - columnNumber: ?number, - compiled: boolean -): string { - let prettyURL; - if (!compiled && sourceFileName && typeof sourceLineNumber === 'number') { - // Remove everything up to the first /src/ or /node_modules/ - const trimMatch = /^[/|\\].*?[/|\\]((src|node_modules)[/|\\].*)/.exec( - sourceFileName - ); - if (trimMatch && trimMatch[1]) { - prettyURL = trimMatch[1]; - } else { - prettyURL = sourceFileName; - } - prettyURL += ':' + sourceLineNumber; - // Note: we intentionally skip 0's because they're produced by cheap Webpack maps - if (sourceColumnNumber) { - prettyURL += ':' + sourceColumnNumber; - } - } else if (fileName && typeof lineNumber === 'number') { - prettyURL = fileName + ':' + lineNumber; - // Note: we intentionally skip 0's because they're produced by cheap Webpack maps - if (columnNumber) { - prettyURL += ':' + columnNumber; - } - } else { - prettyURL = 'unknown'; - } - return prettyURL; -} - -function createFrame( - document: Document, - frameSetting: FrameSetting, - frame: StackFrame, - contextSize: number, - critical: boolean, - omits: OmitsObject, - omitBundle: number, - parentContainer: HTMLDivElement, - lastElement: boolean, - errorName: ?string -) { - const { compiled } = frameSetting; - let { functionName, _originalFileName: sourceFileName } = frame; - const { - fileName, - lineNumber, - columnNumber, - _scriptCode: scriptLines, - _originalLineNumber: sourceLineNumber, - _originalColumnNumber: sourceColumnNumber, - _originalScriptCode: sourceLines, - } = frame; - - // TODO: find a better place for this. - // Chrome has a bug with inferring function.name: - // https://github.com/facebookincubator/create-react-app/issues/2097 - // Let's ignore a meaningless name we get for top-level modules. - if ( - functionName === 'Object.friendlySyntaxErrorLabel' || - functionName === 'Object.exports.__esModule' - ) { - functionName = '(anonymous function)'; - } - - const prettyURL = getPrettyURL( - sourceFileName, - sourceLineNumber, - sourceColumnNumber, - fileName, - lineNumber, - columnNumber, - compiled - ); - - let needsHidden = false; - const isInternalUrl = isInternalFile(sourceFileName, fileName); - const isThrownIntentionally = !isBultinErrorName(errorName); - const shouldCollapse = - isInternalUrl && (isThrownIntentionally || omits.hasReachedAppCode); - - if (!isInternalUrl) { - omits.hasReachedAppCode = true; - } - - if (shouldCollapse) { - ++omits.value; - needsHidden = true; - } - - let collapseElement = null; - if (!shouldCollapse || 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 && shouldCollapse) { - collapseElement = omittedFrames; - } else { - parentContainer.appendChild(omittedFrames); - } - ++omits.bundle; - } - omits.value = 0; - } - - let onSourceClick = null; - if (sourceFileName) { - // e.g. "/path-to-my-app/webpack/bootstrap eaddeb46b67d75e4dfc1" - const isInternalWebpackBootstrapCode = - sourceFileName.trim().indexOf(' ') !== -1; - if (!isInternalWebpackBootstrapCode) { - onSourceClick = () => { - // Keep this in sync with react-error-overlay/middleware.js - fetch( - '/__open-stack-frame-in-editor?fileName=' + - window.encodeURIComponent(sourceFileName) + - '&lineNumber=' + - window.encodeURIComponent(sourceLineNumber || 1) - ).then(() => {}, () => {}); - }; - } - } - - const elem = frameDiv( - document, - functionName, - prettyURL, - shouldCollapse, - onSourceClick - ); - if (needsHidden) { - applyStyles(elem, hiddenStyle); - elem.setAttribute('name', 'bundle-' + omitBundle); - } - - let hasSource = false; - if (!shouldCollapse) { - if ( - compiled && - scriptLines && - scriptLines.length !== 0 && - lineNumber != null - ) { - elem.appendChild( - createCode( - document, - scriptLines, - lineNumber, - columnNumber, - contextSize, - critical, - onSourceClick - ) - ); - hasSource = true; - } else if ( - !compiled && - sourceLines && - sourceLines.length !== 0 && - sourceLineNumber != null - ) { - elem.appendChild( - createCode( - document, - sourceLines, - sourceLineNumber, - sourceColumnNumber, - contextSize, - critical, - onSourceClick - ) - ); - hasSource = true; - } - } - - return { elem: elem, hasSource: hasSource, collapseElement: collapseElement }; -} - -export { createFrame }; diff --git a/packages/react-error-overlay/src/components/frames.js b/packages/react-error-overlay/src/components/frames.js deleted file mode 100644 index 8bd50509295..00000000000 --- a/packages/react-error-overlay/src/components/frames.js +++ /dev/null @@ -1,132 +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. - */ - -/* @flow */ -import type { StackFrame } from '../utils/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, - hasReachedAppCode: boolean, -}; -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, - errorName: ?string -) { - 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, hasReachedAppCode: false }; - 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, - errorName - ), - 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/react-error-overlay/src/components/overlay.js b/packages/react-error-overlay/src/components/overlay.js deleted file mode 100644 index 69acf9ad43f..00000000000 --- a/packages/react-error-overlay/src/components/overlay.js +++ /dev/null @@ -1,95 +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. - */ - -/* @flow */ -import { applyStyles } from '../utils/dom/css'; -import { containerStyle, overlayStyle, headerStyle } from '../styles'; -import { createClose } from './close'; -import { createFrames } from './frames'; -import { createFooter } from './footer'; -import type { CloseCallback } from './close'; -import type { StackFrame } from '../utils/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); - - // Create container - const container = document.createElement('div'); - applyStyles(container, containerStyle); - overlay.appendChild(container); - container.appendChild(createClose(document, closeCallback)); - - // Create "Errors X of Y" in case of multiple errors - const additional = document.createElement('div'); - updateAdditional( - document, - additional, - currentError, - totalErrors, - switchCallback - ); - container.appendChild(additional); - - // Create header - const header = document.createElement('div'); - applyStyles(header, headerStyle); - - // Make message prettier - let finalMessage = - message.match(/^\w*:/) || !name ? message : name + ': ' + message; - - finalMessage = finalMessage - // TODO: maybe remove this prefix from fbjs? - // It's just scaring people - .replace(/^Invariant Violation:\s*/, '') - // This is not helpful either: - .replace(/^Warning:\s*/, '') - // Break the actionable part to the next line. - // AFAIK React 16+ should already do this. - .replace(' Check the render method', '\n\nCheck the render method') - .replace(' Check your code at', '\n\nCheck your code at'); - - // Put it in the DOM - header.appendChild(document.createTextNode(finalMessage)); - container.appendChild(header); - - // Create trace - container.appendChild( - createFrames(document, frames, frameSettings, contextSize, name) - ); - - // Show message - container.appendChild(createFooter(document)); - - return { - overlay, - additional, - }; -} - -export { createOverlay }; diff --git a/packages/react-error-overlay/src/containers/CompileErrorContainer.js b/packages/react-error-overlay/src/containers/CompileErrorContainer.js new file mode 100644 index 00000000000..bd193eb50b4 --- /dev/null +++ b/packages/react-error-overlay/src/containers/CompileErrorContainer.js @@ -0,0 +1,31 @@ +/** + * 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. + */ + +/* @flow */ +import React, { PureComponent } from 'react'; +import Overlay from '../components/Overlay'; +import Footer from '../components/Footer'; +import Header from '../components/Header'; +import CodeBlock from '../components/CodeBlock'; +import generateAnsiHTML from '../utils/generateAnsiHTML'; + +class CompileErrorContainer extends PureComponent { + render() { + const { error } = this.props; + return ( + +
+ +