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 (
+
+
+
+
+
+ );
+ }
+}
+
+export default CompileErrorContainer;
diff --git a/packages/react-error-overlay/src/containers/RuntimeError.js b/packages/react-error-overlay/src/containers/RuntimeError.js
new file mode 100644
index 00000000000..c64824137d2
--- /dev/null
+++ b/packages/react-error-overlay/src/containers/RuntimeError.js
@@ -0,0 +1,68 @@
+/**
+ * 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 Header from '../components/Header';
+import StackTrace from './StackTrace';
+import type { StackFrame } from '../utils/stack-frame';
+
+const wrapperStyle = {
+ display: 'flex',
+ flexDirection: 'column',
+};
+
+type ErrorRecord = {|
+ error: Error,
+ unhandledRejection: boolean,
+ contextSize: number,
+ stackFrames: StackFrame[],
+|};
+
+type Props = {|
+ errorRecord: ErrorRecord,
+ launchEditorEndpoint: ?string,
+|};
+
+function RuntimeError({ errorRecord, launchEditorEndpoint }: Props) {
+ const { error, unhandledRejection, contextSize, stackFrames } = errorRecord;
+ const errorName = unhandledRejection
+ ? 'Unhandled Rejection (' + error.name + ')'
+ : error.name;
+
+ // Make header prettier
+ const message = error.message;
+ let headerText =
+ message.match(/^\w*:/) || !errorName ? message : errorName + ': ' + message;
+
+ headerText = headerText
+ // 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');
+
+ return (
+
+
+
+
+ );
+}
+
+export default RuntimeError;
diff --git a/packages/react-error-overlay/src/containers/RuntimeErrorContainer.js b/packages/react-error-overlay/src/containers/RuntimeErrorContainer.js
new file mode 100644
index 00000000000..c84adb19492
--- /dev/null
+++ b/packages/react-error-overlay/src/containers/RuntimeErrorContainer.js
@@ -0,0 +1,77 @@
+/**
+ * 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 CloseButton from '../components/CloseButton';
+import NavigationBar from '../components/NavigationBar';
+import RuntimeError from './RuntimeError';
+import Footer from '../components/Footer';
+
+class RuntimeErrorContainer extends PureComponent {
+ state = {
+ currentIndex: 0,
+ };
+
+ previous = () => {
+ this.setState((state, props) => ({
+ currentIndex:
+ state.currentIndex > 0
+ ? state.currentIndex - 1
+ : props.errorRecords.length - 1,
+ }));
+ };
+
+ next = () => {
+ this.setState((state, props) => ({
+ currentIndex:
+ state.currentIndex < props.errorRecords.length - 1
+ ? state.currentIndex + 1
+ : 0,
+ }));
+ };
+
+ shortcutHandler = (key: string) => {
+ if (key === 'Escape') {
+ this.props.close();
+ } else if (key === 'ArrowLeft') {
+ this.previous();
+ } else if (key === 'ArrowRight') {
+ this.next();
+ }
+ };
+
+ render() {
+ const { errorRecords, close } = this.props;
+ const totalErrors = errorRecords.length;
+ return (
+
+
+ {totalErrors > 1 &&
+ }
+
+
+
+ );
+ }
+}
+
+export default RuntimeErrorContainer;
diff --git a/packages/react-error-overlay/src/containers/StackFrame.js b/packages/react-error-overlay/src/containers/StackFrame.js
new file mode 100644
index 00000000000..c95ce003f49
--- /dev/null
+++ b/packages/react-error-overlay/src/containers/StackFrame.js
@@ -0,0 +1,188 @@
+/**
+ * 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 CodeBlock from './StackFrameCodeBlock';
+import { getPrettyURL } from '../utils/getPrettyURL';
+import { darkGray } from '../styles';
+
+const linkStyle = {
+ fontSize: '0.9em',
+ marginBottom: '0.9em',
+};
+
+const anchorStyle = {
+ textDecoration: 'none',
+ color: darkGray,
+ cursor: 'pointer',
+};
+
+const codeAnchorStyle = {
+ cursor: 'pointer',
+};
+
+const toggleStyle = {
+ marginBottom: '1.5em',
+ color: darkGray,
+ cursor: 'pointer',
+ border: 'none',
+ display: 'block',
+ width: '100%',
+ textAlign: 'left',
+ background: '#fff',
+ fontFamily: 'Consolas, Menlo, monospace',
+ fontSize: '1em',
+ padding: '0px',
+ lineHeight: '1.5',
+};
+
+class StackFrame extends Component {
+ state = {
+ compiled: false,
+ };
+
+ toggleCompiled = () => {
+ this.setState(state => ({
+ compiled: !state.compiled,
+ }));
+ };
+
+ canOpenInEditor() {
+ if (!this.props.launchEditorEndpoint) {
+ return;
+ }
+ const { _originalFileName: sourceFileName } = this.props.frame;
+ // Unknown file
+ if (!sourceFileName) {
+ return false;
+ }
+ // e.g. "/path-to-my-app/webpack/bootstrap eaddeb46b67d75e4dfc1"
+ const isInternalWebpackBootstrapCode =
+ sourceFileName.trim().indexOf(' ') !== -1;
+ if (isInternalWebpackBootstrapCode) {
+ return false;
+ }
+ // Code is in a real file
+ return true;
+ }
+
+ openInEditor = () => {
+ if (!this.canOpenInEditor()) {
+ return;
+ }
+ const {
+ _originalFileName: sourceFileName,
+ _originalLineNumber: sourceLineNumber,
+ } = this.props.frame;
+ // Keep this in sync with react-error-overlay/middleware.js
+ fetch(
+ `${this.props.launchEditorEndpoint}?fileName=` +
+ window.encodeURIComponent(sourceFileName) +
+ '&lineNumber=' +
+ window.encodeURIComponent(sourceLineNumber || 1)
+ ).then(() => {}, () => {});
+ };
+
+ onKeyDown = (e: SyntheticKeyboardEvent) => {
+ if (e.key === 'Enter') {
+ this.openInEditor();
+ }
+ };
+
+ render() {
+ const { frame, contextSize, critical, showCode } = this.props;
+ const {
+ fileName,
+ lineNumber,
+ columnNumber,
+ _scriptCode: scriptLines,
+ _originalFileName: sourceFileName,
+ _originalLineNumber: sourceLineNumber,
+ _originalColumnNumber: sourceColumnNumber,
+ _originalScriptCode: sourceLines,
+ } = frame;
+ const functionName = frame.getFunctionName();
+
+ const compiled = this.state.compiled;
+ const url = getPrettyURL(
+ sourceFileName,
+ sourceLineNumber,
+ sourceColumnNumber,
+ fileName,
+ lineNumber,
+ columnNumber,
+ compiled
+ );
+
+ let codeBlockProps = null;
+ if (showCode) {
+ if (
+ compiled &&
+ scriptLines &&
+ scriptLines.length !== 0 &&
+ lineNumber != null
+ ) {
+ codeBlockProps = {
+ lines: scriptLines,
+ lineNum: lineNumber,
+ columnNum: columnNumber,
+ contextSize,
+ main: critical,
+ };
+ } else if (
+ !compiled &&
+ sourceLines &&
+ sourceLines.length !== 0 &&
+ sourceLineNumber != null
+ ) {
+ codeBlockProps = {
+ lines: sourceLines,
+ lineNum: sourceLineNumber,
+ columnNum: sourceColumnNumber,
+ contextSize,
+ main: critical,
+ };
+ }
+ }
+
+ const canOpenInEditor = this.canOpenInEditor();
+ return (
+