UNPKG

webpack-dev-middleware

Version:
776 lines (721 loc) 29.6 kB
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } import ansiHTML from "ansi-html-community"; import theme from "./theme.js"; // eslint-disable-next-line jsdoc/reject-any-type /** @typedef {any} EXPECTED_ANY */ /** @type {Record<string, string>} */ var characterReferences = { "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&apos;", "&": "&amp;" }; /** * Encode the characters that are meaningful in HTML. Inlined (same as * webpack-dev-server's overlay) so the client does not need `html-entities`. * @param {string} text raw text * @returns {string} entity-encoded text */ function encodeHtmlEntity(text) { if (!text) { return ""; } return text.replace(/[<>'"&]/g, function (character) { return characterReferences[character]; }); } var OVERLAY_ID = "webpack-dev-middleware-hot-overlay"; var CARD_ID = "".concat(OVERLAY_ID, "-card"); // The overlay lives inside an `about:blank` iframe (same pattern as // webpack-dev-server) so page styles cannot leak into it and its styles cannot // leak out. Every style is applied through the CSSOM (`element.style`), which // a strict `style-src` Content Security Policy allows, unlike inline `style` // attributes. /** * The iframe acts as the backdrop: it covers the viewport and dims the page. * @type {Record<string, string | number>} */ var backdropStyles = { position: "fixed", top: 0, left: 0, right: 0, bottom: 0, width: "100vw", height: "100vh", border: "none", zIndex: 9999, background: theme.backdrop }; /** @type {Record<string, string | number>} */ var styles = { // Dark panel; the top accent bar color is set per problem type in showProblems. position: "relative", background: theme.panel, color: theme.text, lineHeight: "1.6", whiteSpace: "pre-wrap", fontFamily: "Menlo, Consolas, 'Courier New', monospace", fontSize: "14px", width: "100%", maxWidth: "960px", maxHeight: "90vh", margin: "auto", padding: "28px 32px", boxSizing: "border-box", borderRadius: "8px", borderTop: "3px solid #ff3348", boxShadow: "0 8px 40px rgba(0,0,0,0.5)", overflow: "auto", direction: "ltr", textAlign: "left" }; /** @type {Record<string, string | number>} */ var bodyStyles = { margin: 0, padding: "32px", boxSizing: "border-box", minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", overflow: "auto", background: "transparent" }; /** @type {Record<string, string | number>} */ var closeButtonStyles = { position: "absolute", top: "8px", right: "12px", border: "none", background: "transparent", color: theme.muted, fontSize: "22px", lineHeight: "1", cursor: "pointer", padding: "0" }; /** @type {Record<string, string | string[]>} */ var colors = { reset: ["transparent", "transparent"], black: "181818", red: "ff3348", green: "3fff4f", yellow: "ffd30e", blue: "169be0", magenta: "f840b7", cyan: "0ad8e9", lightgrey: "ebe7e3", darkgrey: "6d7891" }; /** * @typedef {object} OverlayState * @property {HTMLIFrameElement | null} frame overlay iframe * @property {HTMLElement | null} card visible panel inside the iframe * @property {boolean} runtimeListenersAttached whether the window listeners are attached * @property {boolean} hostKeydownAttached whether the host document's Escape listener is attached * @property {number} pageIndex page shown when paginating * @property {Record<string, { type: "errors" | "warnings", lines: string[] }>} problemsBySource each reporting source's problems * @property {{ type: "errors" | "warnings", lines: string[] } | null} currentProblems union of every source, as displayed * @property {{ createHTML: (value: string) => EXPECTED_ANY } | undefined} trustedTypesPolicy trusted types policy * @property {boolean | ((error: Error) => boolean)} catchRuntimeError whether (or which) runtime errors are shown — shared, so the copy that attached the window listeners honors every copy's configuration */ /** @returns {OverlayState} fresh overlay state */ function createOverlayState() { return { frame: null, card: null, runtimeListenersAttached: false, hostKeydownAttached: false, pageIndex: 0, problemsBySource: {}, currentProblems: null, trustedTypesPolicy: undefined, catchRuntimeError: false }; } // The DOM/problem state is a per-page singleton shared through `window`, so // every bundled copy of this module (e.g. the webpack-dev-server client and // the webpack-dev-middleware client on the same page) renders into a single // overlay instead of stacking duplicate iframes. var OVERLAY_STATE_KEY = "__webpack_dev_middleware_hot_overlay_state__"; /** @type {OverlayState} */ var state = function () { // The browser suite cannot produce a document-less environment; this is the // guard for a server-side import of the bundle. /* istanbul ignore next -- @preserve */ if (typeof window === "undefined") { return createOverlayState(); } var holder = /** @type {EXPECTED_ANY} */window; if (!holder[OVERLAY_STATE_KEY]) { holder[OVERLAY_STATE_KEY] = createOverlayState(); } else { // A copy from another package version may have created the state with // fewer fields — fill the gaps in place (replacing the object would // orphan the references other copies already hold). var defaults = createOverlayState(); for (var _i = 0, _Object$keys = Object.keys(defaults); _i < _Object$keys.length; _i++) { var key = _Object$keys[_i]; if (!(key in holder[OVERLAY_STATE_KEY])) { holder[OVERLAY_STATE_KEY][key] = defaults[(/** @type {keyof OverlayState} */key)]; } } } return holder[OVERLAY_STATE_KEY]; }(); // Pagination (wdm extension, enabled by default): the card shows one problem // at a time with prev/next navigation. var paginate = true; // When set, the file chips in error messages become clickable and issue // `GET <endpoint>?fileName=<file:line:column>`. The endpoint itself is // provided by the server integration (e.g. a route that calls launch-editor, // like webpack-dev-server's `/webpack-dev-server/open-editor`). /** @type {string} */ var openEditorEndpoint = ""; // Trusted Types support (same pattern as webpack-dev-server): when the page // runs under `require-trusted-types-for 'script'`, every `innerHTML` write // must go through a policy. The policy itself lives in the shared state — // creating two policies with the same name throws under an enforced CSP. /** @type {string | undefined} */ var trustedTypesPolicyName; /** * @param {HTMLElement} element element * @param {string} html html to assign */ function setHTML(element, html) { element.innerHTML = state.trustedTypesPolicy ? state.trustedTypesPolicy.createHTML(html) : html; } /** * @param {EXPECTED_ANY} element element * @param {Record<string, string | number>} style style map */ function applyStyle(element, style) { for (var _i2 = 0, _Object$keys2 = Object.keys(style); _i2 < _Object$keys2.length; _i2++) { var key = _Object$keys2[_i2]; element.style[key] = style[key]; } } /** * Re-apply the inline `style` attributes produced by `ansi-html` (and our own * highlight helpers) through the CSSOM. Under a strict `style-src` CSP the * parser ignores `style` attributes, but CSSOM writes are always allowed. * @param {HTMLElement} root subtree to normalize */ function normalizeInlineStyles(root) { // Indexed rather than `for...of`: a `NodeList` is not iterable in an ES5 // browser, and the loop babel compiles it into throws on one. var elements = root.querySelectorAll("[style]"); for (var index = 0; index < elements.length; index++) { var element = elements[index]; /** @type {EXPECTED_ANY} */ element.style.cssText = element.getAttribute("style"); } } /** * @param {"errors" | "warnings"} type problem type * @returns {string | string[]} hex color (without `#`) for the given type */ function problemColor(type) { /** @type {Record<string, string | string[]>} */ var problemColors = { errors: colors.red, warnings: colors.yellow }; return problemColors[type] || colors.red; } /** * @param {"errors" | "warnings"} type problem type * @returns {string} HTML span with a colored badge */ function problemType(type) { var color = problemColor(type); return "<span style=\"background-color:#".concat(color, "; color:#000000; ") + 'padding:3px 6px; border-radius: 4px;">' + "".concat(type.slice(0, -1).toUpperCase(), "</span>"); } /** * Highlight the offending line of a code frame — the one webpack marks with a * leading `>` gutter — so it stands out from the surrounding context lines. * @param {string} html message HTML (already entity-encoded, so `>` is `&gt;`) * @returns {string} HTML with the error line wrapped in a colored span */ function highlightCodeFrame(html) { return html.split("\n").map(function (line) { return /^\s*&gt;/.test(line) ? '<span style="display:inline-block; width:100%; margin:6px 0; ' + 'color:#ff6b6b; background-color:rgba(255,107,107,0.12);">' + "".concat(line, "</span>") : line; }).join("\n"); } /** * Highlight the file references webpack reports. The header reference (the one * with a `line:col` location, e.g. `./src/render.js 7:2`) is rendered as a file * chip; bare paths elsewhere are just underlined. * @param {string} html message HTML * @returns {string} HTML with file references styled */ function highlightFilePath(html) { return html.replace(/(\.{1,2}\/[\w./-]+\.\w+)(:\d+:\d+|\s\d+:\d+)?/g, function (match, filePath, location) { if (!location) { return "<span style=\"color:".concat(theme.accent, "; text-decoration:underline; ") + "text-underline-offset:2px;\">".concat(match, "</span>"); } if (openEditorEndpoint) { var position = location.trim().replace(/^:/, ""); return "<span style=\"color:".concat(theme.accent, "; cursor:pointer; ") + 'text-decoration:underline; text-underline-offset:2px;" ' + "data-open-file=\"".concat(filePath, ":").concat(position, "\" ") + 'title="Click to open in your editor">' + "".concat(filePath, "</span>").concat(location, "\n"); } return "<span style=\"color:".concat(theme.accent, ";\">").concat(filePath, "</span>").concat(location, "\n"); }); } /** * Turn bare `http(s)` URLs in the message into clickable links. * @param {string} html message HTML * @returns {string} HTML with URLs wrapped in anchor tags */ function linkify(html) { return html.replace(/https?:\/\/[^\s<>"]+/g, function (url) { // Keep trailing punctuation (e.g. a sentence-ending dot) out of the href. var trailing = url.match(/[.,;:!?)\]}]+$/); var cut = trailing ? trailing[0] : ""; var href = url.slice(0, url.length - cut.length); return "<a href=\"".concat(href, "\" target=\"_blank\" rel=\"noopener noreferrer\" ") + "style=\"color:".concat(theme.accent, ";\">").concat(href, "</a>").concat(cut); }); } /** * Compute the union of every source's problems. Errors from any source take * precedence over warnings, mirroring the reporter's per-bundle behavior. * @returns {{ type: "errors" | "warnings", lines: string[] } | null} union */ function computeProblemsUnion() { /** @type {{ type: "errors" | "warnings", lines: string[] }[]} */ var slots = []; for (var _i3 = 0, _Object$keys3 = Object.keys(state.problemsBySource); _i3 < _Object$keys3.length; _i3++) { var source = _Object$keys3[_i3]; slots.push(state.problemsBySource[source]); } if (slots.length === 0) { return null; } /** @type {string[]} */ var errorLines = []; /** @type {string[]} */ var allLines = []; for (var _i4 = 0, _slots = slots; _i4 < _slots.length; _i4++) { var slot = _slots[_i4]; if (slot.type === "errors") { errorLines.push.apply(errorLines, _toConsumableArray(slot.lines)); } allLines.push.apply(allLines, _toConsumableArray(slot.lines)); } return errorLines.length > 0 ? { type: "errors", lines: errorLines } : { type: "warnings", lines: allLines }; } /** * Re-render the card. Late-bound to `renderProblems` below: the listeners * `ensureOverlay` attaches re-render, and rendering needs the card * `ensureOverlay` creates, so one of the two directions cannot be a direct * call. * @type {() => void} */ var render = function render() {}; /** * Clamp the page index and re-render. * @param {number} index requested page index */ function goToPage(index) { // Paging is only reachable from a rendered overlay, which implies problems. /* istanbul ignore next -- @preserve */ if (!state.currentProblems) { return; } state.pageIndex = Math.min(state.currentProblems.lines.length - 1, Math.max(0, index)); render(); } /** * Remove one source's problems, or the whole overlay. * @param {string=} source when given, only that source's problems are * dropped and the overlay re-renders the remaining union; without it the * overlay is dismissed entirely (Escape, backdrop, close button) */ export function clear(source) { if (source !== undefined) { // The reporter clears its slots on every clean build — when the source // never reported anything there is nothing to drop, and re-rendering // would needlessly rebuild the card another client is showing. if (!Object.prototype.hasOwnProperty.call(state.problemsBySource, source)) { return; } delete state.problemsBySource[source]; var union = computeProblemsUnion(); state.currentProblems = union; if (union) { state.pageIndex = Math.min(state.pageIndex, union.lines.length - 1); render(); return; } } if (state.frame && state.frame.parentNode) { /** @type {ParentNode & Node} */ state.frame.parentNode.removeChild(state.frame); } state.frame = null; state.card = null; state.problemsBySource = {}; state.currentProblems = null; state.pageIndex = 0; } /** * Create (or return) the overlay iframe and the card inside it. * @returns {HTMLElement | null} the card element, or null when the frame * document is not available */ function ensureOverlay() { if (state.frame && state.card && state.frame.parentNode) { return state.card; } // Only reachable from a script running before <body> exists. /* istanbul ignore next -- @preserve */ if (!document.body) { return null; } // Dismiss the overlay when pressing Escape while the host page has focus — // the frame's own keydown listener only fires when the frame is focused. // Attached lazily on the first render (and once per page, the flag lives in // the shared state) so importing the module stays free of DOM side effects. if (!state.hostKeydownAttached) { state.hostKeydownAttached = true; document.addEventListener("keydown", function (event) { if (event.key === "Escape") { clear(); } }); } // Enable Trusted Types if they are available in the current browser. if (window.trustedTypes && !state.trustedTypesPolicy) { state.trustedTypesPolicy = window.trustedTypes.createPolicy(trustedTypesPolicyName || "webpack-dev-middleware#overlay", { createHTML: function createHTML(value) { return value; } }); } state.frame = document.createElement("iframe"); state.frame.id = OVERLAY_ID; state.frame.src = "about:blank"; applyStyle(state.frame, backdropStyles); document.body.appendChild(state.frame); // A same-origin `about:blank` document is available synchronously. var frameDocument = state.frame.contentDocument; // A same-origin about:blank frame always has a document by this point. /* istanbul ignore next -- @preserve */ if (!frameDocument || !frameDocument.body) { document.body.removeChild(state.frame); state.frame = null; return null; } applyStyle(frameDocument.body, bodyStyles); // Dismiss the overlay when pressing Escape while the frame has focus; // navigate between problems with the arrow keys when paginating. frameDocument.addEventListener("keydown", function (event) { if (event.key === "Escape") { clear(); } else if (paginate && event.key === "ArrowLeft") { goToPage(state.pageIndex - 1); } else if (paginate && event.key === "ArrowRight") { goToPage(state.pageIndex + 1); } }); // Dismiss the overlay when clicking the backdrop (but not the card itself). frameDocument.addEventListener("click", function (event) { var target = /** @type {EXPECTED_ANY} */event.target; // Elements re-rendered away mid-dispatch (e.g. the pagination buttons) // are no longer inside the card — do not treat them as backdrop clicks. if (target && target.isConnected === false) { return; } if (state.card && !state.card.contains(target)) { clear(); } }); // Open the clicked file reference through the configured endpoint. frameDocument.addEventListener("click", function (event) { var target = /** @type {EXPECTED_ANY} */event.target; var opener = target && typeof target.closest === "function" ? target.closest("[data-open-file]") : null; if (opener && openEditorEndpoint && typeof fetch === "function") { fetch("".concat(openEditorEndpoint, "?fileName=").concat(encodeURIComponent(opener.getAttribute("data-open-file")))); } }); // The card is the visible panel that holds the problem messages. state.card = frameDocument.createElement("div"); state.card.id = CARD_ID; applyStyle(state.card, styles); frameDocument.body.appendChild(state.card); return state.card; } /** * Render the current problem set into the card. */ function renderProblems() { // Both guards cover callers that cannot occur in a browser: rendering is // driven by a problem set, and `ensureOverlay` only fails without a body. /* istanbul ignore next -- @preserve */ if (!state.currentProblems) { return; } var card = ensureOverlay(); /* istanbul ignore next -- @preserve */ if (!card) { return; } var frameDocument = /** @type {Document} */ /** @type {HTMLIFrameElement} */state.frame.contentDocument; var _state$currentProblem = state.currentProblems, type = _state$currentProblem.type, lines = _state$currentProblem.lines; var paginated = paginate && lines.length > 1; // Accent the top bar with the problem color (red for errors, yellow for warnings). card.style.borderTopColor = "#".concat(problemColor(type)); setHTML(card, ""); // A close (×) button pinned to the top-right corner of the card. var closeButton = frameDocument.createElement("button"); closeButton.type = "button"; closeButton.textContent = "×"; closeButton.setAttribute("aria-label", "Close"); applyStyle(closeButton, closeButtonStyles); closeButton.addEventListener("click", function () { clear(); }); card.appendChild(closeButton); var visible = paginated ? [lines[state.pageIndex]] : lines; if (paginated) { // Header row: badge and the problem's first line (usually the file // reference) on the left, page navigation on the right (leaving room for // the absolute-positioned close button). var header = frameDocument.createElement("div"); applyStyle(header, { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "12px", marginBottom: "16px", paddingRight: "28px" }); var line = /** @type {string} */visible[0]; var newlineIndex = line.indexOf("\n"); var title = newlineIndex === -1 ? line : line.slice(0, newlineIndex); var badge = frameDocument.createElement("span"); applyStyle(badge, { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }); setHTML(badge, "".concat(problemType(type), " in ").concat(linkify(highlightFilePath(ansiHTML(encodeHtmlEntity(title)))))); normalizeInlineStyles(badge); var nav = frameDocument.createElement("div"); applyStyle(nav, { display: "flex", alignItems: "center", gap: "12px", fontSize: "13px", color: "#999999" }); /** * @param {string} text button text * @param {number} delta page delta * @param {string} ariaLabel accessible label * @returns {HTMLButtonElement} nav button */ var makeNavButton = function makeNavButton(text, delta, ariaLabel) { var button = frameDocument.createElement("button"); button.type = "button"; button.textContent = text; button.setAttribute("aria-label", ariaLabel); applyStyle(button, { border: "none", background: "transparent", // Follow the problem color (red for errors, yellow for warnings). color: "#".concat(problemColor(type)), cursor: "pointer", fontSize: "16px", lineHeight: "1", padding: "0" }); button.addEventListener("click", function () { goToPage(state.pageIndex + delta); }); return button; }; var counter = frameDocument.createElement("span"); counter.textContent = "".concat(state.pageIndex + 1, " / ").concat(lines.length); applyStyle(counter, { color: "#f2f2f2" }); nav.appendChild(makeNavButton("‹", -1, "Previous problem")); nav.appendChild(counter); nav.appendChild(makeNavButton("›", 1, "Next problem")); header.appendChild(badge); header.appendChild(nav); card.appendChild(header); } var _iterator = _createForOfIteratorHelper(visible), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var _line = _step.value; // When paginating, the first line (badge + file reference) already lives // in the header; render only the remainder. var _newlineIndex = _line.indexOf("\n"); var body = paginated ? _newlineIndex === -1 ? "" : _line.slice(_newlineIndex + 1) : _line; if (paginated && !body) { continue; } var msg = linkify(highlightFilePath(highlightCodeFrame(ansiHTML(encodeHtmlEntity(body))))); var div = frameDocument.createElement("div"); div.style.marginBottom = "20px"; setHTML(div, paginated ? msg : "".concat(problemType(type), " in ").concat(msg)); normalizeInlineStyles(div); card.appendChild(div); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } var hint = frameDocument.createElement("div"); applyStyle(hint, { marginTop: "4px", paddingTop: "16px", borderTop: "1px solid #465e69", color: theme.muted, fontSize: "13px" }); hint.textContent = paginated ? "Use ‹ › or the arrow keys to navigate. Click outside, press Esc, or fix the code to dismiss." : "Click outside, press Esc, or fix the code to dismiss."; card.appendChild(hint); } render = renderProblems; /** * @param {"errors" | "warnings"} type problem type * @param {string[]} lines messages to render * @param {string=} source who reports them — each source (e.g. this client, * the webpack-dev-server client, the runtime error capture) keeps its own * slot and the overlay renders the union of every slot */ export function showProblems(type, lines) { var source = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ""; state.problemsBySource[source] = { type: type, lines: _toConsumableArray(lines) }; var union = /** @type {{ type: "errors" | "warnings", lines: string[] }} */ computeProblemsUnion(); // Re-publishing an identical set (e.g. another bundle of a multi-compiler // synced) must not reset the page the user is reading. var unchanged = state.currentProblems !== null && state.frame !== null && // The frame may have been detached without `clear()` (e.g. a framework // wiping `document.body`) — an identical set must still re-mount it. state.frame.parentNode !== null && state.currentProblems.type === union.type && state.currentProblems.lines.length === union.lines.length && state.currentProblems.lines.every(function (line, index) { return line === union.lines[index]; }); state.currentProblems = union; if (unchanged) { return; } // Each new problem set starts at its first page. state.pageIndex = 0; renderProblems(); } /** * @param {EXPECTED_ANY} error thrown value * @param {string} fallbackMessage fallback message */ function handleRuntimeError(error, fallbackMessage) { // If the error stack indicates a React error boundary caught the error, do // not show the overlay (same heuristic as webpack-dev-server). if (error && error.stack && error.stack.indexOf("invokeGuardedCallbackDev") !== -1) { return; } var errorObject = error instanceof Error ? error : new Error(error || fallbackMessage); // `catchRuntimeError` may be a filter function, like in webpack-dev-server. var shouldDisplay = typeof state.catchRuntimeError === "function" ? state.catchRuntimeError(errorObject) : true; if (!shouldDisplay) { return; } var stack = errorObject.stack ? "\n".concat(errorObject.stack) : ""; var message = "Uncaught runtime error: ".concat(errorObject.message).concat(stack); // Runtime errors accumulate in their own slot, so they coexist with build // problems and clearing the slot resets the accumulation. var runtimeSlot = state.problemsBySource.runtime; var messages = runtimeSlot ? [].concat(_toConsumableArray(runtimeSlot.lines), [message]) : [message]; showProblems("errors", messages, "runtime"); // When paginating, land on the newest runtime error. if (state.currentProblems) { goToPage(state.currentProblems.lines.lastIndexOf(message)); } } /** * Listen for uncaught errors and unhandled rejections on the page. */ function attachRuntimeErrorListeners() { if (state.runtimeListenersAttached) { return; } state.runtimeListenersAttached = true; window.addEventListener("error", function (event) { if (!event.error && !event.message) { return; } handleRuntimeError(event.error, event.message); }); window.addEventListener("unhandledrejection", function (event) { handleRuntimeError(event.reason, "Unknown promise rejection reason"); }); } /** * @param {{ ansiColors?: Record<string, string | string[]>, overlayStyles?: Record<string, string | number>, trustedTypesPolicyName?: string, catchRuntimeError?: boolean | ((error: Error) => boolean), openEditorEndpoint?: string, paginate?: boolean }} options options * @returns {{ showProblems: typeof showProblems, clear: typeof clear }} overlay api */ export default function configureOverlay(options) { if (options.trustedTypesPolicyName) { trustedTypesPolicyName = options.trustedTypesPolicyName; } if (options.openEditorEndpoint !== undefined) { openEditorEndpoint = options.openEditorEndpoint; } if (options.paginate !== undefined) { paginate = Boolean(options.paginate); } if (options.catchRuntimeError !== undefined) { state.catchRuntimeError = options.catchRuntimeError; } if (state.catchRuntimeError) { attachRuntimeErrorListeners(); } if (options.ansiColors) { for (var _i5 = 0, _Object$keys4 = Object.keys(options.ansiColors); _i5 < _Object$keys4.length; _i5++) { var color = _Object$keys4[_i5]; if (color in colors) { colors[color] = options.ansiColors[color]; } } ansiHTML.setColors(colors); } if (options.overlayStyles) { for (var _i6 = 0, _Object$keys5 = Object.keys(options.overlayStyles); _i6 < _Object$keys5.length; _i6++) { var style = _Object$keys5[_i6]; styles[style] = options.overlayStyles[style]; } } if (state.card) { applyStyle(state.card, styles); } return { showProblems: showProblems, clear: clear }; }