Add react-error-overlay package (#2111)

* ༼ つ ◕_◕ ༽つ stack-frame-overlay

* Fix linting

* Remove auto overlay

* Fix e2e

* Pull in the rest

* Appease flow

* Correct dep

* Remove old repo references

* Check flow on test

* Test overlay in e2e

* Add cross env

* Rename package

* Make sure it gets built post-install

* Update the README

* Remove extra builds now that there's a postinstall script

* Revert "Remove extra builds now that there's a postinstall script"

This reverts commit 8bf601dbd36c1e0da7f785fa9ade70ab08ed8772.

* Remove broken script

* Fix some dev ergo
This commit is contained in:
Joe Haddad
2017-05-10 19:56:30 -04:00
committed by GitHub
parent 1acc3a45f1
commit 70768b321e
42 changed files with 1987 additions and 1168 deletions

View File

@@ -5,7 +5,7 @@
"changelog": "lerna-changelog",
"create-react-app": "tasks/cra.sh",
"e2e": "tasks/e2e-simple.sh",
"postinstall": "lerna bootstrap",
"postinstall": "lerna bootstrap && cd packages/react-error-overlay/ && npm run build:prod",
"publish": "tasks/release.sh",
"start": "node packages/react-scripts/scripts/start.js",
"test": "node packages/react-scripts/scripts/test.js --env=jsdom",

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
{
"presets": ["react-app"]
}

View File

@@ -0,0 +1,3 @@
{
"extends": "react-app"
}

View File

@@ -0,0 +1,8 @@
[ignore]
[include]
src/**/*.js
[libs]
[options]

View File

@@ -0,0 +1,2 @@
lib/
coverage/

View File

@@ -0,0 +1,3 @@
__tests__
*.test.js
*.spec.js

View File

@@ -0,0 +1,11 @@
# `react-error-overlay`
`react-error-overlay` is an overlay which displays when there is a runtime error.
## Development
When developing within this package, make sure you run `npm start` (or `yarn start`) so that the files are compiled as you work.
This is ran in watch mode by default.
If you would like to build this for production, run `npm run build:prod` (or `yarn build:prod`).<br>
If you would like to build this one-off for development, you can run `NODE_ENV=development npm run build` (or `NODE_ENV=development yarn build`).

View File

@@ -0,0 +1,71 @@
{
"name": "react-error-overlay",
"version": "0.0.0",
"description": "An overlay for displaying stack frames.",
"main": "lib/index.js",
"scripts": {
"prepublishOnly": "npm run build:prod && npm test",
"start": "cross-env NODE_ENV=development npm run build -- --watch",
"test": "flow && jest",
"build": "babel src/ -d lib/",
"build:prod": "cross-env NODE_ENV=production babel src/ -d lib/"
},
"repository": "facebookincubator/create-react-app",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/facebookincubator/create-react-app/issues"
},
"keywords": [
"overlay",
"syntax",
"error",
"red",
"box",
"redbox",
"crash",
"warning"
],
"author": "Joe Haddad <timer150@gmail.com>",
"files": [
"lib/"
],
"dependencies": {
"anser": "^1.2.5",
"babel-code-frame": "^6.22.0",
"babel-runtime": "^6.23.0",
"react-dev-utils": "^0.5.2",
"settle-promise": "^1.0.0"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-eslint": "7.x",
"babel-preset-react-app": "^2.2.0",
"cross-env": "^4.0.0",
"eslint": "^3.16.1",
"eslint-config-react-app": "^0.6.2",
"eslint-plugin-flowtype": "^2.21.0",
"eslint-plugin-import": "^2.0.1",
"eslint-plugin-jsx-a11y": "^4.0.0",
"eslint-plugin-react": "^6.4.1",
"flow-bin": "^0.46.0",
"jest": "19.x"
},
"jest": {
"setupFiles": [
"./src/__tests__/setupJest.js"
],
"collectCoverage": true,
"coverageReporters": [
"json"
],
"testMatch": [
"<rootDir>/src/**/__tests__/**/*.js?(x)",
"<rootDir>/src/**/?(*.)(spec|test).js?(x)"
],
"testPathIgnorePatterns": [
"/node_modules/",
"/fixtures/",
"setupJest.js"
]
}
}

View File

@@ -0,0 +1,52 @@
/* @flow */
import { applyStyles } from '../utils/dom/css';
import { groupStyle, groupElemLeft, groupElemRight } from '../styles';
import { consumeEvent } from '../utils/dom/consumeEvent';
import { enableTabClick } from '../utils/dom/enableTabClick';
type SwitchCallback = (offset: number) => void;
function updateAdditional(
document: Document,
additionalReference: HTMLDivElement,
currentError: number,
totalErrors: number,
switchCallback: SwitchCallback
) {
if (additionalReference.lastChild) {
additionalReference.removeChild(additionalReference.lastChild);
}
let text = ' ';
if (totalErrors <= 1) {
additionalReference.appendChild(document.createTextNode(text));
return;
}
text = `Errors ${currentError} of ${totalErrors}`;
const span = document.createElement('span');
span.appendChild(document.createTextNode(text));
const group = document.createElement('span');
applyStyles(group, groupStyle);
const left = document.createElement('button');
applyStyles(left, groupElemLeft);
left.addEventListener('click', function(e: MouseEvent) {
consumeEvent(e);
switchCallback(-1);
});
left.appendChild(document.createTextNode('←'));
enableTabClick(left);
const right = document.createElement('button');
applyStyles(right, groupElemRight);
right.addEventListener('click', function(e: MouseEvent) {
consumeEvent(e);
switchCallback(1);
});
right.appendChild(document.createTextNode('→'));
enableTabClick(right);
group.appendChild(left);
group.appendChild(right);
span.appendChild(group);
additionalReference.appendChild(span);
}
export type { SwitchCallback };
export { updateAdditional };

View File

@@ -0,0 +1,25 @@
/* @flow */
import { applyStyles } from '../utils/dom/css';
import { hintsStyle, hintStyle, closeButtonStyle } from '../styles';
function createHint(document: Document, hint: string) {
const span = document.createElement('span');
span.appendChild(document.createTextNode(hint));
applyStyles(span, hintStyle);
return span;
}
type CloseCallback = () => void;
function createClose(document: Document, callback: CloseCallback) {
const hints = document.createElement('div');
applyStyles(hints, hintsStyle);
const close = createHint(document, '×');
close.addEventListener('click', () => callback());
applyStyles(close, closeButtonStyle);
hints.appendChild(close);
return hints;
}
export type { CloseCallback };
export { createClose };

View File

@@ -0,0 +1,89 @@
/* @flow */
import type { ScriptLine } from '../utils/stack-frame';
import { applyStyles } from '../utils/dom/css';
import { absolutifyCaret } from '../utils/dom/absolutifyCaret';
import {
preStyle,
codeStyle,
primaryErrorStyle,
secondaryErrorStyle,
} from '../styles';
import generateAnsiHtml from 'react-dev-utils/ansiHTML';
import codeFrame from 'babel-code-frame';
function createCode(
document: Document,
sourceLines: ScriptLine[],
lineNum: number,
columnNum: number | null,
contextSize: number,
main: boolean = false
) {
const sourceCode = [];
let whiteSpace = Infinity;
sourceLines.forEach(function(e) {
const { content: text } = e;
const m = text.match(/^\s*/);
if (text === '') {
return;
}
if (m && m[0]) {
whiteSpace = Math.min(whiteSpace, m[0].length);
} else {
whiteSpace = 0;
}
});
sourceLines.forEach(function(e) {
let { content: text } = e;
const { lineNumber: line } = e;
if (isFinite(whiteSpace)) {
text = text.substring(whiteSpace);
}
sourceCode[line - 1] = text;
});
const ansiHighlight = codeFrame(
sourceCode.join('\n'),
lineNum,
columnNum == null ? 0 : columnNum - (isFinite(whiteSpace) ? whiteSpace : 0),
{
forceColor: true,
linesAbove: contextSize,
linesBelow: contextSize,
}
);
const htmlHighlight = generateAnsiHtml(ansiHighlight);
const code = document.createElement('code');
code.innerHTML = htmlHighlight;
absolutifyCaret(code);
applyStyles(code, codeStyle);
const ccn = code.childNodes;
// eslint-disable-next-line
oLoop: for (let index = 0; index < ccn.length; ++index) {
const node = ccn[index];
const ccn2 = node.childNodes;
for (let index2 = 0; index2 < ccn2.length; ++index2) {
const lineNode = ccn2[index2];
const text = lineNode.innerText;
if (text == null) {
continue;
}
if (text.indexOf(' ' + lineNum + ' |') === -1) {
continue;
}
// $FlowFixMe
applyStyles(node, main ? primaryErrorStyle : secondaryErrorStyle);
// eslint-disable-next-line
break oLoop;
}
}
const pre = document.createElement('pre');
applyStyles(pre, preStyle);
pre.appendChild(code);
return pre;
}
export { createCode };

View File

@@ -0,0 +1,22 @@
/* @flow */
import { applyStyles } from '../utils/dom/css';
import { footerStyle } from '../styles';
function createFooter(document: Document) {
const div = document.createElement('div');
applyStyles(div, footerStyle);
div.appendChild(
document.createTextNode(
'This screen is visible only in development. It will not appear when the app crashes in production.'
)
);
div.appendChild(document.createElement('br'));
div.appendChild(
document.createTextNode(
'Open your browsers developer console to further inspect this error.'
)
);
return div;
}
export { createFooter };

View File

@@ -0,0 +1,232 @@
/* @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 {
omittedFramesStyle,
functionNameStyle,
depStyle,
linkStyle,
anchorStyle,
hiddenStyle,
} from '../styles';
function getGroupToggle(
document: Document,
omitsCount: number,
omitBundle: number
) {
const omittedFrames = document.createElement('div');
enableTabClick(omittedFrames);
const text1 = document.createTextNode(
'\u25B6 ' + omitsCount + ' stack frames were collapsed.'
);
omittedFrames.appendChild(text1);
omittedFrames.addEventListener('click', function() {
const hide = text1.textContent.match(/▲/);
const list = document.getElementsByName('bundle-' + omitBundle);
for (let index = 0; index < list.length; ++index) {
const n = list[index];
if (hide) {
n.style.display = 'none';
} else {
n.style.display = '';
}
}
if (hide) {
text1.textContent = text1.textContent.replace(/▲/, '▶');
text1.textContent = text1.textContent.replace(/expanded/, 'collapsed');
} else {
text1.textContent = text1.textContent.replace(/▶/, '▲');
text1.textContent = text1.textContent.replace(/collapsed/, 'expanded');
}
});
applyStyles(omittedFrames, omittedFramesStyle);
return omittedFrames;
}
function insertBeforeBundle(
document: Document,
parent: Node,
omitsCount: number,
omitBundle: number,
actionElement
) {
const children = document.getElementsByName('bundle-' + omitBundle);
if (children.length < 1) {
return;
}
let first: ?Node = children[0];
while (first != null && first.parentNode !== parent) {
first = first.parentNode;
}
const div = document.createElement('div');
enableTabClick(div);
div.setAttribute('name', 'bundle-' + omitBundle);
const text = document.createTextNode(
'\u25BC ' + omitsCount + ' stack frames were expanded.'
);
div.appendChild(text);
div.addEventListener('click', function() {
return actionElement.click();
});
applyStyles(div, omittedFramesStyle);
div.style.display = 'none';
parent.insertBefore(div, first);
}
function frameDiv(document: Document, functionName, url, internalUrl) {
const frame = document.createElement('div');
const frameFunctionName = document.createElement('div');
let cleanedFunctionName;
if (!functionName || functionName === 'Object.<anonymous>') {
cleanedFunctionName = '(anonymous function)';
} else {
cleanedFunctionName = functionName;
}
const cleanedUrl = url.replace('webpack://', '.');
if (internalUrl) {
applyStyles(
frameFunctionName,
Object.assign({}, functionNameStyle, depStyle)
);
} else {
applyStyles(frameFunctionName, functionNameStyle);
}
frameFunctionName.appendChild(document.createTextNode(cleanedFunctionName));
frame.appendChild(frameFunctionName);
const frameLink = document.createElement('div');
applyStyles(frameLink, linkStyle);
const frameAnchor = document.createElement('a');
applyStyles(frameAnchor, anchorStyle);
frameAnchor.appendChild(document.createTextNode(cleanedUrl));
frameLink.appendChild(frameAnchor);
frame.appendChild(frameLink);
return frame;
}
function createFrame(
document: Document,
frameSetting: FrameSetting,
frame: StackFrame,
contextSize: number,
critical: boolean,
omits: OmitsObject,
omitBundle: number,
parentContainer: HTMLDivElement,
lastElement: boolean
) {
const { compiled } = frameSetting;
const {
functionName,
fileName,
lineNumber,
columnNumber,
_scriptCode: scriptLines,
_originalFileName: sourceFileName,
_originalLineNumber: sourceLineNumber,
_originalColumnNumber: sourceColumnNumber,
_originalScriptCode: sourceLines,
} = frame;
let url;
if (!compiled && sourceFileName && sourceLineNumber) {
url = sourceFileName + ':' + sourceLineNumber;
if (sourceColumnNumber) {
url += ':' + sourceColumnNumber;
}
} else if (fileName && lineNumber) {
url = fileName + ':' + lineNumber;
if (columnNumber) {
url += ':' + columnNumber;
}
} else {
url = 'unknown';
}
let needsHidden = false;
const internalUrl = isInternalFile(url, sourceFileName);
if (internalUrl) {
++omits.value;
needsHidden = true;
}
let collapseElement = null;
if (!internalUrl || lastElement) {
if (omits.value > 0) {
const capV = omits.value;
const omittedFrames = getGroupToggle(document, capV, omitBundle);
window.requestAnimationFrame(() => {
insertBeforeBundle(
document,
parentContainer,
capV,
omitBundle,
omittedFrames
);
});
if (lastElement && internalUrl) {
collapseElement = omittedFrames;
} else {
parentContainer.appendChild(omittedFrames);
}
++omits.bundle;
}
omits.value = 0;
}
const elem = frameDiv(document, functionName, url, internalUrl);
if (needsHidden) {
applyStyles(elem, hiddenStyle);
elem.setAttribute('name', 'bundle-' + omitBundle);
}
let hasSource = false;
if (!internalUrl) {
if (
compiled && scriptLines && scriptLines.length !== 0 && lineNumber != null
) {
elem.appendChild(
createCode(
document,
scriptLines,
lineNumber,
columnNumber,
contextSize,
critical
)
);
hasSource = true;
} else if (
!compiled &&
sourceLines &&
sourceLines.length !== 0 &&
sourceLineNumber != null
) {
elem.appendChild(
createCode(
document,
sourceLines,
sourceLineNumber,
sourceColumnNumber,
contextSize,
critical
)
);
hasSource = true;
}
}
return { elem: elem, hasSource: hasSource, collapseElement: collapseElement };
}
export { createFrame };

View File

@@ -0,0 +1,117 @@
/* @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 };
type FrameSetting = { compiled: boolean };
export type { OmitsObject, FrameSetting };
function createFrameWrapper(
document: Document,
parent: HTMLDivElement,
factory,
lIndex: number,
frameSettings: FrameSetting[],
contextSize: number
) {
const fac = factory();
if (fac == null) {
return;
}
const { hasSource, elem, collapseElement } = fac;
const elemWrapper = document.createElement('div');
elemWrapper.appendChild(elem);
if (hasSource) {
const compiledDiv = document.createElement('div');
enableTabClick(compiledDiv);
applyStyles(compiledDiv, toggleStyle);
const o = frameSettings[lIndex];
const compiledText = document.createTextNode(
'View ' + (o && o.compiled ? 'source' : 'compiled')
);
compiledDiv.addEventListener('click', function() {
if (o) {
o.compiled = !o.compiled;
}
const next = createFrameWrapper(
document,
parent,
factory,
lIndex,
frameSettings,
contextSize
);
if (next != null) {
parent.insertBefore(next, elemWrapper);
parent.removeChild(elemWrapper);
}
});
compiledDiv.appendChild(compiledText);
elemWrapper.appendChild(compiledDiv);
}
if (collapseElement != null) {
elemWrapper.appendChild(collapseElement);
}
return elemWrapper;
}
function createFrames(
document: Document,
resolvedFrames: StackFrame[],
frameSettings: FrameSetting[],
contextSize: number
) {
if (resolvedFrames.length !== frameSettings.length) {
throw new Error(
'You must give a frame settings array of identical length to resolved frames.'
);
}
const trace = document.createElement('div');
applyStyles(trace, traceStyle);
let index = 0;
let critical = true;
const omits: OmitsObject = { value: 0, bundle: 1 };
resolvedFrames.forEach(function(frame) {
const lIndex = index++;
const elem = createFrameWrapper(
document,
trace,
createFrame.bind(
undefined,
document,
frameSettings[lIndex],
frame,
contextSize,
critical,
omits,
omits.bundle,
trace,
index === resolvedFrames.length
),
lIndex,
frameSettings,
contextSize
);
if (elem == null) {
return;
}
critical = false;
trace.appendChild(elem);
});
//TODO: fix this
omits.value = 0;
return trace;
}
export { createFrames };

View File

@@ -0,0 +1,74 @@
/* @flow */
import { applyStyles } from '../utils/dom/css';
import { overlayStyle, headerStyle, additionalStyle } from '../styles';
import { createClose } from './close';
import { createFrames } from './frames';
import { createFooter } from './footer';
import type { CloseCallback } from './close';
import type { StackFrame } from '../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);
overlay.appendChild(createClose(document, closeCallback));
// Create container
const container = document.createElement('div');
container.className = 'cra-container';
overlay.appendChild(container);
// Create additional
const additional = document.createElement('div');
applyStyles(additional, additionalStyle);
container.appendChild(additional);
updateAdditional(
document,
additional,
currentError,
totalErrors,
switchCallback
);
// Create header
const header = document.createElement('div');
applyStyles(header, headerStyle);
if (message.match(/^\w*:/)) {
header.appendChild(document.createTextNode(message));
} else {
header.appendChild(document.createTextNode(name + ': ' + message));
}
container.appendChild(header);
// Create trace
container.appendChild(
createFrames(document, frames, frameSettings, contextSize)
);
// Show message
container.appendChild(createFooter(document));
return {
overlay,
additional,
};
}
export { createOverlay };

View File

@@ -0,0 +1,15 @@
/* @flow */
type ConsoleProxyCallback = (message: string) => void;
const permanentRegister = function proxyConsole(
type: string,
callback: ConsoleProxyCallback
) {
const orig = console[type];
console[type] = function __stack_frame_overlay_proxy_console__() {
const message = [].slice.call(arguments).join(' ');
callback(message);
return orig.apply(this, arguments);
};
};
export { permanentRegister };

View File

@@ -0,0 +1,44 @@
/* @flow */
const SHORTCUT_ESCAPE = 'SHORTCUT_ESCAPE',
SHORTCUT_LEFT = 'SHORTCUT_LEFT',
SHORTCUT_RIGHT = 'SHORTCUT_RIGHT';
let boundKeyHandler = null;
type ShortcutCallback = (type: string) => void;
function keyHandler(callback: ShortcutCallback, e: KeyboardEvent) {
const { key, keyCode, which } = e;
if (key === 'Escape' || keyCode === 27 || which === 27) {
callback(SHORTCUT_ESCAPE);
} else if (key === 'ArrowLeft' || keyCode === 37 || which === 37) {
callback(SHORTCUT_LEFT);
} else if (key === 'ArrowRight' || keyCode === 39 || which === 39) {
callback(SHORTCUT_RIGHT);
}
}
function registerShortcuts(target: EventTarget, callback: ShortcutCallback) {
if (boundKeyHandler !== null) {
return;
}
boundKeyHandler = keyHandler.bind(undefined, callback);
target.addEventListener('keydown', boundKeyHandler);
}
function unregisterShortcuts(target: EventTarget) {
if (boundKeyHandler === null) {
return;
}
target.removeEventListener('keydown', boundKeyHandler);
boundKeyHandler = null;
}
export {
SHORTCUT_ESCAPE,
SHORTCUT_LEFT,
SHORTCUT_RIGHT,
registerShortcuts as register,
unregisterShortcuts as unregister,
keyHandler as handler,
};

View File

@@ -0,0 +1,36 @@
/* @flow */
let stackTraceRegistered: boolean = false;
// Default: https://docs.microsoft.com/en-us/scripting/javascript/reference/stacktracelimit-property-error-javascript
let restoreStackTraceValue: number = 10;
const MAX_STACK_LENGTH: number = 50;
function registerStackTraceLimit(limit: number = MAX_STACK_LENGTH) {
if (stackTraceRegistered) {
return;
}
try {
restoreStackTraceValue = Error.stackTraceLimit;
Error.stackTraceLimit = limit;
stackTraceRegistered = true;
} catch (e) {
// Not all browsers support this so we don't care if it errors
}
}
function unregisterStackTraceLimit() {
if (!stackTraceRegistered) {
return;
}
try {
Error.stackTraceLimit = restoreStackTraceValue;
stackTraceRegistered = false;
} catch (e) {
// Not all browsers support this so we don't care if it errors
}
}
export {
registerStackTraceLimit as register,
unregisterStackTraceLimit as unregister,
};

View File

@@ -0,0 +1,40 @@
/* @flow */
let boundErrorHandler = null;
type ErrorCallback = (error: Error) => void;
function errorHandler(callback: ErrorCallback, e: Event): void {
if (!e.error) {
return;
}
// $FlowFixMe
const { error } = e;
if (error instanceof Error) {
callback(error);
} else {
// A non-error was thrown, we don't have a trace. :(
// Look in your browser's devtools for more information
callback(new Error(error));
}
}
function registerUnhandledError(target: EventTarget, callback: ErrorCallback) {
if (boundErrorHandler !== null) {
return;
}
boundErrorHandler = errorHandler.bind(undefined, callback);
target.addEventListener('error', boundErrorHandler);
}
function unregisterUnhandledError(target: EventTarget) {
if (boundErrorHandler === null) {
return;
}
target.removeEventListener('error', boundErrorHandler);
boundErrorHandler = null;
}
export {
registerUnhandledError as register,
unregisterUnhandledError as unregister,
};

View File

@@ -0,0 +1,46 @@
/* @flow */
let boundRejectionHandler = null;
type ErrorCallback = (error: Error) => void;
function rejectionHandler(
callback: ErrorCallback,
e: PromiseRejectionEvent
): void {
if (e == null || e.reason == null) {
return callback(new Error('Unknown'));
}
let { reason } = e;
if (reason instanceof Error) {
return callback(reason);
}
// A non-error was rejected, we don't have a trace :(
// Look in your browser's devtools for more information
return callback(new Error(reason));
}
function registerUnhandledRejection(
target: EventTarget,
callback: ErrorCallback
) {
if (boundRejectionHandler !== null) {
return;
}
boundRejectionHandler = rejectionHandler.bind(undefined, callback);
// $FlowFixMe
target.addEventListener('unhandledrejection', boundRejectionHandler);
}
function unregisterUnhandledRejection(target: EventTarget) {
if (boundRejectionHandler === null) {
return;
}
// $FlowFixMe
target.removeEventListener('unhandledrejection', boundRejectionHandler);
boundRejectionHandler = null;
}
export {
registerUnhandledRejection as register,
unregisterUnhandledRejection as unregister,
};

View File

@@ -0,0 +1,9 @@
/* @flow */
import { inject, uninject } from './overlay';
inject();
if (module.hot && typeof module.hot.dispose === 'function') {
module.hot.dispose(function() {
uninject();
});
}

View File

@@ -0,0 +1,217 @@
/* @flow */
import {
register as registerError,
unregister as unregisterError,
} from './effects/unhandledError';
import {
register as registerPromise,
unregister as unregisterPromise,
} from './effects/unhandledRejection';
import {
register as registerShortcuts,
unregister as unregisterShortcuts,
handler as keyEventHandler,
SHORTCUT_ESCAPE,
SHORTCUT_LEFT,
SHORTCUT_RIGHT,
} from './effects/shortcuts';
import {
register as registerStackTraceLimit,
unregister as unregisterStackTraceLimit,
} from './effects/stackTraceLimit';
import {
consume as consumeError,
getErrorRecord,
drain as drainErrors,
} from './utils/errorRegister';
import type { ErrorRecordReference } from './utils/errorRegister';
import type { StackFrame } from './utils/stack-frame';
import { iframeStyle } from './styles';
import { injectCss, applyStyles } from './utils/dom/css';
import { createOverlay } from './components/overlay';
import { updateAdditional } from './components/additional';
const CONTEXT_SIZE: number = 3;
let iframeReference: HTMLIFrameElement | null = null;
let additionalReference = null;
let errorReferences: ErrorRecordReference[] = [];
let currReferenceIndex: number = -1;
const css = [
'.cra-container {',
' padding-right: 15px;',
' padding-left: 15px;',
' margin-right: auto;',
' margin-left: auto;',
'}',
'',
'@media (min-width: 768px) {',
' .cra-container {',
' width: calc(750px - 6em);',
' }',
'}',
'',
'@media (min-width: 992px) {',
' .cra-container {',
' width: calc(970px - 6em);',
' }',
'}',
'',
'@media (min-width: 1200px) {',
' .cra-container {',
' width: calc(1170px - 6em);',
' }',
'}',
].join('\n');
function render(name: string, message: string, resolvedFrames: StackFrame[]) {
disposeCurrentView();
const iframe = window.document.createElement('iframe');
applyStyles(iframe, iframeStyle);
iframeReference = iframe;
iframe.onload = () => {
if (iframeReference == null) {
return;
}
const w = iframeReference.contentWindow;
const document = iframeReference.contentDocument;
const { overlay, additional } = createOverlay(
document,
name,
message,
resolvedFrames,
CONTEXT_SIZE,
currReferenceIndex + 1,
errorReferences.length,
offset => {
switchError(offset);
},
() => {
unmount();
}
);
if (w != null) {
w.onkeydown = event => {
keyEventHandler(type => shortcutHandler(type), event);
};
}
injectCss(iframeReference.contentDocument, css);
if (document.body != null) {
document.body.appendChild(overlay);
}
additionalReference = additional;
};
window.document.body.appendChild(iframe);
}
function renderErrorByIndex(index: number) {
currReferenceIndex = index;
const { error, unhandledRejection, enhancedFrames } = getErrorRecord(
errorReferences[index]
);
if (unhandledRejection) {
render(
'Unhandled Rejection (' + error.name + ')',
error.message,
enhancedFrames
);
} else {
render(error.name, error.message, enhancedFrames);
}
}
function switchError(offset) {
const nextView = currReferenceIndex + offset;
if (nextView < 0 || nextView >= errorReferences.length) {
return;
}
renderErrorByIndex(nextView);
}
function disposeCurrentView() {
if (iframeReference === null) {
return;
}
window.document.body.removeChild(iframeReference);
iframeReference = null;
additionalReference = null;
}
function unmount() {
disposeCurrentView();
drainErrors();
errorReferences = [];
currReferenceIndex = -1;
}
function crash(error: Error, unhandledRejection = false) {
if (module.hot && typeof module.hot.decline === 'function') {
module.hot.decline();
}
consumeError(error, unhandledRejection, CONTEXT_SIZE)
.then(ref => {
errorReferences.push(ref);
if (iframeReference !== null && additionalReference !== null) {
updateAdditional(
iframeReference.contentDocument,
additionalReference,
currReferenceIndex + 1,
errorReferences.length,
offset => {
switchError(offset);
}
);
} else {
if (errorReferences.length !== 1) {
throw new Error('Something is *really* wrong.');
}
renderErrorByIndex((currReferenceIndex = 0));
}
})
.catch(e => {
console.log('Could not consume error:', e);
});
}
function shortcutHandler(type: string) {
switch (type) {
case SHORTCUT_ESCAPE: {
unmount();
break;
}
case SHORTCUT_LEFT: {
switchError(-1);
break;
}
case SHORTCUT_RIGHT: {
switchError(1);
break;
}
default: {
//TODO: this
break;
}
}
}
function inject() {
registerError(window, error => crash(error));
registerPromise(window, error => crash(error, true));
registerShortcuts(window, shortcutHandler);
registerStackTraceLimit();
}
function uninject() {
unregisterStackTraceLimit();
unregisterShortcuts(window);
unregisterPromise(window);
unregisterError(window);
}
export { inject, uninject };

View File

@@ -0,0 +1,186 @@
/* @flow */
const black = '#293238',
darkGray = '#878e91',
lightGray = '#fafafa',
red = '#ce1126',
lightRed = '#fccfcf',
yellow = '#fbf5b4';
const iframeStyle = {
'background-color': lightGray,
position: 'fixed',
top: '1em',
left: '1em',
bottom: '1em',
right: '1em',
width: 'calc(100% - 2em)',
height: 'calc(100% - 2em)',
border: 'none',
'border-radius': '3px',
'box-shadow': '0 0 6px 0 rgba(0, 0, 0, 0.5)',
'z-index': 1337,
};
const overlayStyle = {
'box-sizing': 'border-box',
padding: '4rem',
'font-family': 'Consolas, Menlo, monospace',
color: black,
'white-space': 'pre-wrap',
overflow: 'auto',
'overflow-x': 'hidden',
'word-break': 'break-word',
'line-height': 1.5,
};
const hintsStyle = {
'font-size': '0.8em',
'margin-top': '-3em',
'margin-bottom': '3em',
'text-align': 'right',
color: darkGray,
};
const hintStyle = {
padding: '0.5em 1em',
cursor: 'pointer',
};
const closeButtonStyle = {
'font-size': '26px',
color: black,
padding: '0.5em 1em',
cursor: 'pointer',
position: 'absolute',
right: 0,
top: 0,
};
const additionalStyle = {
'margin-bottom': '1.5em',
'margin-top': '-4em',
};
const headerStyle = {
'font-size': '1.7em',
'font-weight': 'bold',
color: red,
};
const functionNameStyle = {
'margin-top': '1em',
'font-size': '1.2em',
};
const linkStyle = {
'font-size': '0.9em',
};
const anchorStyle = {
'text-decoration': 'none',
color: darkGray,
};
const traceStyle = {
'font-size': '1em',
};
const depStyle = {
'font-size': '1.2em',
};
const primaryErrorStyle = {
'background-color': lightRed,
};
const secondaryErrorStyle = {
'background-color': yellow,
};
const omittedFramesStyle = {
color: black,
'font-size': '0.9em',
margin: '1.5em 0',
cursor: 'pointer',
};
const preStyle = {
display: 'block',
padding: '0.5em',
'margin-top': '1.5em',
'margin-bottom': '0px',
'overflow-x': 'auto',
'font-size': '1.1em',
'white-space': 'pre',
};
const toggleStyle = {
'margin-bottom': '1.5em',
color: darkGray,
cursor: 'pointer',
};
const codeStyle = {
'font-family': 'Consolas, Menlo, monospace',
};
const hiddenStyle = {
display: 'none',
};
const groupStyle = {
'margin-left': '1em',
};
const _groupElemStyle = {
'background-color': 'inherit',
'border-color': '#ddd',
'border-width': '1px',
'border-radius': '4px',
'border-style': 'solid',
padding: '3px 6px',
cursor: 'pointer',
};
const groupElemLeft = Object.assign({}, _groupElemStyle, {
'border-top-right-radius': '0px',
'border-bottom-right-radius': '0px',
'margin-right': '0px',
});
const groupElemRight = Object.assign({}, _groupElemStyle, {
'border-top-left-radius': '0px',
'border-bottom-left-radius': '0px',
'margin-left': '-1px',
});
const footerStyle = {
'text-align': 'center',
color: darkGray,
};
export {
iframeStyle,
overlayStyle,
hintsStyle,
hintStyle,
closeButtonStyle,
additionalStyle,
headerStyle,
functionNameStyle,
linkStyle,
anchorStyle,
traceStyle,
depStyle,
primaryErrorStyle,
secondaryErrorStyle,
omittedFramesStyle,
preStyle,
toggleStyle,
codeStyle,
hiddenStyle,
groupStyle,
groupElemLeft,
groupElemRight,
footerStyle,
};

View File

@@ -0,0 +1,34 @@
/* @flow */
function removeNextBr(parent, component: ?Element) {
while (component != null && component.tagName.toLowerCase() !== 'br') {
component = component.nextElementSibling;
}
if (component != null) {
parent.removeChild(component);
}
}
function absolutifyCaret(component: Node) {
const ccn = component.childNodes;
for (let index = 0; index < ccn.length; ++index) {
const c = ccn[index];
// $FlowFixMe
if (c.tagName.toLowerCase() !== 'span') {
continue;
}
const _text = c.innerText;
if (_text == null) {
continue;
}
const text = _text.replace(/\s/g, '');
if (text !== '|^') {
continue;
}
// $FlowFixMe
c.style.position = 'absolute';
// $FlowFixMe
removeNextBr(component, c);
}
}
export { absolutifyCaret };

View File

@@ -0,0 +1,9 @@
/* @flow */
function consumeEvent(e: Event) {
e.preventDefault();
if (typeof e.target.blur === 'function') {
e.target.blur();
}
}
export { consumeEvent };

View File

@@ -0,0 +1,40 @@
/* @flow */
let injectedCount = 0;
const injectedCache = {};
function getHead(document: Document) {
return document.head || document.getElementsByTagName('head')[0];
}
function injectCss(document: Document, css: string): number {
const head = getHead(document);
const style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(css));
head.appendChild(style);
injectedCache[++injectedCount] = style;
return injectedCount;
}
function removeCss(document: Document, ref: number) {
if (injectedCache[ref] == null) {
return;
}
const head = getHead(document);
head.removeChild(injectedCache[ref]);
delete injectedCache[ref];
}
function applyStyles(element: HTMLElement, styles: Object) {
element.setAttribute('style', '');
for (const key in styles) {
if (!styles.hasOwnProperty(key)) {
continue;
}
// $FlowFixMe
element.style[key] = styles[key];
}
}
export { getHead, injectCss, removeCss, applyStyles };

View File

@@ -0,0 +1,15 @@
/* @flow */
function enableTabClick(node: Element) {
node.setAttribute('tabindex', '0');
node.addEventListener('keydown', function(e: KeyboardEvent) {
const { key, which, keyCode } = e;
if (key === 'Enter' || which === 13 || keyCode === 13) {
e.preventDefault();
if (typeof e.target.click === 'function') {
e.target.click();
}
}
});
}
export { enableTabClick };

View File

@@ -0,0 +1,64 @@
/* @flow */
import type { StackFrame } from './stack-frame';
import { parse } from './parser';
import { map } from './mapper';
import { unmap } from './unmapper';
type ErrorRecord = {
error: Error,
unhandledRejection: boolean,
contextSize: number,
enhancedFrames: StackFrame[],
};
type ErrorRecordReference = number;
const recorded: ErrorRecord[] = [];
let errorsConsumed: ErrorRecordReference = 0;
function consume(
error: Error,
unhandledRejection: boolean = false,
contextSize: number = 3
): Promise<ErrorRecordReference> {
const parsedFrames = parse(error);
let enhancedFramesPromise;
if (error.__unmap_source) {
enhancedFramesPromise = unmap(
// $FlowFixMe
error.__unmap_source,
parsedFrames,
contextSize
);
} else {
enhancedFramesPromise = map(parsedFrames, contextSize);
}
return enhancedFramesPromise.then(enhancedFrames => {
enhancedFrames = enhancedFrames.filter(
({ functionName }) =>
functionName == null ||
functionName.indexOf('__stack_frame_overlay_proxy_console__') === -1
);
recorded[++errorsConsumed] = {
error,
unhandledRejection,
contextSize,
enhancedFrames,
};
return errorsConsumed;
});
}
function getErrorRecord(ref: ErrorRecordReference): ErrorRecord {
return recorded[ref];
}
function drain() {
// $FlowFixMe
const keys = Object.keys(recorded);
for (let index = 0; index < keys.length; ++index) {
delete recorded[keys[index]];
}
}
export { consume, getErrorRecord, drain };
export type { ErrorRecordReference };

View File

@@ -0,0 +1,30 @@
//@flow
import { ScriptLine } from './stack-frame';
/**
*
* @param {number} line The line number to provide context around.
* @param {number} count The number of lines you'd like for context.
* @param {string[] | string} lines The source code.
*/
function getLinesAround(
line: number,
count: number,
lines: string[] | string
): ScriptLine[] {
if (typeof lines === 'string') {
lines = lines.split('\n');
}
const result = [];
for (
let index = Math.max(0, line - 1 - count);
index <= Math.min(lines.length - 1, line - 1 + count);
++index
) {
result.push(new ScriptLine(index + 1, lines[index], index === line - 1));
}
return result;
}
export { getLinesAround };
export default getLinesAround;

View File

@@ -0,0 +1,118 @@
//@flow
import { SourceMapConsumer } from 'source-map';
/**
* A wrapped instance of a <code>{@link https://github.com/mozilla/source-map SourceMapConsumer}</code>.
*
* This exposes methods which will be indifferent to changes made in <code>{@link https://github.com/mozilla/source-map source-map}</code>.
*/
class SourceMap {
__source_map: SourceMapConsumer;
constructor(sourceMap) {
this.__source_map = sourceMap;
}
/**
* Returns the original code position for a generated code position.
* @param {number} line The line of the generated code position.
* @param {number} column The column of the generated code position.
*/
getOriginalPosition(
line: number,
column: number
): { source: string, line: number, column: number } {
const {
line: l,
column: c,
source: s,
} = this.__source_map.originalPositionFor({
line,
column,
});
return { line: l, column: c, source: s };
}
/**
* Returns the generated code position for an original position.
* @param {string} source The source file of the original code position.
* @param {number} line The line of the original code position.
* @param {number} column The column of the original code position.
*/
getGeneratedPosition(
source: string,
line: number,
column: number
): { line: number, column: number } {
const { line: l, column: c } = this.__source_map.generatedPositionFor({
source,
line,
column,
});
return {
line: l,
column: c,
};
}
/**
* Returns the code for a given source file name.
* @param {string} sourceName The name of the source file.
*/
getSource(sourceName: string): string {
return this.__source_map.sourceContentFor(sourceName);
}
getSources(): string[] {
return this.__source_map.sources;
}
}
function extractSourceMapUrl(fileUri: string, fileContents: string) {
const regex = /\/\/[#@] ?sourceMappingURL=([^\s'"]+)\s*$/gm;
let match = null;
for (;;) {
let next = regex.exec(fileContents);
if (next == null) {
break;
}
match = next;
}
if (!(match && match[1])) {
return Promise.reject(`Cannot find a source map directive for ${fileUri}.`);
}
return Promise.resolve(match[1].toString());
}
/**
* Returns an instance of <code>{@link SourceMap}</code> for a given fileUri and fileContents.
* @param {string} fileUri The URI of the source file.
* @param {string} fileContents The contents of the source file.
*/
async function getSourceMap(
fileUri: string,
fileContents: string
): Promise<SourceMap> {
let sm = await extractSourceMapUrl(fileUri, fileContents);
if (sm.indexOf('data:') === 0) {
const base64 = /^data:application\/json;([\w=:"-]+;)*base64,/;
const match2 = sm.match(base64);
if (!match2) {
throw new Error(
'Sorry, non-base64 inline source-map encoding is not supported.'
);
}
sm = sm.substring(match2[0].length);
sm = window.atob(sm);
sm = JSON.parse(sm);
return new SourceMap(new SourceMapConsumer(sm));
} else {
const index = fileUri.lastIndexOf('/');
const url = fileUri.substring(0, index + 1) + sm;
const obj = await fetch(url).then(res => res.json());
return new SourceMap(new SourceMapConsumer(obj));
}
}
export { extractSourceMapUrl, getSourceMap };
export default getSourceMap;

View File

@@ -0,0 +1,10 @@
/* @flow */
function isInternalFile(url: string, sourceFileName: string | null | void) {
return url.indexOf('/~/') !== -1 ||
url.indexOf('/node_modules/') !== -1 ||
url.trim().indexOf(' ') !== -1 ||
sourceFileName == null ||
sourceFileName.length === 0;
}
export { isInternalFile };

View File

@@ -0,0 +1,60 @@
// @flow
import StackFrame from './stack-frame';
import { getSourceMap } from './getSourceMap';
import { getLinesAround } from './getLinesAround';
import { settle } from 'settle-promise';
/**
* Enhances a set of <code>StackFrame</code>s with their original positions and code (when available).
* @param {StackFrame[]} frames A set of <code>StackFrame</code>s which contain (generated) code positions.
* @param {number} [contextLines=3] The number of lines to provide before and after the line specified in the <code>StackFrame</code>.
*/
async function map(
frames: StackFrame[],
contextLines: number = 3
): Promise<StackFrame[]> {
const cache: any = {};
const files: string[] = [];
frames.forEach(frame => {
const { fileName } = frame;
if (fileName == null) return;
if (files.indexOf(fileName) !== -1) {
return;
}
files.push(fileName);
});
await settle(
files.map(async fileName => {
const fileSource = await fetch(fileName).then(r => r.text());
const map = await getSourceMap(fileName, fileSource);
cache[fileName] = { fileSource, map };
})
);
return frames.map(frame => {
const { functionName, fileName, lineNumber, columnNumber } = frame;
let { map, fileSource } = cache[fileName] || {};
if (map == null || lineNumber == null) {
return frame;
}
const { source, line, column } = map.getOriginalPosition(
lineNumber,
columnNumber
);
const originalSource = source == null ? [] : map.getSource(source);
return new StackFrame(
functionName,
fileName,
lineNumber,
columnNumber,
getLinesAround(lineNumber, contextLines, fileSource),
functionName,
source,
line,
column,
getLinesAround(line, contextLines, originalSource)
);
});
}
export { map };
export default map;

View File

@@ -0,0 +1,78 @@
// @flow
import StackFrame from './stack-frame';
const regexExtractLocation = /\(?(.+?)(?::(\d+))?(?::(\d+))?\)?$/;
function extractLocation(token: string): [string, number, number] {
return regexExtractLocation.exec(token).slice(1).map(v => {
const p = Number(v);
if (!isNaN(p)) {
return p;
}
return v;
});
}
const regexValidFrame_Chrome = /^\s*(at|in)\s.+(:\d+)/;
const regexValidFrame_FireFox = /(^|@)\S+:\d+|.+line\s+\d+\s+>\s+(eval|Function).+/;
function parseStack(stack: string[]): StackFrame[] {
const frames = stack
.filter(
e => regexValidFrame_Chrome.test(e) || regexValidFrame_FireFox.test(e)
)
.map(e => {
if (regexValidFrame_FireFox.test(e)) {
// Strip eval, we don't care about it
let isEval = false;
if (/ > (eval|Function)/.test(e)) {
e = e.replace(
/ line (\d+)(?: > eval line \d+)* > (eval|Function):\d+:\d+/g,
':$1'
);
isEval = true;
}
const data = e.split(/[@]/g);
const last = data.pop();
return new StackFrame(
data.join('@') || (isEval ? 'eval' : null),
...extractLocation(last)
);
} else {
// Strip eval, we don't care about it
if (e.indexOf('(eval ') !== -1) {
e = e.replace(/(\(eval at [^()]*)|(\),.*$)/g, '');
}
if (e.indexOf('(at ') !== -1) {
e = e.replace(/\(at /, '(');
}
const data = e.trim().split(/\s+/g).slice(1);
const last = data.pop();
return new StackFrame(data.join(' ') || null, ...extractLocation(last));
}
});
return frames;
}
/**
* Turns an <code>Error</code>, or similar object, into a set of <code>StackFrame</code>s.
* @alias parse
*/
function parseError(error: Error | string | string[]): StackFrame[] {
if (error == null) {
throw new Error('You cannot pass a null object.');
}
if (typeof error === 'string') {
return parseStack(error.split('\n'));
}
if (Array.isArray(error)) {
return parseStack(error);
}
if (typeof error.stack === 'string') {
return parseStack(error.stack.split('\n'));
}
throw new Error('The error you provided does not contain a stack trace.');
}
export { parseError as parse };
export default parseError;

View File

@@ -0,0 +1,101 @@
//@flow
/** A container holding a script line. */
class ScriptLine {
/** The line number of this line of source. */
lineNumber: number;
/** The content (or value) of this line of source. */
content: string;
/** Whether or not this line should be highlighted. Particularly useful for error reporting with context. */
highlight: boolean;
constructor(lineNumber: number, content: string, highlight: boolean = false) {
this.lineNumber = lineNumber;
this.content = content;
this.highlight = highlight;
}
}
/**
* A representation of a stack frame.
*/
class StackFrame {
functionName: string | null;
fileName: string | null;
lineNumber: number | null;
columnNumber: number | null;
_originalFunctionName: string | null;
_originalFileName: string | null;
_originalLineNumber: number | null;
_originalColumnNumber: number | null;
_scriptCode: ScriptLine[] | null;
_originalScriptCode: ScriptLine[] | null;
constructor(
functionName: string | null = null,
fileName: string | null = null,
lineNumber: number | null = null,
columnNumber: number | null = null,
scriptCode: ScriptLine[] | null = null,
sourceFunctionName: string | null = null,
sourceFileName: string | null = null,
sourceLineNumber: number | null = null,
sourceColumnNumber: number | null = null,
sourceScriptCode: ScriptLine[] | null = null
) {
this.functionName = functionName;
this.fileName = fileName;
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
this._originalFunctionName = sourceFunctionName;
this._originalFileName = sourceFileName;
this._originalLineNumber = sourceLineNumber;
this._originalColumnNumber = sourceColumnNumber;
this._scriptCode = scriptCode;
this._originalScriptCode = sourceScriptCode;
}
/**
* Returns the name of this function.
*/
getFunctionName(): string | null {
return this.functionName;
}
/**
* Returns the source of the frame.
* This contains the file name, line number, and column number when available.
*/
getSource(): string {
let str = '';
if (this.fileName != null) {
str += this.fileName + ':';
}
if (this.lineNumber != null) {
str += this.lineNumber + ':';
}
if (this.columnNumber != null) {
str += this.columnNumber + ':';
}
return str.slice(0, -1);
}
/**
* Returns a pretty version of this stack frame.
*/
toString(): string {
const f = this.getFunctionName();
if (f == null) {
return this.getSource();
}
return `${f} (${this.getSource()})`;
}
}
export { StackFrame, ScriptLine };
export default StackFrame;

View File

@@ -0,0 +1,102 @@
// @flow
import StackFrame from './stack-frame';
import { getSourceMap } from './getSourceMap';
import { getLinesAround } from './getLinesAround';
import path from 'path';
/**
* Turns a set of mapped <code>StackFrame</code>s back into their generated code position and enhances them with code.
* @param {string} fileUri The URI of the <code>bundle.js</code> file.
* @param {StackFrame[]} frames A set of <code>StackFrame</code>s which are already mapped and missing their generated positions.
* @param {number} [fileContents=3] The number of lines to provide before and after the line specified in the <code>StackFrame</code>.
*/
async function unmap(
_fileUri: string | { uri: string, contents: string },
frames: StackFrame[],
contextLines: number = 3
): Promise<StackFrame[]> {
let fileContents = typeof _fileUri === 'object' ? _fileUri.contents : null;
let fileUri = typeof _fileUri === 'object' ? _fileUri.uri : _fileUri;
if (fileContents == null) {
fileContents = await fetch(fileUri).then(res => res.text());
}
const map = await getSourceMap(fileUri, fileContents);
return frames.map(frame => {
const {
functionName,
lineNumber,
columnNumber,
_originalLineNumber,
} = frame;
if (_originalLineNumber != null) {
return frame;
}
let { fileName } = frame;
if (fileName) {
fileName = path.normalize(fileName);
}
if (fileName == null) {
return frame;
}
const fN: string = fileName;
const splitCache1: any = {}, splitCache2: any = {}, splitCache3: any = {};
const source = map
.getSources()
.map(s => s.replace(/[\\]+/g, '/'))
.filter(s => {
s = path.normalize(s);
return s.indexOf(fN) === s.length - fN.length;
})
.sort((a, b) => {
let a2 = splitCache1[a] || (splitCache1[a] = a.split(path.sep)),
b2 = splitCache1[b] || (splitCache1[b] = b.split(path.sep));
return Math.sign(a2.length - b2.length);
})
.sort((a, b) => {
let a2 = splitCache2[a] || (splitCache2[a] = a.split('node_modules')),
b2 = splitCache2[b] || (splitCache2[b] = b.split('node_modules'));
return Math.sign(a2.length - b2.length);
})
.sort((a, b) => {
let a2 = splitCache3[a] || (splitCache3[a] = a.split('~')),
b2 = splitCache3[b] || (splitCache3[b] = b.split('~'));
return Math.sign(a2.length - b2.length);
});
if (source.length < 1 || lineNumber == null) {
return new StackFrame(
null,
null,
null,
null,
null,
functionName,
fN,
lineNumber,
columnNumber,
null
);
}
const { line, column } = map.getGeneratedPosition(
source[0],
lineNumber,
// $FlowFixMe
columnNumber
);
const originalSource = map.getSource(source[0]);
return new StackFrame(
functionName,
fileUri,
line,
column || null,
getLinesAround(line, contextLines, fileContents || []),
functionName,
fN,
lineNumber,
columnNumber,
getLinesAround(lineNumber, contextLines, originalSource)
);
});
}
export { unmap };
export default unmap;

View File

@@ -55,7 +55,7 @@ module.exports = {
// We ship a few polyfills by default:
require.resolve('./polyfills'),
// Errors should be considered fatal in development
require.resolve('react-dev-utils/crashOverlay'),
require.resolve('react-error-overlay'),
// Finally, this is your app's code:
paths.appIndexJs,
// We include the app code last so that if there is a runtime error during

View File

@@ -52,6 +52,7 @@
"postcss-loader": "1.3.3",
"promise": "7.1.1",
"react-dev-utils": "^0.5.2",
"react-error-overlay": "^0.0.0",
"style-loader": "0.16.1",
"url-loader": "0.5.8",
"webpack": "2.4.1",

View File

@@ -99,6 +99,10 @@ fi
# We removed the postinstall, so do it manually
./node_modules/.bin/lerna bootstrap --concurrency=1
cd packages/react-error-overlay/
npm run build:prod
cd ../..
# ******************************************************************************
# First, pack and install create-react-app.
# ******************************************************************************

View File

@@ -82,6 +82,10 @@ fi
# We removed the postinstall, so do it manually
./node_modules/.bin/lerna bootstrap --concurrency=1
cd packages/react-error-overlay/
npm run build:prod
cd ../..
# ******************************************************************************
# First, pack react-scripts and create-react-app so we can use them.
# ******************************************************************************

View File

@@ -98,7 +98,16 @@ then
fi
# Lint own code
./node_modules/.bin/eslint --max-warnings 0 .
./node_modules/.bin/eslint --max-warnings 0 packages/babel-preset-react-app/
./node_modules/.bin/eslint --max-warnings 0 packages/create-react-app/
./node_modules/.bin/eslint --max-warnings 0 packages/eslint-config-react-app/
./node_modules/.bin/eslint --max-warnings 0 packages/react-dev-utils/
./node_modules/.bin/eslint --max-warnings 0 packages/react-scripts/
cd packages/react-error-overlay/
./node_modules/.bin/eslint --max-warnings 0 src/
npm test
npm run build:prod
cd ../..
# ******************************************************************************
# First, test the create-react-app development environment.