Spaces:
Sleeping
Sleeping
| const RuntimeErrorFooter = require('./components/RuntimeErrorFooter.js'); | |
| const RuntimeErrorHeader = require('./components/RuntimeErrorHeader.js'); | |
| const CompileErrorContainer = require('./containers/CompileErrorContainer.js'); | |
| const RuntimeErrorContainer = require('./containers/RuntimeErrorContainer.js'); | |
| const theme = require('./theme.js'); | |
| const utils = require('./utils.js'); | |
| /** | |
| * @callback RenderFn | |
| * @returns {void} | |
| */ | |
| /* ===== Cached elements for DOM manipulations ===== */ | |
| /** | |
| * The iframe that contains the overlay. | |
| * @type {HTMLIFrameElement} | |
| */ | |
| let iframeRoot = null; | |
| /** | |
| * The document object from the iframe root, used to create and render elements. | |
| * @type {Document} | |
| */ | |
| let rootDocument = null; | |
| /** | |
| * The root div elements will attach to. | |
| * @type {HTMLDivElement} | |
| */ | |
| let root = null; | |
| /** | |
| * A Cached function to allow deferred render. | |
| * @type {RenderFn | null} | |
| */ | |
| let scheduledRenderFn = null; | |
| /* ===== Overlay State ===== */ | |
| /** | |
| * The latest error message from Webpack compilation. | |
| * @type {string} | |
| */ | |
| let currentCompileErrorMessage = ''; | |
| /** | |
| * Index of the error currently shown by the overlay. | |
| * @type {number} | |
| */ | |
| let currentRuntimeErrorIndex = 0; | |
| /** | |
| * The latest runtime error objects. | |
| * @type {Error[]} | |
| */ | |
| let currentRuntimeErrors = []; | |
| /** | |
| * The render mode the overlay is currently in. | |
| * @type {'compileError' | 'runtimeError' | null} | |
| */ | |
| let currentMode = null; | |
| /** | |
| * @typedef {Object} IframeProps | |
| * @property {function(): void} onIframeLoad | |
| */ | |
| /** | |
| * Creates the main `iframe` the overlay will attach to. | |
| * Accepts a callback to be ran after iframe is initialized. | |
| * @param {Document} document | |
| * @param {HTMLElement} root | |
| * @param {IframeProps} props | |
| * @returns {HTMLIFrameElement} | |
| */ | |
| function IframeRoot(document, root, props) { | |
| const iframe = document.createElement('iframe'); | |
| iframe.id = 'react-refresh-overlay'; | |
| iframe.src = 'about:blank'; | |
| iframe.style.border = 'none'; | |
| iframe.style.height = '100%'; | |
| iframe.style.left = '0'; | |
| iframe.style.minHeight = '100vh'; | |
| iframe.style.minHeight = '-webkit-fill-available'; | |
| iframe.style.position = 'fixed'; | |
| iframe.style.top = '0'; | |
| iframe.style.width = '100vw'; | |
| iframe.style.zIndex = '2147483647'; | |
| iframe.addEventListener('load', function onLoad() { | |
| // Reset margin of iframe body | |
| iframe.contentDocument.body.style.margin = '0'; | |
| props.onIframeLoad(); | |
| }); | |
| // We skip mounting and returns as we need to ensure | |
| // the load event is fired after we setup the global variable | |
| return iframe; | |
| } | |
| /** | |
| * Creates the main `div` element for the overlay to render. | |
| * @param {Document} document | |
| * @param {HTMLElement} root | |
| * @returns {HTMLDivElement} | |
| */ | |
| function OverlayRoot(document, root) { | |
| const div = document.createElement('div'); | |
| div.id = 'react-refresh-overlay-error'; | |
| // Style the contents container | |
| div.style.backgroundColor = '#' + theme.grey; | |
| div.style.boxSizing = 'border-box'; | |
| div.style.color = '#' + theme.white; | |
| div.style.fontFamily = [ | |
| '-apple-system', | |
| 'BlinkMacSystemFont', | |
| '"Segoe UI"', | |
| '"Helvetica Neue"', | |
| 'Helvetica', | |
| 'Arial', | |
| 'sans-serif', | |
| '"Apple Color Emoji"', | |
| '"Segoe UI Emoji"', | |
| 'Segoe UI Symbol', | |
| ].join(', '); | |
| div.style.fontSize = '0.875rem'; | |
| div.style.height = '100%'; | |
| div.style.lineHeight = '1.3'; | |
| div.style.overflow = 'auto'; | |
| div.style.padding = '1rem 1.5rem 0'; | |
| div.style.paddingTop = 'max(1rem, env(safe-area-inset-top))'; | |
| div.style.paddingRight = 'max(1.5rem, env(safe-area-inset-right))'; | |
| div.style.paddingBottom = 'env(safe-area-inset-bottom)'; | |
| div.style.paddingLeft = 'max(1.5rem, env(safe-area-inset-left))'; | |
| div.style.width = '100vw'; | |
| root.appendChild(div); | |
| return div; | |
| } | |
| /** | |
| * Ensures the iframe root and the overlay root are both initialized before render. | |
| * If check fails, render will be deferred until both roots are initialized. | |
| * @param {RenderFn} renderFn A function that triggers a DOM render. | |
| * @returns {void} | |
| */ | |
| function ensureRootExists(renderFn) { | |
| if (root) { | |
| // Overlay root is ready, we can render right away. | |
| renderFn(); | |
| return; | |
| } | |
| // Creating an iframe may be asynchronous so we'll defer render. | |
| // In case of multiple calls, function from the last call will be used. | |
| scheduledRenderFn = renderFn; | |
| if (iframeRoot) { | |
| // Iframe is already ready, it will fire the load event. | |
| return; | |
| } | |
| // Create the iframe root, and, the overlay root inside it when it is ready. | |
| iframeRoot = IframeRoot(document, document.body, { | |
| onIframeLoad: function onIframeLoad() { | |
| rootDocument = iframeRoot.contentDocument; | |
| root = OverlayRoot(rootDocument, rootDocument.body); | |
| scheduledRenderFn(); | |
| }, | |
| }); | |
| // We have to mount here to ensure `iframeRoot` is set when `onIframeLoad` fires. | |
| // This is because onIframeLoad() will be called synchronously | |
| // or asynchronously depending on the browser. | |
| document.body.appendChild(iframeRoot); | |
| } | |
| /** | |
| * Creates the main `div` element for the overlay to render. | |
| * @returns {void} | |
| */ | |
| function render() { | |
| ensureRootExists(function () { | |
| const currentFocus = rootDocument.activeElement; | |
| let currentFocusId; | |
| if (currentFocus.localName === 'button' && currentFocus.id) { | |
| currentFocusId = currentFocus.id; | |
| } | |
| utils.removeAllChildren(root); | |
| if (currentCompileErrorMessage) { | |
| currentMode = 'compileError'; | |
| CompileErrorContainer(rootDocument, root, { | |
| errorMessage: currentCompileErrorMessage, | |
| }); | |
| } else if (currentRuntimeErrors.length) { | |
| currentMode = 'runtimeError'; | |
| RuntimeErrorHeader(rootDocument, root, { | |
| currentErrorIndex: currentRuntimeErrorIndex, | |
| totalErrors: currentRuntimeErrors.length, | |
| }); | |
| RuntimeErrorContainer(rootDocument, root, { | |
| currentError: currentRuntimeErrors[currentRuntimeErrorIndex], | |
| }); | |
| RuntimeErrorFooter(rootDocument, root, { | |
| initialFocus: currentFocusId, | |
| multiple: currentRuntimeErrors.length > 1, | |
| onClickCloseButton: function onClose() { | |
| clearRuntimeErrors(); | |
| }, | |
| onClickNextButton: function onNext() { | |
| if (currentRuntimeErrorIndex === currentRuntimeErrors.length - 1) { | |
| return; | |
| } | |
| currentRuntimeErrorIndex += 1; | |
| ensureRootExists(render); | |
| }, | |
| onClickPrevButton: function onPrev() { | |
| if (currentRuntimeErrorIndex === 0) { | |
| return; | |
| } | |
| currentRuntimeErrorIndex -= 1; | |
| ensureRootExists(render); | |
| }, | |
| }); | |
| } | |
| }); | |
| } | |
| /** | |
| * Destroys the state of the overlay. | |
| * @returns {void} | |
| */ | |
| function cleanup() { | |
| // Clean up and reset all internal state. | |
| document.body.removeChild(iframeRoot); | |
| scheduledRenderFn = null; | |
| root = null; | |
| iframeRoot = null; | |
| } | |
| /** | |
| * Clears Webpack compilation errors and dismisses the compile error overlay. | |
| * @returns {void} | |
| */ | |
| function clearCompileError() { | |
| if (!root || currentMode !== 'compileError') { | |
| return; | |
| } | |
| currentCompileErrorMessage = ''; | |
| currentMode = null; | |
| cleanup(); | |
| } | |
| /** | |
| * Clears runtime error records and dismisses the runtime error overlay. | |
| * @param {boolean} [dismissOverlay] Whether to dismiss the overlay or not. | |
| * @returns {void} | |
| */ | |
| function clearRuntimeErrors(dismissOverlay) { | |
| if (!root || currentMode !== 'runtimeError') { | |
| return; | |
| } | |
| currentRuntimeErrorIndex = 0; | |
| currentRuntimeErrors = []; | |
| if (typeof dismissOverlay === 'undefined' || dismissOverlay) { | |
| currentMode = null; | |
| cleanup(); | |
| } | |
| } | |
| /** | |
| * Shows the compile error overlay with the specific Webpack error message. | |
| * @param {string} message | |
| * @returns {void} | |
| */ | |
| function showCompileError(message) { | |
| if (!message) { | |
| return; | |
| } | |
| currentCompileErrorMessage = message; | |
| render(); | |
| } | |
| /** | |
| * Shows the runtime error overlay with the specific error records. | |
| * @param {Error[]} errors | |
| * @returns {void} | |
| */ | |
| function showRuntimeErrors(errors) { | |
| if (!errors || !errors.length) { | |
| return; | |
| } | |
| currentRuntimeErrors = errors; | |
| render(); | |
| } | |
| /** | |
| * The debounced version of `showRuntimeErrors` to prevent frequent renders | |
| * due to rapid firing listeners. | |
| * @param {Error[]} errors | |
| * @returns {void} | |
| */ | |
| const debouncedShowRuntimeErrors = utils.debounce(showRuntimeErrors, 30); | |
| /** | |
| * Detects if an error is a Webpack compilation error. | |
| * @param {Error} error The error of interest. | |
| * @returns {boolean} If the error is a Webpack compilation error. | |
| */ | |
| function isWebpackCompileError(error) { | |
| return /Module [A-z ]+\(from/.test(error.message) || /Cannot find module/.test(error.message); | |
| } | |
| /** | |
| * Handles runtime error contexts captured with EventListeners. | |
| * Integrates with a runtime error overlay. | |
| * @param {Error} error A valid error object. | |
| * @returns {void} | |
| */ | |
| function handleRuntimeError(error) { | |
| if (error && !isWebpackCompileError(error) && currentRuntimeErrors.indexOf(error) === -1) { | |
| currentRuntimeErrors = currentRuntimeErrors.concat(error); | |
| } | |
| debouncedShowRuntimeErrors(currentRuntimeErrors); | |
| } | |
| module.exports = Object.freeze({ | |
| clearCompileError: clearCompileError, | |
| clearRuntimeErrors: clearRuntimeErrors, | |
| handleRuntimeError: handleRuntimeError, | |
| showCompileError: showCompileError, | |
| showRuntimeErrors: showRuntimeErrors, | |
| }); | |