Files
create-react-app/packages/react-dev-utils/failFast.js
2016-11-29 21:23:31 -06:00

333 lines
9.5 KiB
JavaScript

(function() {
const codeFrame = require('babel-code-frame')
const ansiHTML = require('./ansiHTML')
const StackTraceResolve = require('stacktrace-resolve').default
const CONTEXT_SIZE = 3
const black = '#293238'
const darkGray = '#878e91'
const lightGray = '#fafafa'
const red = '#ce1126'
const overlayStyle = {
position: 'fixed',
'box-sizing': 'border-box',
top: '1em', left: '1em',
bottom: '1em', right: '1em',
width: 'calc(100% - 2em)', height: 'calc(100% - 2em)',
'border-radius': '3px',
'background-color': lightGray,
padding: '4rem',
'z-index': 1337,
'font-family': 'Consolas, Menlo, monospace',
color: black,
'white-space': 'pre-wrap',
overflow: 'auto',
'box-shadow': '0 0 6px 0 rgba(0, 0, 0, 0.5)',
'line-height': 1.5,
}
// TODO: reapply containerStyle on resize or externalize to css and ensure
// e2e checks & tests pass
// ref commit: 46db1ae54c0449b737f82fb1cf8a47b7457d5b9b
const containerStyle = {
'margin': '0 auto',
width: () => calcWidth(window.innerWidth)
}
const hintsStyle = {
'font-size': '0.8em',
'margin-top': '-3em',
'margin-bottom': '3em',
'text-align': 'right',
color: darkGray
}
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 omittedFramesStyle = {
color: black,
'font-size': '0.9em',
'margin': '1.5em 0',
}
const preStyle = {
display: 'block',
padding: '0.5em',
margin: '1.5em 0',
'overflow-x': 'auto',
'font-size': '1.1em'
}
const codeStyle = {
'font-family': 'Consolas, Menlo, monospace',
}
function calcWidth(width) {
if (width >= 1200) return '1170px'
if (width >= 992) return '970px'
if (width >= 768) return '750px'
return 'auto'
}
function applyStyles(element, styles) {
element.setAttribute('style', '')
// Firefox can't handle const due to non-compliant implementation
// Revisit Jan 2016
// https://developer.mozilla.org/en-US/Firefox/Releases/51#JavaScript
// https://bugzilla.mozilla.org/show_bug.cgi?id=1101653
for (let key in styles) {
if (!styles.hasOwnProperty(key)) continue
let val = styles[key]
if (typeof val === 'function') val = val()
element.style[key] = val.toString()
}
}
let overlayReference = null
let errorCache = null
let additionalCount = 0
let internalDisabled = true
function toggleInternal() {
internalDisabled = !internalDisabled
if (errorCache != null) {
unmount()
crash(errorCache)
}
}
function renderAdditional() {
++additionalCount
const title = overlayReference.childNodes[1].childNodes[0]
const children = title.childNodes
const text = document.createTextNode(` (+${additionalCount} more)`)
if (children.length < 2) {
title.appendChild(text)
} else {
title.removeChild(children[children.length - 1])
title.appendChild(text)
}
}
function sourceCodePre(sourceLines, lineNum, columnNum) {
let sourceCode = []
sourceLines.forEach(({ text, line }) => {
sourceCode[line - 1] = text
})
sourceCode = sourceCode.join('\n')
const ansiHighlight = codeFrame(sourceCode, lineNum, columnNum, {
highlightCode: true,
linesAbove: CONTEXT_SIZE,
linesBelow: CONTEXT_SIZE
})
const htmlHighlight = ansiHTML(ansiHighlight)
const code = document.createElement('code')
code.innerHTML = htmlHighlight
applyStyles(code, codeStyle)
const pre = document.createElement('pre')
applyStyles(pre, preStyle)
pre.appendChild(code)
return pre
}
function render(error, name, message, resolvedFrames) {
if (overlayReference !== null) {
renderAdditional()
return
}
// Create overlay
const overlay = document.createElement('div')
applyStyles(overlay, overlayStyle)
const hints = document.createElement('div')
hints.appendChild(document.createTextNode(`[i] ${internalDisabled ? 'Show' : 'Hide'} internal calls`))
hints.appendChild(document.createTextNode('\t\t'))
hints.appendChild(document.createTextNode('[escape] Close'))
applyStyles(hints, hintsStyle)
overlay.appendChild(hints)
const container = document.createElement('div')
applyStyles(container, containerStyle)
overlay.appendChild(container)
// Create header
const header = document.createElement('div')
applyStyles(header, headerStyle)
header.appendChild(document.createTextNode(`${name}: ${message}`))
container.appendChild(header)
// Show trace
const trace = document.createElement('div')
applyStyles(trace, traceStyle)
// Firefox can't handle const due to non-compliant implementation
// Revisit Jan 2016
// https://developer.mozilla.org/en-US/Firefox/Releases/51#JavaScript
// https://bugzilla.mozilla.org/show_bug.cgi?id=1101653
let omittedFramesCount = 0
const appendOmittedFrames = () => {
if (!omittedFramesCount) return
const omittedFrames = document.createElement('div')
omittedFrames.appendChild(document.createTextNode(`---[ ${omittedFramesCount} internal calls hidden ]---`))
applyStyles(omittedFrames, omittedFramesStyle)
trace.appendChild(omittedFrames)
omittedFramesCount = 0
}
for (let frame of resolvedFrames) {
const {
functionName,
fileName, lineNumber, columnNumber,
_scriptLines,
sourceFileName, sourceLineNumber, sourceColumnNumber,
sourceLines
} = frame
let url
if (sourceFileName) {
url = sourceFileName + ':' + sourceLineNumber
if (sourceColumnNumber) url += ':' + sourceColumnNumber
} else {
url = fileName + ':' + lineNumber
if (columnNumber) url += ':' + columnNumber
}
const internalUrl = isInternalFile(url)
if (internalUrl && internalDisabled) {
omittedFramesCount++
continue
}
appendOmittedFrames()
const elem = document.createElement('div')
const elemFunctionName = document.createElement('div')
if (internalUrl) {
applyStyles(elemFunctionName, Object.assign({}, functionNameStyle, depStyle))
} else {
applyStyles(elemFunctionName, functionNameStyle)
}
elemFunctionName.appendChild(document.createTextNode(functionName || '(anonymous function)'))
elem.appendChild(elemFunctionName)
const elemLink = document.createElement('div')
applyStyles(elemLink, linkStyle)
const elemAnchor = document.createElement('a')
applyStyles(elemAnchor, anchorStyle)
//elemAnchor.href = url
elemAnchor.appendChild(document.createTextNode(url.replace('webpack://', '.')))
elemLink.appendChild(elemAnchor)
elem.appendChild(elemLink)
if (!internalUrl && sourceLines.length !== 0) {
elem.appendChild(sourceCodePre(sourceLines, sourceLineNumber, sourceColumnNumber))
}
trace.appendChild(elem)
}
appendOmittedFrames()
container.appendChild(trace)
// Mount
document.body.appendChild(overlayReference = overlay)
errorCache = error
additionalCount = 0
}
function unmount() {
if (overlayReference === null) return
document.body.removeChild(overlayReference)
overlayReference = null
}
function isInternalFile(url) {
return url.indexOf('/~/') !== -1 || url.trim().indexOf(' ') !== -1
}
function crash(error, unhandledRejection = false) {
StackTraceResolve(error, CONTEXT_SIZE).then(function(resolvedFrames) {
if (unhandledRejection) {
render(error, `Unhandled Rejection (${error.name})`, error.message, resolvedFrames)
} else {
render(error, error.name, error.message, resolvedFrames)
}
}).catch(function(e) {
// This is another fail case (unlikely to happen)
// e.g. render(...) throws an error with provided arguments
console.log('Red box renderer error:', e)
render(null, 'Error', 'Unknown Error (failure to materialize)', [])
})
}
window.onerror = function(messageOrEvent, source, lineno, colno, error) {
if (error == null || !(error instanceof Error) || messageOrEvent.indexOf('Script error') !== -1) {
crash(new Error(error || messageOrEvent))// TODO: more helpful message
} else {
crash(error)
}
}
let promiseHandler = function(event) {
if (event != null && event.reason != null) {
const { reason } = event
if (reason == null || !(reason instanceof Error)) {
crash(new Error(reason), true)
} else {
crash(reason, true)
}
} else {
crash(new Error('Unknown event'), true)
}
}
window.addEventListener('unhandledrejection', promiseHandler)
let escapeHandler = function(event) {
const { key, keyCode, which } = event
if (key === 'Escape' || keyCode === 27 || which === 27) unmount()
else if (key === 'i' || keyCode === 73 || which === 73) toggleInternal()
}
window.addEventListener('keydown', escapeHandler)
try {
Error.stackTraceLimit = 50
} catch (e) { }
if (module.hot) {
module.hot.dispose(function() {
unmount()
window.removeEventListener('unhandledrejection', promiseHandler)
window.removeEventListener('keydown', escapeHandler)
})
}
})()