Commit ecd1f054 authored by Tharaka Wijebandara's avatar Tharaka Wijebandara Committed by Dan Abramov
Browse files

Convert react-error-overlay to React (#2515)

* Convert react-error-overlay to React

* Update compile-time error overlay to use react-error-overlay components

 * Refactor react-error-overlay components to container and presentational components.

 * Make the compile-time error overlay a part of react-error-overlay package.

 * Use react-error-overlay as dependency in react-dev-utils to show compile-time errors.

* Run Prettier

* Move the function name fix into StackFrame itself

* Fix clicking on source code snippet to open the code in editor

* Use exact objects + minor style tweak

* Don't linkify frames that don't exist on the disk

* Fix lint

* Consolidate iframe rendering logic

* Remove circular dependency between react-dev-utils and react-error-overlay

* Fix lint

* Fix decoupling of react-dev-utils and react-error-overlay by moving middleware

* Deduplicate identical errors
parent 3b917482
Showing with 439 additions and 858 deletions
+439 -858
......@@ -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 {
......
......@@ -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';
......@@ -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",
......
......@@ -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 =
'<div style="' +
overlayHeaderStyle() +
'">Failed to compile</div>' +
'<pre style="' +
'display: block; padding: 0.5em; margin-top: 0; ' +
'margin-bottom: 0.5em; overflow-x: auto; white-space: pre-wrap; ' +
'border-radius: 0.25rem; background-color: rgba(206, 17, 38, 0.05)">' +
'<code style="font-family: Consolas, Menlo, monospace;">' +
ansiHTML(message) +
'</code></pre>' +
'<div style="' +
'font-family: sans-serif; color: rgb(135, 142, 145); margin-top: 0.5rem; ' +
'flex: 0 0 auto">' +
'This error occurred during the build time and cannot be dismissed.</div>';
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') {
......
[ignore]
.*/node_modules/eslint-plugin-jsx-a11y/.*
[include]
src/**/*.js
......
......@@ -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.<anonymous>",
"functionName": null,
"fileName": "http://localhost:3000/static/js/bundle.js",
"lineNumber": 41219,
"columnNumber": 18,
"_originalFunctionName": "Object.<anonymous>",
"_originalFunctionName": null,
"_originalFileName": null,
"_originalLineNumber": null,
"_originalColumnNumber": null,
......
......@@ -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"
},
......
......@@ -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', () => {
......
......@@ -8,24 +8,31 @@
*/
/* @flow */
import { applyStyles } from '../utils/dom/css';
import { footerStyle } from '../styles';
import React from 'react';
import { black } 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.'
)
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 (
<span
title="Click or press Escape to dismiss."
onClick={close}
style={closeButtonStyle}
>
×
</span>
);
return div;
}
export { createFooter };
export default CloseButton;
/**
* 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 (
<pre style={preStyle}>
<code style={codeStyle} dangerouslySetInnerHTML={codeBlock} />
</pre>
);
}
export default CodeBlock;
/**
* 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 (
<div>
<button
onClick={this.toggleCollaped}
style={
collapsed ? collapsibleCollapsedStyle : collapsibleExpandedStyle
}
>
{(collapsed ? '' : '') +
` ${count} stack frames were ` +
(collapsed ? 'collapsed.' : 'expanded.')}
</button>
<div style={{ display: collapsed ? 'none' : 'block' }}>
{this.props.children}
<button
onClick={this.toggleCollaped}
style={collapsibleExpandedStyle}
>
{`▲ ${count} stack frames were expanded.`}
</button>
</div>
</div>
);
}
}
export default Collapsible;
/**
* 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 (
<div style={footerStyle}>
{props.line1}
<br />
{props.line2}
</div>
);
}
export default Footer;
/**
* 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 (
<div style={headerStyle}>
{props.headerText}
</div>
);
}
export default Header;
/**
* 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 (
<div style={navigationBarStyle}>
<span style={buttonContainerStyle}>
<button onClick={previous} style={leftButtonStyle}>
</button>
<button onClick={next} style={rightButtonStyle}>
</button>
</span>
{`${currentError} of ${totalErrors} errors on the page`}
</div>
);
}
export default NavigationBar;
/**
* 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 (
<div style={overlayStyle} ref={this.getIframeWindow}>
{this.props.children}
</div>
);
}
}
export default Overlay;
/**
* 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 };
/**
* 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 };
/**
* 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 === '<anonymous>') {
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 };
/**
* 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 };
/**
* 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 };
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment