diff --git a/client-src/index.js b/client-src/index.js index 7ea51c0c63..981bb99ee8 100644 --- a/client-src/index.js +++ b/client-src/index.js @@ -4,7 +4,7 @@ import webpackHotLog from "webpack/hot/log.js"; import stripAnsi from "./utils/stripAnsi.js"; import parseURL from "./utils/parseURL.js"; import socket from "./socket.js"; -import { formatProblem, show, hide } from "./overlay.js"; +import { formatProblem, createOverlay } from "./overlay.js"; import { log, logEnabledFeatures, setLogLevel } from "./utils/log.js"; import sendMessage from "./utils/sendMessage.js"; import reloadApp from "./utils/reloadApp.js"; @@ -115,6 +115,13 @@ self.addEventListener("beforeunload", () => { status.isUnloading = true; }); +const trustedTypesPolicyName = + typeof options.overlay === "object" && options.overlay.trustedTypesPolicyName; + +const overlay = createOverlay({ + trustedTypesPolicyName, +}); + const onSocketMessage = { hot() { if (parsedResourceQuery.hot === "false") { @@ -135,7 +142,7 @@ const onSocketMessage = { // Fixes #1042. overlay doesn't clear if errors are fixed but warnings remain. if (options.overlay) { - hide(); + overlay.send({ type: "DISMISS" }); } sendMessage("Invalid"); @@ -192,7 +199,7 @@ const onSocketMessage = { log.info("Nothing changed."); if (options.overlay) { - hide(); + overlay.send({ type: "DISMISS" }); } sendMessage("StillOk"); @@ -201,7 +208,7 @@ const onSocketMessage = { sendMessage("Ok"); if (options.overlay) { - hide(); + overlay.send({ type: "DISMISS" }); } reloadApp(options, status); @@ -256,10 +263,11 @@ const onSocketMessage = { : options.overlay && options.overlay.warnings; if (needShowOverlayForWarnings) { - const trustedTypesPolicyName = - typeof options.overlay === "object" && - options.overlay.trustedTypesPolicyName; - show("warning", warnings, trustedTypesPolicyName || null); + overlay.send({ + type: "BUILD_ERROR", + level: "warning", + messages: warnings, + }); } if (params && params.preventReloading) { @@ -292,10 +300,11 @@ const onSocketMessage = { : options.overlay && options.overlay.errors; if (needShowOverlayForErrors) { - const trustedTypesPolicyName = - typeof options.overlay === "object" && - options.overlay.trustedTypesPolicyName; - show("error", errors, trustedTypesPolicyName || null); + overlay.send({ + type: "BUILD_ERROR", + level: "error", + messages: errors, + }); } }, /** @@ -308,7 +317,7 @@ const onSocketMessage = { log.info("Disconnected!"); if (options.overlay) { - hide(); + overlay.send({ type: "DISMISS" }); } sendMessage("Close"); diff --git a/client-src/overlay.js b/client-src/overlay.js index 4e7909b370..a46300a61b 100644 --- a/client-src/overlay.js +++ b/client-src/overlay.js @@ -3,6 +3,11 @@ import ansiHTML from "ansi-html-community"; import { encode } from "html-entities"; +import { + listenToRuntimeError, + parseErrorToStacks, +} from "./overlay/runtime-error.js"; +import createOverlayMachine from "./overlay/state-machine.js"; import { containerStyle, dismissButtonStyle, @@ -26,130 +31,11 @@ const colors = { darkgrey: "6D7891", }; -/** @type {HTMLIFrameElement | null | undefined} */ -let iframeContainerElement; -/** @type {HTMLDivElement | null | undefined} */ -let containerElement; -/** @type {Array<(element: HTMLDivElement) => void>} */ -let onLoadQueue = []; -/** @type {TrustedTypePolicy | undefined} */ -let overlayTrustedTypesPolicy; - ansiHTML.setColors(colors); -/** - * - * @param {HTMLElement} element - * @param {CSSStyleDeclaration} style - */ -function applyStyle(element, style) { - Object.keys(style).forEach((prop) => { - element.style[prop] = style[prop]; - }); -} - -/** - * @param {string | null} trustedTypesPolicyName - */ -function createContainer(trustedTypesPolicyName) { - // Enable Trusted Types if they are available in the current browser. - if (window.trustedTypes) { - overlayTrustedTypesPolicy = window.trustedTypes.createPolicy( - trustedTypesPolicyName || "webpack-dev-server#overlay", - { - createHTML: (value) => value, - } - ); - } - - iframeContainerElement = document.createElement("iframe"); - iframeContainerElement.id = "webpack-dev-server-client-overlay"; - iframeContainerElement.src = "about:blank"; - applyStyle(iframeContainerElement, iframeStyle); - iframeContainerElement.onload = () => { - containerElement = - /** @type {Document} */ - ( - /** @type {HTMLIFrameElement} */ - (iframeContainerElement).contentDocument - ).createElement("div"); - - containerElement.id = "webpack-dev-server-client-overlay-div"; - applyStyle(containerElement, containerStyle); - - const headerElement = document.createElement("div"); - - headerElement.innerText = "Compiled with problems:"; - applyStyle(headerElement, headerStyle); - - const closeButtonElement = document.createElement("button"); - - applyStyle(closeButtonElement, dismissButtonStyle); - - closeButtonElement.innerText = "×"; - closeButtonElement.ariaLabel = "Dismiss"; - closeButtonElement.addEventListener("click", () => { - hide(); - }); - - containerElement.appendChild(headerElement); - containerElement.appendChild(closeButtonElement); - - /** @type {Document} */ - ( - /** @type {HTMLIFrameElement} */ - (iframeContainerElement).contentDocument - ).body.appendChild(containerElement); - - onLoadQueue.forEach((onLoad) => { - onLoad(/** @type {HTMLDivElement} */ (containerElement)); - }); - onLoadQueue = []; - - /** @type {HTMLIFrameElement} */ - (iframeContainerElement).onload = null; - }; - - document.body.appendChild(iframeContainerElement); -} - -/** - * @param {(element: HTMLDivElement) => void} callback - * @param {string | null} trustedTypesPolicyName - */ -function ensureOverlayExists(callback, trustedTypesPolicyName) { - if (containerElement) { - // Everything is ready, call the callback right away. - callback(containerElement); - - return; - } - - onLoadQueue.push(callback); - - if (iframeContainerElement) { - return; - } - - createContainer(trustedTypesPolicyName); -} - -// Successful compilation. -function hide() { - if (!iframeContainerElement) { - return; - } - - // Clean up and reset internal state. - document.body.removeChild(iframeContainerElement); - - iframeContainerElement = null; - containerElement = null; -} - /** * @param {string} type - * @param {string | { file?: string, moduleName?: string, loc?: string, message?: string }} item + * @param {string | { file?: string, moduleName?: string, loc?: string, message?: string; stack?: string[] }} item * @returns {{ header: string, body: string }} */ function formatProblem(type, item) { @@ -178,57 +64,233 @@ function formatProblem(type, item) { body += item.message || ""; } + if (Array.isArray(item.stack)) { + item.stack.forEach((stack) => { + if (typeof stack === "string") { + body += `\r\n${stack}`; + } + }); + } + return { header, body }; } -// Compilation with errors (e.g. syntax error or missing modules). /** - * @param {string} type - * @param {Array} messages - * @param {string | null} trustedTypesPolicyName + * @typedef {Object} CreateOverlayOptions + * @property {string | null} trustedTypesPolicyName + */ + +/** + * + * @param {CreateOverlayOptions} options */ -function show(type, messages, trustedTypesPolicyName) { - ensureOverlayExists(() => { - messages.forEach((message) => { - const entryElement = document.createElement("div"); - const msgStyle = type === "warning" ? msgStyles.warning : msgStyles.error; - applyStyle(entryElement, { - ...msgStyle, - padding: "1rem 1rem 1.5rem 1rem", +const createOverlay = (options) => { + /** @type {HTMLIFrameElement | null | undefined} */ + let iframeContainerElement; + /** @type {HTMLDivElement | null | undefined} */ + let containerElement; + /** @type {Array<(element: HTMLDivElement) => void>} */ + let onLoadQueue = []; + /** @type {TrustedTypePolicy | undefined} */ + let overlayTrustedTypesPolicy; + + /** + * + * @param {HTMLElement} element + * @param {CSSStyleDeclaration} style + */ + function applyStyle(element, style) { + Object.keys(style).forEach((prop) => { + element.style[prop] = style[prop]; + }); + } + + /** + * @param {string | null} trustedTypesPolicyName + */ + function createContainer(trustedTypesPolicyName) { + // Enable Trusted Types if they are available in the current browser. + if (window.trustedTypes) { + overlayTrustedTypesPolicy = window.trustedTypes.createPolicy( + trustedTypesPolicyName || "webpack-dev-server#overlay", + { + createHTML: (value) => value, + } + ); + } + + iframeContainerElement = document.createElement("iframe"); + iframeContainerElement.id = "webpack-dev-server-client-overlay"; + iframeContainerElement.src = "about:blank"; + applyStyle(iframeContainerElement, iframeStyle); + iframeContainerElement.onload = () => { + const contentElement = + /** @type {Document} */ + ( + /** @type {HTMLIFrameElement} */ + (iframeContainerElement).contentDocument + ).createElement("div"); + containerElement = + /** @type {Document} */ + ( + /** @type {HTMLIFrameElement} */ + (iframeContainerElement).contentDocument + ).createElement("div"); + + contentElement.id = "webpack-dev-server-client-overlay-div"; + applyStyle(contentElement, containerStyle); + + const headerElement = document.createElement("div"); + + headerElement.innerText = "Compiled with problems:"; + applyStyle(headerElement, headerStyle); + + const closeButtonElement = document.createElement("button"); + + applyStyle(closeButtonElement, dismissButtonStyle); + + closeButtonElement.innerText = "×"; + closeButtonElement.ariaLabel = "Dismiss"; + closeButtonElement.addEventListener("click", () => { + // eslint-disable-next-line no-use-before-define + overlayService.send({ type: "DISMISS" }); }); - const typeElement = document.createElement("div"); - const { header, body } = formatProblem(type, message); + contentElement.appendChild(headerElement); + contentElement.appendChild(closeButtonElement); + contentElement.appendChild(containerElement); - typeElement.innerText = header; - applyStyle(typeElement, msgTypeStyle); + /** @type {Document} */ + ( + /** @type {HTMLIFrameElement} */ + (iframeContainerElement).contentDocument + ).body.appendChild(contentElement); - if (message.moduleIdentifier) { - applyStyle(typeElement, { cursor: "pointer" }); - typeElement.dataset.canOpen = true; - typeElement.addEventListener("click", () => { - fetch( - `/webpack-dev-server/open-editor?fileName=${message.moduleIdentifier}` - ); - }); - } + onLoadQueue.forEach((onLoad) => { + onLoad(/** @type {HTMLDivElement} */ (contentElement)); + }); + onLoadQueue = []; - // Make it look similar to our terminal. - const text = ansiHTML(encode(body)); - const messageTextNode = document.createElement("div"); - applyStyle(messageTextNode, msgTextStyle); + /** @type {HTMLIFrameElement} */ + (iframeContainerElement).onload = null; + }; + + document.body.appendChild(iframeContainerElement); + } + + /** + * @param {(element: HTMLDivElement) => void} callback + * @param {string | null} trustedTypesPolicyName + */ + function ensureOverlayExists(callback, trustedTypesPolicyName) { + if (containerElement) { + containerElement.innerHTML = ""; + // Everything is ready, call the callback right away. + callback(containerElement); + + return; + } + + onLoadQueue.push(callback); + + if (iframeContainerElement) { + return; + } + + createContainer(trustedTypesPolicyName); + } + + // Successful compilation. + function hide() { + if (!iframeContainerElement) { + return; + } + + // Clean up and reset internal state. + document.body.removeChild(iframeContainerElement); - messageTextNode.innerHTML = overlayTrustedTypesPolicy - ? overlayTrustedTypesPolicy.createHTML(text) - : text; + iframeContainerElement = null; + containerElement = null; + } + + // Compilation with errors (e.g. syntax error or missing modules). + /** + * @param {string} type + * @param {Array} messages + * @param {string | null} trustedTypesPolicyName + */ + function show(type, messages, trustedTypesPolicyName) { + ensureOverlayExists(() => { + messages.forEach((message) => { + const entryElement = document.createElement("div"); + const msgStyle = + type === "warning" ? msgStyles.warning : msgStyles.error; + applyStyle(entryElement, { + ...msgStyle, + padding: "1rem 1rem 1.5rem 1rem", + }); - entryElement.appendChild(typeElement); - entryElement.appendChild(messageTextNode); + const typeElement = document.createElement("div"); + const { header, body } = formatProblem(type, message); + + typeElement.innerText = header; + applyStyle(typeElement, msgTypeStyle); + + if (message.moduleIdentifier) { + applyStyle(typeElement, { cursor: "pointer" }); + // element.dataset not supported in IE + typeElement.setAttribute("data-can-open", true); + typeElement.addEventListener("click", () => { + fetch( + `/webpack-dev-server/open-editor?fileName=${message.moduleIdentifier}` + ); + }); + } + + // Make it look similar to our terminal. + const text = ansiHTML(encode(body)); + const messageTextNode = document.createElement("div"); + applyStyle(messageTextNode, msgTextStyle); + + messageTextNode.innerHTML = overlayTrustedTypesPolicy + ? overlayTrustedTypesPolicy.createHTML(text) + : text; + + entryElement.appendChild(typeElement); + entryElement.appendChild(messageTextNode); + + /** @type {HTMLDivElement} */ + (containerElement).appendChild(entryElement); + }); + }, trustedTypesPolicyName); + } - /** @type {HTMLDivElement} */ - (containerElement).appendChild(entryElement); + const overlayService = createOverlayMachine({ + showOverlay: ({ level = "error", messages }) => + show(level, messages, options.trustedTypesPolicyName), + hideOverlay: hide, + }); + + listenToRuntimeError((errorEvent) => { + // error property may be empty in older browser like IE + const { error, message } = errorEvent; + if (!error && !message) { + return; + } + const errorObject = + error instanceof Error ? error : new Error(error || message); + overlayService.send({ + type: "RUNTIME_ERROR", + messages: [ + { + message: errorObject.message, + stack: parseErrorToStacks(errorObject), + }, + ], }); - }, trustedTypesPolicyName); -} + }); + + return overlayService; +}; -export { formatProblem, show, hide }; +export { formatProblem, createOverlay }; diff --git a/client-src/overlay/fsm.js b/client-src/overlay/fsm.js new file mode 100644 index 0000000000..78020e97aa --- /dev/null +++ b/client-src/overlay/fsm.js @@ -0,0 +1,64 @@ +/** + * @typedef {Object} StateDefinitions + * @property {{[event: string]: { target: string; actions?: Array }}} [on] + */ + +/** + * @typedef {Object} Options + * @property {{[state: string]: StateDefinitions}} states + * @property {object} context; + * @property {string} initial + */ + +/** + * @typedef {Object} Implementation + * @property {{[actionName: string]: (ctx: object, event: any) => object}} actions + */ + +/** + * A simplified `createMachine` from `@xstate/fsm` with the following differences: + * + * - the returned machine is technically a "service". No `interpret(machine).start()` is needed. + * - the state definition only support `on` and target must be declared with { target: 'nextState', actions: [] } explicitly. + * - event passed to `send` must be an object with `type` property. + * - actions implementation will be [assign action](https://xstate.js.org/docs/guides/context.html#assign-action) if you return any value. + * Do not return anything if you just want to invoke side effect. + * + * The goal of this custom function is to avoid installing the entire `'xstate/fsm'` package, while enabling modeling using + * state machine. You can copy the first parameter into the editor at https://stately.ai/viz to visualize the state machine. + * + * @param {Options} options + * @param {Implementation} implementation + */ +function createMachine({ states, context, initial }, { actions }) { + let currentState = initial; + let currentContext = context; + + return { + send: (event) => { + const currentStateOn = states[currentState].on; + const transitionConfig = currentStateOn && currentStateOn[event.type]; + + if (transitionConfig) { + currentState = transitionConfig.target; + if (transitionConfig.actions) { + transitionConfig.actions.forEach((actName) => { + const actionImpl = actions[actName]; + + const nextContextValue = + actionImpl && actionImpl(currentContext, event); + + if (nextContextValue) { + currentContext = { + ...currentContext, + ...nextContextValue, + }; + } + }); + } + } + }, + }; +} + +export default createMachine; diff --git a/client-src/overlay/runtime-error.js b/client-src/overlay/runtime-error.js new file mode 100644 index 0000000000..a04fd82353 --- /dev/null +++ b/client-src/overlay/runtime-error.js @@ -0,0 +1,33 @@ +/** + * + * @param {Error} error + */ +function parseErrorToStacks(error) { + if (!error || !(error instanceof Error)) { + throw new Error(`parseErrorToStacks expects Error object`); + } + if (typeof error.stack === "string") { + return error.stack + .split("\n") + .filter((stack) => stack !== `Error: ${error.message}`); + } +} + +/** + * @callback ErrorCallback + * @param {ErrorEvent} error + * @returns {void} + */ + +/** + * @param {ErrorCallback} callback + */ +function listenToRuntimeError(callback) { + window.addEventListener("error", callback); + + return function cleanup() { + window.removeEventListener("error", callback); + }; +} + +export { listenToRuntimeError, parseErrorToStacks }; diff --git a/client-src/overlay/state-machine.js b/client-src/overlay/state-machine.js new file mode 100644 index 0000000000..d9ed764198 --- /dev/null +++ b/client-src/overlay/state-machine.js @@ -0,0 +1,99 @@ +import createMachine from "./fsm.js"; + +/** + * @typedef {Object} ShowOverlayData + * @property {'warning' | 'error'} level + * @property {Array} messages + */ + +/** + * @typedef {Object} CreateOverlayMachineOptions + * @property {(data: ShowOverlayData) => void} showOverlay + * @property {() => void} hideOverlay + */ + +/** + * @param {CreateOverlayMachineOptions} options + */ +const createOverlayMachine = (options) => { + const { hideOverlay, showOverlay } = options; + const overlayMachine = createMachine( + { + initial: "hidden", + context: { + level: "error", + messages: [], + }, + states: { + hidden: { + on: { + BUILD_ERROR: { + target: "displayBuildError", + actions: ["setMessages", "showOverlay"], + }, + RUNTIME_ERROR: { + target: "displayRuntimeError", + actions: ["setMessages", "showOverlay"], + }, + }, + }, + displayBuildError: { + on: { + DISMISS: { + target: "hidden", + actions: ["dismissMessages", "hideOverlay"], + }, + BUILD_ERROR: { + target: "displayBuildError", + actions: ["appendMessages", "showOverlay"], + }, + }, + }, + displayRuntimeError: { + on: { + DISMISS: { + target: "hidden", + actions: ["dismissMessages", "hideOverlay"], + }, + RUNTIME_ERROR: { + target: "displayRuntimeError", + actions: ["appendMessages", "showOverlay"], + }, + BUILD_ERROR: { + target: "displayBuildError", + actions: ["setMessages", "showOverlay"], + }, + }, + }, + }, + }, + { + actions: { + dismissMessages: () => { + return { + messages: [], + level: "error", + }; + }, + appendMessages: (context, event) => { + return { + messages: context.messages.concat(event.messages), + level: event.level || context.level, + }; + }, + setMessages: (context, event) => { + return { + messages: event.messages, + level: event.level || context.level, + }; + }, + hideOverlay, + showOverlay, + }, + } + ); + + return overlayMachine; +}; + +export default createOverlayMachine; diff --git a/examples/client/overlay/app.js b/examples/client/overlay/app.js index 77142167f0..a4344aa340 100644 --- a/examples/client/overlay/app.js +++ b/examples/client/overlay/app.js @@ -1,7 +1,12 @@ "use strict"; +// eslint-disable-next-line import/order +const createErrorBtn = require("./error-button"); + const target = document.querySelector("#target"); +target.insertAdjacentElement("afterend", createErrorBtn()); + // eslint-disable-next-line import/no-unresolved, import/extensions const invalid = require("./invalid.js"); diff --git a/examples/client/overlay/error-button.js b/examples/client/overlay/error-button.js new file mode 100644 index 0000000000..11fe606af0 --- /dev/null +++ b/examples/client/overlay/error-button.js @@ -0,0 +1,18 @@ +"use strict"; + +function unsafeOperation() { + throw new Error("Error message thrown from JS"); +} + +function handleButtonClick() { + unsafeOperation(); +} + +module.exports = function createErrorButton() { + const errorBtn = document.createElement("button"); + + errorBtn.addEventListener("click", handleButtonClick); + errorBtn.innerHTML = "Click to throw error"; + + return errorBtn; +}; diff --git a/examples/client/overlay/webpack.config.js b/examples/client/overlay/webpack.config.js index 43e883e183..41d8ad543b 100644 --- a/examples/client/overlay/webpack.config.js +++ b/examples/client/overlay/webpack.config.js @@ -13,4 +13,6 @@ module.exports = setup({ overlay: true, }, }, + // uncomment to test for IE + // target: ["web", "es5"], }); diff --git a/test/client/index.test.js b/test/client/index.test.js index a1ed197be5..c3883e5fd4 100644 --- a/test/client/index.test.js +++ b/test/client/index.test.js @@ -35,15 +35,21 @@ describe("index", () => { jest.setMock("../../client-src/socket.js", jest.fn()); socket = require("../../client-src/socket"); + const send = jest.fn(); + // overlay jest.setMock("../../client-src/overlay.js", { - hide: jest.fn(), - show: jest.fn(), + createOverlay: () => { + return { + send, + }; + }, formatProblem: (item) => { return { header: "HEADER warning", body: `BODY: ${item}` }; }, }); - overlay = require("../../client-src/overlay"); + const { createOverlay } = require("../../client-src/overlay"); + overlay = createOverlay(); // reloadApp jest.setMock("../../client-src/utils/reloadApp.js", jest.fn()); @@ -89,13 +95,13 @@ describe("index", () => { expect(log.log.info.mock.calls[0][0]).toMatchSnapshot(); expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); - expect(overlay.hide).not.toBeCalled(); + expect(overlay.send).not.toBeCalledWith({ type: "DISMISS" }); // change flags onSocketMessage.overlay(true); onSocketMessage["still-ok"](); - expect(overlay.hide).toBeCalled(); + expect(overlay.send).toHaveBeenCalledWith({ type: "DISMISS" }); }); test("should run onSocketMessage.progress and onSocketMessage['progress-update']", () => { @@ -191,9 +197,14 @@ describe("index", () => { // change flags onSocketMessage.overlay({ warnings: true }); - onSocketMessage.warnings([]); + onSocketMessage.warnings(["warning message"]); - expect(overlay.show).toBeCalled(); + expect(overlay.send).toHaveBeenCalledTimes(1); + expect(overlay.send).toHaveBeenCalledWith({ + type: "BUILD_ERROR", + level: "warning", + messages: ["warning message"], + }); }); test("should parse overlay options from resource query", () => { @@ -202,17 +213,22 @@ describe("index", () => { global.__resourceQuery = `?overlay=${encodeURIComponent( `{"warnings": false}` )}`; - overlay.show.mockReset(); + overlay.send.mockReset(); socket.mockReset(); jest.unmock("../../client-src/utils/parseURL.js"); require("../../client-src"); onSocketMessage = socket.mock.calls[0][1]; onSocketMessage.warnings(["warn1"]); - expect(overlay.show).not.toBeCalled(); + expect(overlay.send).not.toBeCalled(); onSocketMessage.errors(["error1"]); - expect(overlay.show).toBeCalledTimes(1); + expect(overlay.send).toBeCalledTimes(1); + expect(overlay.send).toHaveBeenCalledWith({ + type: "BUILD_ERROR", + level: "error", + messages: ["error1"], + }); }); jest.isolateModules(() => { @@ -220,17 +236,22 @@ describe("index", () => { global.__resourceQuery = `?overlay=${encodeURIComponent( `{"errors": false}` )}`; - overlay.show.mockReset(); + overlay.send.mockReset(); socket.mockReset(); jest.unmock("../../client-src/utils/parseURL.js"); require("../../client-src"); onSocketMessage = socket.mock.calls[0][1]; onSocketMessage.errors(["error1"]); - expect(overlay.show).not.toBeCalled(); + expect(overlay.send).not.toBeCalled(); onSocketMessage.warnings(["warn1"]); - expect(overlay.show).toBeCalledTimes(1); + expect(overlay.send).toBeCalledTimes(1); + expect(overlay.send).toHaveBeenCalledWith({ + type: "BUILD_ERROR", + level: "warning", + messages: ["warn1"], + }); }); jest.isolateModules(() => { @@ -238,15 +259,25 @@ describe("index", () => { global.__resourceQuery = "?overlay=true"; jest.unmock("../../client-src/utils/parseURL.js"); socket.mockReset(); - overlay.show.mockReset(); + overlay.send.mockReset(); require("../../client-src"); onSocketMessage = socket.mock.calls[0][1]; onSocketMessage.warnings(["warn2"]); - expect(overlay.show).toBeCalledTimes(1); + expect(overlay.send).toBeCalledTimes(1); + expect(overlay.send).toHaveBeenLastCalledWith({ + type: "BUILD_ERROR", + level: "warning", + messages: ["warn2"], + }); onSocketMessage.errors(["error2"]); - expect(overlay.show).toBeCalledTimes(2); + expect(overlay.send).toBeCalledTimes(2); + expect(overlay.send).toHaveBeenLastCalledWith({ + type: "BUILD_ERROR", + level: "error", + messages: ["error2"], + }); }); }); diff --git a/test/e2e/__snapshots__/multi-compiler.test.js.snap.webpack5 b/test/e2e/__snapshots__/multi-compiler.test.js.snap.webpack5 index 764980bfe3..5da317a992 100644 --- a/test/e2e/__snapshots__/multi-compiler.test.js.snap.webpack5 +++ b/test/e2e/__snapshots__/multi-compiler.test.js.snap.webpack5 @@ -51,13 +51,13 @@ Array [ "[HMR] Cannot apply update. Need to do a full reload!", "[HMR] Error: Aborted because ./browser.js is not accepted Update propagation: ./browser.js - at applyHandler (http://127.0.0.1:8103/browser.js:1044:31) - at http://127.0.0.1:8103/browser.js:743:21 + at applyHandler (http://127.0.0.1:8103/browser.js:1077:31) + at http://127.0.0.1:8103/browser.js:776:21 at Array.map () - at internalApply (http://127.0.0.1:8103/browser.js:742:54) - at http://127.0.0.1:8103/browser.js:712:26 - at waitForBlockingPromises (http://127.0.0.1:8103/browser.js:666:48) - at http://127.0.0.1:8103/browser.js:710:24", + at internalApply (http://127.0.0.1:8103/browser.js:775:54) + at http://127.0.0.1:8103/browser.js:745:26 + at waitForBlockingPromises (http://127.0.0.1:8103/browser.js:699:48) + at http://127.0.0.1:8103/browser.js:743:24", "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay enabled.", "[HMR] Waiting for update signal from WDS...", "Hello from the browser", diff --git a/test/e2e/__snapshots__/overlay.test.js.snap.webpack4 b/test/e2e/__snapshots__/overlay.test.js.snap.webpack4 index e80e804422..f989b527bb 100644 --- a/test/e2e/__snapshots__/overlay.test.js.snap.webpack4 +++ b/test/e2e/__snapshots__/overlay.test.js.snap.webpack4 @@ -82,34 +82,36 @@ exports[`overlay should not show initially, then show on an error and allow to c > × -
+
- ERROR -
-
- ./foo.js 1:1 Module parse failed: Unterminated template (1:1) You may - need an appropriate loader to handle this file type, currently no - loaders are configured to process this file. See - https://webpack.js.org/concepts#loaders > \`; +
+ ERROR +
+
+ ./foo.js 1:1 Module parse failed: Unterminated template (1:1) You may + need an appropriate loader to handle this file type, currently no + loaders are configured to process this file. See + https://webpack.js.org/concepts#loaders > \`; +
@@ -204,34 +206,36 @@ exports[`overlay should not show initially, then show on an error, then hide on > × -
-
- ERROR -
+
- ./foo.js 1:1 Module parse failed: Unterminated template (1:1) You may - need an appropriate loader to handle this file type, currently no - loaders are configured to process this file. See - https://webpack.js.org/concepts#loaders > \`; +
+ ERROR +
+
+ ./foo.js 1:1 Module parse failed: Unterminated template (1:1) You may + need an appropriate loader to handle this file type, currently no + loaders are configured to process this file. See + https://webpack.js.org/concepts#loaders > \`; +
@@ -326,34 +330,36 @@ exports[`overlay should not show initially, then show on an error, then show oth > × -
-
- ERROR -
+
- ./foo.js 1:1 Module parse failed: Unterminated template (1:1) You may - need an appropriate loader to handle this file type, currently no - loaders are configured to process this file. See - https://webpack.js.org/concepts#loaders > \`; +
+ ERROR +
+
+ ./foo.js 1:1 Module parse failed: Unterminated template (1:1) You may + need an appropriate loader to handle this file type, currently no + loaders are configured to process this file. See + https://webpack.js.org/concepts#loaders > \`; +
@@ -411,34 +417,36 @@ exports[`overlay should not show initially, then show on an error, then show oth > × -
+
- ERROR -
-
- ./foo.js 1:1 Module parse failed: Unterminated template (1:1) You may - need an appropriate loader to handle this file type, currently no - loaders are configured to process this file. See - https://webpack.js.org/concepts#loaders > \`;a +
+ ERROR +
+
+ ./foo.js 1:1 Module parse failed: Unterminated template (1:1) You may + need an appropriate loader to handle this file type, currently no + loaders are configured to process this file. See + https://webpack.js.org/concepts#loaders > \`;a +
@@ -554,31 +562,33 @@ exports[`overlay should show a warning after invalidation: overlay html 1`] = ` > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -657,58 +667,60 @@ exports[`overlay should show a warning and error for initial compilation and pro > × -
-
- WARNING -
+
- <strong>strong</strong> -
-
-
-
- ERROR +
+ ERROR +
+
+ <strong>strong</strong> +
- <strong>strong</strong> +
+ ERROR +
+
+ <strong>strong</strong> +
@@ -787,139 +799,141 @@ exports[`overlay should show a warning and error for initial compilation: overla > × -
-
- WARNING -
-
- Warning from compilation -
-
-
-
- WARNING -
+
- Warning from compilation -
-
-
-
- ERROR +
+ ERROR +
+
+ Warning from compilation +
- Error from compilation. Can't find 'test' module. -
-
-
-
- ERROR +
+ ERROR +
+
+ Warning from compilation +
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
-
-
- ERROR +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
@@ -998,31 +1012,33 @@ exports[`overlay should show a warning and hide them after closing connection: o > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1109,31 +1125,33 @@ exports[`overlay should show a warning for initial compilation: overlay html 1`] > × -
-
- WARNING -
+
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1212,31 +1230,33 @@ exports[`overlay should show a warning when "client.overlay" is "true": overlay > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1315,31 +1335,33 @@ exports[`overlay should show a warning when "client.overlay.errors" is "true": o > × -
-
- WARNING -
+
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1418,31 +1440,33 @@ exports[`overlay should show a warning when "client.overlay.warnings" is "true": > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1521,42 +1545,44 @@ exports[`overlay should show an ansi formatted error for initial compilation: ov > × -
+
- ERROR -
-
- - 18 | - Render - ansi formatted text +
+ + 18 | + Render + ansi formatted text +
@@ -1635,31 +1661,33 @@ exports[`overlay should show an error after invalidation: overlay html 1`] = ` > × -
+
- ERROR -
-
- Error from compilation +
+ ERROR +
+
+ Error from compilation +
@@ -1738,31 +1766,33 @@ exports[`overlay should show an error for initial compilation: overlay html 1`] > × -
+
- ERROR -
-
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
@@ -1841,31 +1871,33 @@ exports[`overlay should show an error when "client.overlay" is "true": overlay h > × -
+
- ERROR -
-
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
@@ -1944,31 +1976,33 @@ exports[`overlay should show an error when "client.overlay.errors" is "true": ov > × -
-
- ERROR -
+
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
@@ -2047,31 +2081,33 @@ exports[`overlay should show an error when "client.overlay.warnings" is "true": > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
diff --git a/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 b/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 index c85c4cf4cc..33fc1dbc70 100644 --- a/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 +++ b/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 @@ -82,36 +82,38 @@ exports[`overlay should not show initially, then show on an error and allow to c > × -
-
- ERROR in ./foo.js 1:1 -
+
- Module parse failed: Unterminated template (1:1) You may need an - appropriate loader to handle this file type, currently no loaders are - configured to process this file. See - https://webpack.js.org/concepts#loaders > \`; +
+ ERROR in ./foo.js 1:1 +
+
+ Module parse failed: Unterminated template (1:1) You may need an + appropriate loader to handle this file type, currently no loaders are + configured to process this file. See + https://webpack.js.org/concepts#loaders > \`; +
@@ -206,36 +208,38 @@ exports[`overlay should not show initially, then show on an error, then hide on > × -
-
- ERROR in ./foo.js 1:1 -
+
- Module parse failed: Unterminated template (1:1) You may need an - appropriate loader to handle this file type, currently no loaders are - configured to process this file. See - https://webpack.js.org/concepts#loaders > \`; +
+ ERROR in ./foo.js 1:1 +
+
+ Module parse failed: Unterminated template (1:1) You may need an + appropriate loader to handle this file type, currently no loaders are + configured to process this file. See + https://webpack.js.org/concepts#loaders > \`; +
@@ -330,36 +334,38 @@ exports[`overlay should not show initially, then show on an error, then show oth > × -
+
- ERROR in ./foo.js 1:1 -
-
- Module parse failed: Unterminated template (1:1) You may need an - appropriate loader to handle this file type, currently no loaders are - configured to process this file. See - https://webpack.js.org/concepts#loaders > \`; +
+ ERROR in ./foo.js 1:1 +
+
+ Module parse failed: Unterminated template (1:1) You may need an + appropriate loader to handle this file type, currently no loaders are + configured to process this file. See + https://webpack.js.org/concepts#loaders > \`; +
@@ -417,36 +423,38 @@ exports[`overlay should not show initially, then show on an error, then show oth > × -
-
- ERROR in ./foo.js 1:1 -
+
- Module parse failed: Unterminated template (1:1) You may need an - appropriate loader to handle this file type, currently no loaders are - configured to process this file. See - https://webpack.js.org/concepts#loaders > \`;a +
+ ERROR in ./foo.js 1:1 +
+
+ Module parse failed: Unterminated template (1:1) You may need an + appropriate loader to handle this file type, currently no loaders are + configured to process this file. See + https://webpack.js.org/concepts#loaders > \`;a +
@@ -570,31 +578,33 @@ exports[`overlay should show a warning after invalidation: overlay html 1`] = ` > × -
-
- WARNING -
+
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -673,58 +683,60 @@ exports[`overlay should show a warning and error for initial compilation and pro > × -
-
- WARNING -
+
- <strong>strong</strong> -
-
-
-
- ERROR +
+ ERROR +
+
+ <strong>strong</strong> +
- <strong>strong</strong> +
+ ERROR +
+
+ <strong>strong</strong> +
@@ -803,139 +815,141 @@ exports[`overlay should show a warning and error for initial compilation: overla > × -
-
- WARNING -
-
- Warning from compilation -
-
-
-
- WARNING -
-
- Warning from compilation -
-
-
-
- ERROR -
+
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Warning from compilation +
-
-
- ERROR +
+ ERROR +
+
+ Warning from compilation +
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
-
-
- ERROR +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
@@ -1014,31 +1028,33 @@ exports[`overlay should show a warning and hide them after closing connection: o > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1125,31 +1141,33 @@ exports[`overlay should show a warning for initial compilation: overlay html 1`] > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1228,31 +1246,33 @@ exports[`overlay should show a warning when "client.overlay" is "true": overlay > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1331,31 +1351,33 @@ exports[`overlay should show a warning when "client.overlay.errors" is "true": o > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1434,31 +1456,33 @@ exports[`overlay should show a warning when "client.overlay.warnings" is "true": > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1537,42 +1561,44 @@ exports[`overlay should show an ansi formatted error for initial compilation: ov > × -
+
- ERROR -
-
- - 18 | - Render - ansi formatted text +
+ + 18 | + Render + ansi formatted text +
@@ -1651,31 +1677,33 @@ exports[`overlay should show an error after invalidation: overlay html 1`] = ` > × -
-
- ERROR -
+
- Error from compilation +
+ ERROR +
+
+ Error from compilation +
@@ -1754,31 +1782,33 @@ exports[`overlay should show an error for initial compilation: overlay html 1`] > × -
+
- ERROR -
-
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
@@ -1857,31 +1887,33 @@ exports[`overlay should show an error when "client.overlay" is "true": overlay h > × -
+
- ERROR -
-
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
@@ -1960,31 +1992,33 @@ exports[`overlay should show an error when "client.overlay.errors" is "true": ov > × -
-
- ERROR -
+
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
@@ -2063,31 +2097,33 @@ exports[`overlay should show an error when "client.overlay.warnings" is "true": > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -2166,31 +2202,33 @@ exports[`overlay should show overlay when Trusted Types are enabled: overlay htm > × -
+
- ERROR -
-
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +