From 47f39185a7e67875f7b1431015305ae37e65def8 Mon Sep 17 00:00:00 2001
From: Sebastian Beltran
Date: Sat, 5 Sep 2026 23:16:11 -0500
Subject: [PATCH 1/4] refactor: reuse dev-middleware overlay and progress
indicator
---
client-src/globals.d.ts | 24 +-
client-src/index.js | 28 +-
client-src/overlay.js | 414 +-
client-src/progress.js | 249 +-
package-lock.json | 137 +-
package.json | 3 +-
test/client/overlay.test.js | 55 +
test/client/progress.test.js | 38 +
.../overlay.test.js.snap.webpack5 | 3449 +++++++++--------
test/e2e/overlay.test.js | 306 +-
10 files changed, 2192 insertions(+), 2511 deletions(-)
create mode 100644 test/client/overlay.test.js
create mode 100644 test/client/progress.test.js
diff --git a/client-src/globals.d.ts b/client-src/globals.d.ts
index bdf8a44f49..4a454b86c2 100644
--- a/client-src/globals.d.ts
+++ b/client-src/globals.d.ts
@@ -13,12 +13,22 @@ declare const __webpack_dev_server_client__:
| { default: CommunicationClientConstructor }
| undefined;
-declare module "ansi-html-community" {
- function ansiHtmlCommunity(str: string): string;
-
- namespace ansiHtmlCommunity {
- function setColors(colors: Record): void;
- }
+declare module "webpack-dev-middleware/client/overlay" {
+ export default function configureOverlay(options: {
+ trustedTypesPolicyName?: string;
+ openEditorEndpoint?: string;
+ paginate?: boolean;
+ }): {
+ showProblems(
+ type: "errors" | "warnings",
+ lines: string[],
+ source?: string,
+ ): void;
+ clear(source?: string): void;
+ };
+}
- export default ansiHtmlCommunity;
+declare module "webpack-dev-middleware/client/indicator" {
+ export function show(text?: string, percent?: number, source?: string): void;
+ export function hide(source?: string): void;
}
diff --git a/client-src/index.js b/client-src/index.js
index e8a650ea3d..c779a7b715 100644
--- a/client-src/index.js
+++ b/client-src/index.js
@@ -4,7 +4,7 @@ import hotEmitter from "webpack/hot/emitter.js";
// @ts-expect-error
import webpackHotLog from "webpack/hot/log.js";
import { createOverlay, formatProblem } from "./overlay.js";
-import { defineProgressElement, isProgressSupported } from "./progress.js";
+import { hideProgress, showProgress } from "./progress.js";
import socket from "./socket.js";
import { log, setLogLevel } from "./utils/log.js";
import sendMessage from "./utils/sendMessage.js";
@@ -189,7 +189,11 @@ if (parsedResourceQuery["live-reload"] === "true") {
enabledFeatures["Live Reloading"] = true;
}
-if (parsedResourceQuery.progress === "true") {
+if (
+ parsedResourceQuery.progress === "true" ||
+ parsedResourceQuery.progress === "linear" ||
+ parsedResourceQuery.progress === "circular"
+) {
options.progress = true;
enabledFeatures.Progress = true;
}
@@ -444,6 +448,10 @@ const onSocketMessage = {
*/
progress(value) {
options.progress = value;
+
+ if (!value) {
+ hideProgress();
+ }
},
/**
* @param {{ pluginName?: string, percent: string, msg: string }} data date with progress
@@ -457,20 +465,14 @@ const onSocketMessage = {
);
}
- if (isProgressSupported() && typeof options.progress === "string") {
- let progress = document.querySelector("wds-progress");
- if (!progress) {
- defineProgressElement();
- progress = document.createElement("wds-progress");
- document.body.appendChild(progress);
- }
- progress.setAttribute("progress", data.percent);
- progress.setAttribute("type", options.progress);
+ if (options.progress) {
+ showProgress(Number(data.percent), data.msg);
}
sendMessage("Progress", data);
},
"still-ok": function stillOk() {
+ hideProgress();
log.info("Nothing changed.");
if (options.overlay) {
@@ -480,6 +482,7 @@ const onSocketMessage = {
sendMessage("StillOk");
},
ok() {
+ hideProgress();
sendMessage("Ok");
if (options.overlay) {
@@ -505,6 +508,7 @@ const onSocketMessage = {
* @param {{ preventReloading: boolean }=} params extra params
*/
warnings(warnings, params) {
+ hideProgress();
log.warn("Warnings while compiling.");
const printableWarnings = warnings.map((error) => {
@@ -549,6 +553,7 @@ const onSocketMessage = {
* @param {Error[]} errors errors
*/
errors(errors) {
+ hideProgress();
log.error("Errors while compiling. Reload prevented.");
const printableErrors = errors.map((error) => {
@@ -590,6 +595,7 @@ const onSocketMessage = {
log.error(error);
},
close() {
+ hideProgress();
log.info("Disconnected!");
if (options.overlay) {
diff --git a/client-src/overlay.js b/client-src/overlay.js
index 6003b6194b..710379014e 100644
--- a/client-src/overlay.js
+++ b/client-src/overlay.js
@@ -1,82 +1,7 @@
-// The error overlay is inspired (and mostly copied) from Create React App (https://github.com/facebookincubator/create-react-app)
-// They, in turn, got inspired by webpack-hot-middleware (https://github.com/glenjamin/webpack-hot-middleware).
-
-import ansiHTML from "ansi-html-community";
+import configureOverlay from "webpack-dev-middleware/client/overlay";
/** @typedef {import("./index.js").EXPECTED_ANY} EXPECTED_ANY */
-/**
- * @type {(input: string, position: number) => number | undefined}
- */
-// @ts-expect-error
-const getCodePoint = String.prototype.codePointAt
- ? // @ts-expect-error
- (input, position) => input.codePointAt(position)
- : (input, position) =>
- (input.charCodeAt(position) - 0xd800) * 0x400 +
- input.charCodeAt(position + 1) -
- 0xdc00 +
- 0x10000;
-
-/**
- * @param {string} macroText macro text
- * @param {RegExp} macroRegExp macro reg exp
- * @param {(input: string) => string} macroReplacer macro replacer
- * @returns {string} result
- */
-const replaceUsingRegExp = (macroText, macroRegExp, macroReplacer) => {
- macroRegExp.lastIndex = 0;
- let replaceMatch = macroRegExp.exec(macroText);
- let replaceResult;
- if (replaceMatch) {
- replaceResult = "";
- let replaceLastIndex = 0;
- do {
- if (replaceLastIndex !== replaceMatch.index) {
- replaceResult += macroText.slice(replaceLastIndex, replaceMatch.index);
- }
- const replaceInput = replaceMatch[0];
- replaceResult += macroReplacer(replaceInput);
- replaceLastIndex = replaceMatch.index + replaceInput.length;
- } while ((replaceMatch = macroRegExp.exec(macroText)));
-
- if (replaceLastIndex !== macroText.length) {
- replaceResult += macroText.slice(replaceLastIndex);
- }
- } else {
- replaceResult = macroText;
- }
- return replaceResult;
-};
-
-const references = {
- "<": "<",
- ">": ">",
- '"': """,
- "'": "'",
- "&": "&",
-};
-
-/**
- * @param {string} text text
- * @returns {string} encoded text
- */
-function encode(text) {
- if (!text) {
- return "";
- }
-
- return replaceUsingRegExp(text, /[<>'"&]/g, (input) => {
- let result = references[/** @type {keyof typeof references} */ (input)];
- if (!result) {
- const code =
- input.length > 1 ? getCodePoint(input, 0) : input.charCodeAt(0);
- result = `${code};`;
- }
- return result;
- });
-}
-
/**
* @typedef {object} Context
* @property {"warning" | "error"} level level
@@ -299,97 +224,6 @@ const listenToUnhandledRejection = (callback) => {
};
};
-// Styles are inspired by `react-error-overlay`
-
-const msgStyles = {
- error: {
- backgroundColor: "rgba(206, 17, 38, 0.1)",
- color: "#fccfcf",
- },
- warning: {
- backgroundColor: "rgba(251, 245, 180, 0.1)",
- color: "#fbf5b4",
- },
-};
-const iframeStyle = {
- position: "fixed",
- top: "0px",
- left: "0px",
- right: "0px",
- bottom: "0px",
- width: "100vw",
- height: "100vh",
- border: "none",
- "z-index": 9999999999,
-};
-const containerStyle = {
- position: "fixed",
- boxSizing: "border-box",
- left: "0px",
- top: "0px",
- right: "0px",
- bottom: "0px",
- width: "100vw",
- height: "100vh",
- fontSize: "large",
- padding: "2rem 2rem 4rem 2rem",
- lineHeight: "1.2",
- whiteSpace: "pre-wrap",
- overflow: "auto",
- backgroundColor: "rgba(0, 0, 0, 0.9)",
- color: "white",
-};
-const headerStyle = {
- color: "#e83b46",
- fontSize: "2em",
- whiteSpace: "pre-wrap",
- fontFamily: "sans-serif",
- margin: "0 2rem 2rem 0",
- flex: "0 0 auto",
- maxHeight: "50%",
- overflow: "auto",
-};
-const dismissButtonStyle = {
- color: "#ffffff",
- lineHeight: "1rem",
- fontSize: "1.5rem",
- padding: "1rem",
- cursor: "pointer",
- position: "absolute",
- right: "0px",
- top: "0px",
- backgroundColor: "transparent",
- border: "none",
-};
-const msgTypeStyle = {
- color: "#e83b46",
- fontSize: "1.2em",
- marginBottom: "1rem",
- fontFamily: "sans-serif",
-};
-const msgTextStyle = {
- lineHeight: "1.5",
- fontSize: "1rem",
- fontFamily: "Menlo, Consolas, monospace",
-};
-
-// ANSI HTML
-
-const colors = {
- reset: ["transparent", "transparent"],
- black: "181818",
- red: "E36049",
- green: "B3CB74",
- yellow: "FFD080",
- blue: "7CAFC2",
- magenta: "7FACCA",
- cyan: "C3C2EF",
- lightgrey: "EBE7E3",
- darkgrey: "6D7891",
-};
-
-ansiHTML.setColors(colors);
-
/** @typedef {Error & { file?: string, moduleName?: string, moduleIdentifier?: string, loc?: string, message?: string, stack?: string | string[] }} Message */
/**
@@ -444,205 +278,12 @@ const formatProblem = (type, item) => {
* @returns {StateMachine} overlay
*/
const createOverlay = (options) => {
- /** @type {HTMLIFrameElement | null | undefined} */
- let iframeContainerElement;
- /** @type {HTMLDivElement | null | undefined} */
- let containerElement;
- /** @type {HTMLDivElement | null | undefined} */
- let headerElement;
- /** @type {((element: HTMLDivElement) => void)[]} */
- let onLoadQueue = [];
- /** @type {Omit | undefined} */
- let overlayTrustedTypesPolicy;
-
- /** @typedef {Extract} CSSStyleDeclarationKeys */
-
- /**
- * @param {HTMLElement} element element
- * @param {Partial} style style
- */
- function applyStyle(element, style) {
- Object.keys(style).forEach((prop) => {
- element.style[/** @type {CSSStyleDeclarationKeys} */ (prop)] =
- /** @type {string} */
- (style[/** @type {CSSStyleDeclarationKeys} */ (prop)]);
- });
- }
-
- /**
- * @param {string | false | undefined} trustedTypesPolicyName trusted types police name
- */
- 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);
-
- 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" });
- });
-
- contentElement.appendChild(headerElement);
- contentElement.appendChild(closeButtonElement);
- contentElement.appendChild(containerElement);
-
- /** @type {Document} */
- (
- /** @type {HTMLIFrameElement} */
- (iframeContainerElement).contentDocument
- ).body.appendChild(contentElement);
-
- onLoadQueue.forEach((onLoad) => {
- onLoad(/** @type {HTMLDivElement} */ (contentElement));
- });
- onLoadQueue = [];
-
- /** @type {HTMLIFrameElement} */
- (iframeContainerElement).onload = null;
- };
-
- document.body.appendChild(iframeContainerElement);
- }
-
- /**
- * @param {(element: HTMLDivElement) => void} callback callback
- * @param {string | false | undefined} trustedTypesPolicyName trusted types policy name
- */
- function ensureOverlayExists(callback, trustedTypesPolicyName) {
- if (containerElement) {
- // @ts-expect-error https://github.com/microsoft/TypeScript/issues/30024
- containerElement.innerHTML = overlayTrustedTypesPolicy
- ? overlayTrustedTypesPolicy.createHTML("")
- : "";
- // Everything is ready, call the callback right away.
- callback(containerElement);
-
- return;
- }
-
- onLoadQueue.push(callback);
-
- if (iframeContainerElement) {
- return;
- }
-
- createContainer(trustedTypesPolicyName);
- }
-
- // Successful compilation.
- /**
- * @returns {void}
- */
- function hide() {
- if (!iframeContainerElement) {
- return;
- }
-
- // Clean up and reset internal state.
- document.body.removeChild(iframeContainerElement);
-
- iframeContainerElement = null;
- containerElement = null;
- }
-
- // Compilation with errors (e.g. syntax error or missing modules).
- /**
- * @param {string} type type
- * @param {(string | Message)[]} messages messages
- * @param {undefined | false | string} trustedTypesPolicyName trusted types policy name
- * @param {"build" | "runtime"} messageSource message source
- */
- function show(type, messages, trustedTypesPolicyName, messageSource) {
- ensureOverlayExists(() => {
- /** @type {HTMLDivElement} */
- (headerElement).innerText =
- messageSource === "runtime"
- ? "Uncaught runtime errors:"
- : "Compiled with problems:";
-
- 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 typeElement = document.createElement("div");
- const { header, body } = formatProblem(type, message);
-
- typeElement.innerText = header;
- applyStyle(typeElement, msgTypeStyle);
-
- if (typeof message !== "string" && 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);
-
- // @ts-expect-error https://github.com/microsoft/TypeScript/issues/30024
- messageTextNode.innerHTML = overlayTrustedTypesPolicy
- ? overlayTrustedTypesPolicy.createHTML(text)
- : text;
-
- entryElement.appendChild(typeElement);
- entryElement.appendChild(messageTextNode);
-
- /** @type {HTMLDivElement} */
- (containerElement).appendChild(entryElement);
- });
- }, trustedTypesPolicyName);
- }
+ const sharedOverlay = configureOverlay({
+ trustedTypesPolicyName:
+ options.trustedTypesPolicyName || "webpack-dev-server#overlay",
+ openEditorEndpoint: "/webpack-dev-server/open-editor",
+ paginate: false,
+ });
/** @type {(event: KeyboardEvent) => void} */
let handleEscapeKey;
@@ -653,12 +294,47 @@ const createOverlay = (options) => {
const hideOverlayWithEscCleanup = () => {
window.removeEventListener("keydown", handleEscapeKey);
- hide();
+ sharedOverlay.clear("webpack-dev-server");
};
const overlayService = createOverlayMachine({
- showOverlay: ({ level = "error", messages, messageSource }) =>
- show(level, messages, options.trustedTypesPolicyName, messageSource),
+ showOverlay: ({ level = "error", messages }) => {
+ sharedOverlay.showProblems(
+ level === "warning" ? "warnings" : "errors",
+ messages.map((message) => {
+ const { header, body } = formatProblem(level, message);
+ const location = header.replace(/^(ERROR|WARNING)( in )?/, "");
+ return location ? `${location}\n${body}` : body;
+ }),
+ "webpack-dev-server",
+ );
+
+ // The shared renderer links display paths. Resolve those links to the
+ // compiler's module identifiers, which can be outside the server cwd.
+ const frame = /** @type {HTMLIFrameElement | null} */ (
+ document.getElementById("webpack-dev-middleware-hot-overlay")
+ );
+ const links =
+ frame?.contentDocument?.querySelectorAll("[data-open-file]");
+ links?.forEach((link) => {
+ const file = link.getAttribute("data-open-file") || "";
+ messages.forEach((message) => {
+ if (typeof message === "string" || !message.moduleIdentifier) {
+ return;
+ }
+ const name = (message.moduleName || message.file || "").replace(
+ /^(\s|\S)*!/,
+ "",
+ );
+ if (name && file.indexOf(`${name}:`) === 0) {
+ link.setAttribute(
+ "data-open-file",
+ `${message.moduleIdentifier.replace(/^[^|]*\|/, "").replace(/^(\s|\S)*!/, "")}${file.slice(name.length)}`,
+ );
+ }
+ });
+ });
+ },
hideOverlay: hideOverlayWithEscCleanup,
});
/**
diff --git a/client-src/progress.js b/client-src/progress.js
index acac9ed77a..7eb9ca1861 100644
--- a/client-src/progress.js
+++ b/client-src/progress.js
@@ -1,238 +1,31 @@
-/**
- * @returns {boolean} true when custom elements supported, otherwise false
- */
-export function isProgressSupported() {
- return (
- "customElements" in self && Boolean(HTMLElement.prototype.attachShadow)
- );
-}
+import { hide, show } from "webpack-dev-middleware/client/indicator";
+
+const source = "webpack-dev-server";
/**
+ * @param {number} percent compilation progress
+ * @param {string} message progress message
* @returns {void}
*/
-export function defineProgressElement() {
- if (customElements.get("wds-progress")) {
+export function showProgress(percent, message) {
+ if (
+ typeof document === "undefined" ||
+ typeof HTMLElement === "undefined" ||
+ !HTMLElement.prototype.attachShadow
+ ) {
return;
}
- class WebpackDevServerProgress extends HTMLElement {
- constructor() {
- super();
- this.attachShadow({ mode: "open" });
- this.maxDashOffset = -219.99078369140625;
- this.animationTimer = null;
- }
-
- #reset() {
- clearTimeout(this.animationTimer);
- this.animationTimer = null;
-
- const typeAttr = this.getAttribute("type")?.toLowerCase();
- this.type = typeAttr === "circular" ? "circular" : "linear";
-
- const innerHTML =
- this.type === "circular"
- ? WebpackDevServerProgress.#circularTemplate()
- : WebpackDevServerProgress.#linearTemplate();
- /** @type {ShadowRoot} */
- (this.shadowRoot).innerHTML = innerHTML;
-
- const progressValue = this.getAttribute("progress");
- this.initialProgress = progressValue ? Number(progressValue) : 0;
-
- this.#update(this.initialProgress);
- }
-
- static #circularTemplate() {
- return `
-
-
-
-
-
- 0
- %
-
-
- `;
- }
-
- static #linearTemplate() {
- return `
-
-
- `;
- }
-
- connectedCallback() {
- this.#reset();
- }
-
- static get observedAttributes() {
- return ["progress", "type"];
- }
-
- /**
- * @param {string} name name
- * @param {string} oldValue old value
- * @param {string} newValue new value
- */
- attributeChangedCallback(name, oldValue, newValue) {
- if (name === "progress") {
- this.#update(Number(newValue));
- } else if (name === "type") {
- this.#reset();
- }
- }
-
- /**
- * @param {number} percent percent
- */
- #update(percent) {
- const shadowRoot = /** @type {ShadowRoot} */ (this.shadowRoot);
- const element =
- /** @type {HTMLElement} */
- (shadowRoot.querySelector("#progress"));
- if (this.type === "circular") {
- const path =
- /** @type {SVGPathElement} */
- (shadowRoot.querySelector("path"));
- const value =
- /** @type {HTMLElement} */
- (shadowRoot.querySelector("#percent-value"));
- const offset = ((100 - percent) / 100) * this.maxDashOffset;
-
- path.style.strokeDashoffset = String(offset);
- value.textContent = String(percent);
- } else {
- element.style.width = `${percent}%`;
- }
-
- if (percent >= 100) {
- this.#hide();
- } else if (percent > 0) {
- this.#show();
- }
- }
-
- #show() {
- const shadowRoot = /** @type {ShadowRoot} */ (this.shadowRoot);
- const element =
- /** @type {HTMLElement} */
- (shadowRoot.querySelector("#progress"));
- element.classList.remove("hidden");
- }
-
- #hide() {
- const shadowRoot = /** @type {ShadowRoot} */ (this.shadowRoot);
- const element =
- /** @type {HTMLElement} */
- (shadowRoot.querySelector("#progress"));
- if (this.type === "circular") {
- element.classList.add("disappear");
- element.addEventListener(
- "animationend",
- () => {
- element.classList.add("hidden");
- this.#update(0);
- },
- { once: true },
- );
- } else if (this.type === "linear") {
- element.classList.add("disappear");
- this.animationTimer = setTimeout(() => {
- element.classList.remove("disappear");
- element.classList.add("hidden");
- element.style.width = "0%";
- this.animationTimer = null;
- }, 800);
- }
- }
+ if (percent >= 100) {
+ hide(source);
+ } else {
+ show(`${percent}% - ${message}`, percent, source);
}
+}
- customElements.define("wds-progress", WebpackDevServerProgress);
+/**
+ * @returns {void}
+ */
+export function hideProgress() {
+ hide(source);
}
diff --git a/package-lock.json b/package-lock.json
index 67f7f2df12..8923ab5046 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,6 @@
"@types/serve-index": "^1.9.4",
"@types/serve-static": "^2.2.0",
"@types/ws": "^8.18.1",
- "ansi-html-community": "^0.0.8",
"bonjour-service": "^1.3.0",
"chokidar": "^5.0.0",
"compression": "^1.8.1",
@@ -32,7 +31,7 @@
"selfsigned": "^5.5.0",
"serve-index": "^1.9.2",
"tinyglobby": "^0.2.15",
- "webpack-dev-middleware": "^8.0.3",
+ "webpack-dev-middleware": "^8.3.0",
"ws": "^8.20.0"
},
"bin": {
@@ -3780,13 +3779,13 @@
}
},
"node_modules/@jsonjoy.com/fs-core": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.1.tgz",
- "integrity": "sha512-YrEi/ZPmgc+GfdO0esBF04qv8boK9Dg9WpRQw/+vM8Qt3nnVIJWIa8HwZ/LXVZ0DB11XUROM8El/7yYTJX+WtA==",
+ "version": "4.71.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.71.0.tgz",
+ "integrity": "sha512-9DFR/j+bm+tig1abs1CWS1/r0IVjNUdTp/+q6R/PXayXpO1rgbUh7tkanGYP40I0zdxbOreN3tmzBpVHgfMKzg==",
"license": "Apache-2.0",
"dependencies": {
- "@jsonjoy.com/fs-node-builtins": "4.57.1",
- "@jsonjoy.com/fs-node-utils": "4.57.1",
+ "@jsonjoy.com/fs-node-builtins": "4.71.0",
+ "@jsonjoy.com/fs-node-utils": "4.71.0",
"thingies": "^2.5.0"
},
"engines": {
@@ -3801,14 +3800,14 @@
}
},
"node_modules/@jsonjoy.com/fs-fsa": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.1.tgz",
- "integrity": "sha512-ooEPvSW/HQDivPDPZMibHGKZf/QS4WRir1czGZmXmp3MsQqLECZEpN0JobrD8iV9BzsuwdIv+PxtWX9WpPLsIA==",
+ "version": "4.71.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.71.0.tgz",
+ "integrity": "sha512-dRw5ojxzep3lntVGVBLzQUMYj1hXLH+DAz9sK0vKU+594x/zA2nYPOiitlyhYtOJ2nv1FXNq7c55rgwANknIWw==",
"license": "Apache-2.0",
"dependencies": {
- "@jsonjoy.com/fs-core": "4.57.1",
- "@jsonjoy.com/fs-node-builtins": "4.57.1",
- "@jsonjoy.com/fs-node-utils": "4.57.1",
+ "@jsonjoy.com/fs-core": "4.71.0",
+ "@jsonjoy.com/fs-node-builtins": "4.71.0",
+ "@jsonjoy.com/fs-node-utils": "4.71.0",
"thingies": "^2.5.0"
},
"engines": {
@@ -3823,16 +3822,16 @@
}
},
"node_modules/@jsonjoy.com/fs-node": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.1.tgz",
- "integrity": "sha512-3YaKhP8gXEKN+2O49GLNfNb5l2gbnCFHyAaybbA2JkkbQP3dpdef7WcUaHAulg/c5Dg4VncHsA3NWAUSZMR5KQ==",
+ "version": "4.71.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.71.0.tgz",
+ "integrity": "sha512-yt5Ak0otPdHPIlmMvF16aLG2qSZL52EYJ7HOkYpBcIPWw+kmT1FtTSj4oiV2OUkJbG8pLzA+RhMgrlRl85QuBw==",
"license": "Apache-2.0",
"dependencies": {
- "@jsonjoy.com/fs-core": "4.57.1",
- "@jsonjoy.com/fs-node-builtins": "4.57.1",
- "@jsonjoy.com/fs-node-utils": "4.57.1",
- "@jsonjoy.com/fs-print": "4.57.1",
- "@jsonjoy.com/fs-snapshot": "4.57.1",
+ "@jsonjoy.com/fs-core": "4.71.0",
+ "@jsonjoy.com/fs-node-builtins": "4.71.0",
+ "@jsonjoy.com/fs-node-utils": "4.71.0",
+ "@jsonjoy.com/fs-print": "4.71.0",
+ "@jsonjoy.com/fs-snapshot": "4.71.0",
"glob-to-regex.js": "^1.0.0",
"thingies": "^2.5.0"
},
@@ -3848,9 +3847,9 @@
}
},
"node_modules/@jsonjoy.com/fs-node-builtins": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.1.tgz",
- "integrity": "sha512-XHkFKQ5GSH3uxm8c3ZYXVrexGdscpWKIcMWKFQpMpMJc8gA3AwOMBJXJlgpdJqmrhPyQXxaY9nbkNeYpacC0Og==",
+ "version": "4.71.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.71.0.tgz",
+ "integrity": "sha512-BSzl+QFSxZF58BxGjV1wqCJ+qSn3b2IZnb+z3hDQq6goqOO5RuxF8gRihjsFx17AfpEpa7XjYcP8XpQtDU8RrQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=10.0"
@@ -3864,14 +3863,14 @@
}
},
"node_modules/@jsonjoy.com/fs-node-to-fsa": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.1.tgz",
- "integrity": "sha512-pqGHyWWzNck4jRfaGV39hkqpY5QjRUQ/nRbNT7FYbBa0xf4bDG+TE1Gt2KWZrSkrkZZDE3qZUjYMbjwSliX6pg==",
+ "version": "4.71.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.71.0.tgz",
+ "integrity": "sha512-OlXBZKIeDx5bIGcQmn0w+nVkLheCiuQSepKiBAiWSWimSdn8Q6Yc9ih2y8zStcJk31fieqnov9V+o8XTWn/5Rw==",
"license": "Apache-2.0",
"dependencies": {
- "@jsonjoy.com/fs-fsa": "4.57.1",
- "@jsonjoy.com/fs-node-builtins": "4.57.1",
- "@jsonjoy.com/fs-node-utils": "4.57.1"
+ "@jsonjoy.com/fs-fsa": "4.71.0",
+ "@jsonjoy.com/fs-node-builtins": "4.71.0",
+ "@jsonjoy.com/fs-node-utils": "4.71.0"
},
"engines": {
"node": ">=10.0"
@@ -3885,12 +3884,13 @@
}
},
"node_modules/@jsonjoy.com/fs-node-utils": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.1.tgz",
- "integrity": "sha512-vp+7ZzIB8v43G+GLXTS4oDUSQmhAsRz532QmmWBbdYA20s465JvwhkSFvX9cVTqRRAQg+vZ7zWDaIEh0lFe2gw==",
+ "version": "4.71.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.71.0.tgz",
+ "integrity": "sha512-YtzCL3jbKYx6LHxqS9ymJ9Ob7SO9cucI+kpNO3LijGNFEjFbvNsTlHkvFgocAMAjUk96TpQTCsYkzFQi1CdlAA==",
"license": "Apache-2.0",
"dependencies": {
- "@jsonjoy.com/fs-node-builtins": "4.57.1"
+ "@jsonjoy.com/fs-node-builtins": "4.71.0",
+ "glob-to-regex.js": "^1.0.1"
},
"engines": {
"node": ">=10.0"
@@ -3904,12 +3904,12 @@
}
},
"node_modules/@jsonjoy.com/fs-print": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.1.tgz",
- "integrity": "sha512-Ynct7ZJmfk6qoXDOKfpovNA36ITUx8rChLmRQtW08J73VOiuNsU8PB6d/Xs7fxJC2ohWR3a5AqyjmLojfrw5yw==",
+ "version": "4.71.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.71.0.tgz",
+ "integrity": "sha512-OhfDSdvyO8uGV0U11OP+mOeVCPgjuP1HqCGR/TdBQLxHoDEWJAK3KOP5fPXo57H7i3G0x0Vmv8xPRHDle4XGzA==",
"license": "Apache-2.0",
"dependencies": {
- "@jsonjoy.com/fs-node-utils": "4.57.1",
+ "@jsonjoy.com/fs-node-utils": "4.71.0",
"tree-dump": "^1.1.0"
},
"engines": {
@@ -3924,13 +3924,13 @@
}
},
"node_modules/@jsonjoy.com/fs-snapshot": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.1.tgz",
- "integrity": "sha512-/oG8xBNFMbDXTq9J7vepSA1kerS5vpgd3p5QZSPd+nX59uwodGJftI51gDYyHRpP57P3WCQf7LHtBYPqwUg2Bg==",
+ "version": "4.71.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.71.0.tgz",
+ "integrity": "sha512-md+Wov365xa9A3nfW9YQTHLcweHHAee/e/0UoVRbldEHxboPJknuO3sEkNVVECO0dTqKra/ERaQylAMRrGWd0Q==",
"license": "Apache-2.0",
"dependencies": {
"@jsonjoy.com/buffers": "^17.65.0",
- "@jsonjoy.com/fs-node-utils": "4.57.1",
+ "@jsonjoy.com/fs-node-utils": "4.71.0",
"@jsonjoy.com/json-pack": "^17.65.0",
"@jsonjoy.com/util": "^17.65.0"
},
@@ -10420,9 +10420,9 @@
}
},
"node_modules/glob-to-regex.js": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.0.1.tgz",
- "integrity": "sha512-CG/iEvgQqfzoVsMUbxSJcwbG2JwyZ3naEqPkeltwl0BSS8Bp83k3xlGms+0QdWFUAwV+uvo80wNswKF6FWEkKg==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz",
+ "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=10.0"
@@ -13250,19 +13250,19 @@
}
},
"node_modules/memfs": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.1.tgz",
- "integrity": "sha512-WvzrWPwMQT+PtbX2Et64R4qXKK0fj/8pO85MrUCzymX3twwCiJCdvntW3HdhG1teLJcHDDLIKx5+c3HckWYZtQ==",
+ "version": "4.71.0",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.71.0.tgz",
+ "integrity": "sha512-Zwrk7TpTXBkic7taZL+2QeppbauG0QOufRsT1zuXDYLW8jCajH0W1VSZ17+I7ODqiNbH9J1Vf1QJCqFlCfurDg==",
"license": "Apache-2.0",
"dependencies": {
- "@jsonjoy.com/fs-core": "4.57.1",
- "@jsonjoy.com/fs-fsa": "4.57.1",
- "@jsonjoy.com/fs-node": "4.57.1",
- "@jsonjoy.com/fs-node-builtins": "4.57.1",
- "@jsonjoy.com/fs-node-to-fsa": "4.57.1",
- "@jsonjoy.com/fs-node-utils": "4.57.1",
- "@jsonjoy.com/fs-print": "4.57.1",
- "@jsonjoy.com/fs-snapshot": "4.57.1",
+ "@jsonjoy.com/fs-core": "4.71.0",
+ "@jsonjoy.com/fs-fsa": "4.71.0",
+ "@jsonjoy.com/fs-node": "4.71.0",
+ "@jsonjoy.com/fs-node-builtins": "4.71.0",
+ "@jsonjoy.com/fs-node-to-fsa": "4.71.0",
+ "@jsonjoy.com/fs-node-utils": "4.71.0",
+ "@jsonjoy.com/fs-print": "4.71.0",
+ "@jsonjoy.com/fs-snapshot": "4.71.0",
"@jsonjoy.com/json-pack": "^1.11.0",
"@jsonjoy.com/util": "^1.9.0",
"glob-to-regex.js": "^1.0.1",
@@ -13273,9 +13273,6 @@
"funding": {
"type": "github",
"url": "https://github.com/sponsors/streamich"
- },
- "peerDependencies": {
- "tslib": "2"
}
},
"node_modules/memorystream": {
@@ -15691,12 +15688,16 @@
"license": "MIT"
},
"node_modules/range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
+ "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/raw-body": {
@@ -18415,15 +18416,15 @@
}
},
"node_modules/webpack-dev-middleware": {
- "version": "8.0.3",
- "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-8.0.3.tgz",
- "integrity": "sha512-zWrde9VZDiRaFuWsjHO40wm9LxxtXEk8DdzFXdU7eU5ZpiANnZZDBbZgN3guxbEoKqUHd9YupBmynyioz42nkA==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-8.3.0.tgz",
+ "integrity": "sha512-jFNNMB29ugmkEUFW9cpWNqcWFQNwuMTMVoRpKVAiz/5nh3gA4x9x6DRPhNHGc/SLre7pEP9yrAPDAmCvdCblKg==",
"license": "MIT",
"dependencies": {
- "memfs": "^4.56.10",
+ "ansi-html-community": "^0.0.8",
+ "memfs": "^4.68.2",
"mime-types": "^3.0.2",
- "on-finished": "^2.4.1",
- "range-parser": "^1.2.1",
+ "range-parser": "^1.3.0",
"schema-utils": "^4.3.3"
},
"engines": {
diff --git a/package.json b/package.json
index 38fbb1dd59..5971216253 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,6 @@
"@types/serve-index": "^1.9.4",
"@types/serve-static": "^2.2.0",
"@types/ws": "^8.18.1",
- "ansi-html-community": "^0.0.8",
"bonjour-service": "^1.3.0",
"chokidar": "^5.0.0",
"compression": "^1.8.1",
@@ -81,7 +80,7 @@
"selfsigned": "^5.5.0",
"serve-index": "^1.9.2",
"tinyglobby": "^0.2.15",
- "webpack-dev-middleware": "^8.0.3",
+ "webpack-dev-middleware": "^8.3.0",
"ws": "^8.20.0"
},
"devDependencies": {
diff --git a/test/client/overlay.test.js b/test/client/overlay.test.js
new file mode 100644
index 0000000000..b40e0c6994
--- /dev/null
+++ b/test/client/overlay.test.js
@@ -0,0 +1,55 @@
+import "../helpers/jsdom-setup.js";
+
+import { afterEach, describe, it } from "node:test";
+import { expect } from "expect";
+import { clear, showProblems } from "webpack-dev-middleware/client/overlay";
+import { createOverlay } from "../../client-src/overlay.js";
+
+const selector = "#webpack-dev-middleware-hot-overlay";
+
+describe("shared overlay", () => {
+ afterEach(() => clear());
+
+ it("should render compiler messages safely and link files to the editor", () => {
+ const overlay = createOverlay({ catchRuntimeError: false });
+ overlay.send({
+ type: "BUILD_ERROR",
+ level: "error",
+ messages: [
+ {
+ moduleName: "./src/app.js",
+ moduleIdentifier: "javascript/esm|/project/src/app.js",
+ loc: "2:3",
+ message: "",
+ },
+ ],
+ });
+ const frame = globalThis.document.querySelector(selector);
+ const document = frame.contentDocument;
+ expect(document.querySelector("script")).toBeNull();
+ expect(document.querySelector("[data-open-file]").dataset.openFile).toBe(
+ "/project/src/app.js:2:3",
+ );
+ expect(document.body.textContent).toContain("");
+ overlay.send({ type: "DISMISS" });
+ expect(globalThis.document.querySelector(selector)).toBeNull();
+ });
+
+ it("should preserve problems reported by another client on a clean build", () => {
+ showProblems("errors", ["Other client error"], "other-client");
+ const overlay = createOverlay({ catchRuntimeError: false });
+ overlay.send({
+ type: "BUILD_ERROR",
+ level: "error",
+ messages: ["Server error"],
+ });
+ expect(document.querySelectorAll(selector)).toHaveLength(1);
+ overlay.send({ type: "DISMISS" });
+ expect(
+ document.querySelector(selector).contentDocument.body.textContent,
+ ).toContain("Other client error");
+ expect(
+ document.querySelector(selector).contentDocument.body.textContent,
+ ).not.toContain("Server error");
+ });
+});
diff --git a/test/client/progress.test.js b/test/client/progress.test.js
new file mode 100644
index 0000000000..d0514176a7
--- /dev/null
+++ b/test/client/progress.test.js
@@ -0,0 +1,38 @@
+import "../helpers/jsdom-setup.js";
+
+import { afterEach, describe, it } from "node:test";
+import { expect } from "expect";
+import { hide, show } from "webpack-dev-middleware/client/indicator";
+import { hideProgress, showProgress } from "../../client-src/progress.js";
+
+const selector = "#webpack-dev-middleware-building-indicator";
+
+describe("progress", () => {
+ afterEach(() => hide());
+
+ it("should display progress and remove the indicator on completion", () => {
+ showProgress(25, "building");
+ const indicator = document.querySelector(selector);
+ expect(indicator.shadowRoot.textContent).toContain("25% - building");
+ expect(indicator.shadowRoot.querySelector("svg").style.display).toBe(
+ "block",
+ );
+
+ showProgress(100, "completed");
+ expect(document.querySelector(selector)).toBeNull();
+
+ showProgress(10, "rebuilding");
+ expect(document.querySelector(selector).shadowRoot.textContent).toContain(
+ "10% - rebuilding",
+ );
+ });
+
+ it("should preserve another client's indicator when this client finishes", () => {
+ show("Other build", 10, "other-client");
+ showProgress(50, "building");
+ hideProgress();
+ expect(document.querySelector(selector)).not.toBeNull();
+ hide("other-client");
+ expect(document.querySelector(selector)).toBeNull();
+ });
+});
diff --git a/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 b/test/e2e/__snapshots__/overlay.test.js.snap.webpack5
index cc27c7c873..cbe57e8a71 100644
--- a/test/e2e/__snapshots__/overlay.test.js.snap.webpack5
+++ b/test/e2e/__snapshots__/overlay.test.js.snap.webpack5
@@ -44,7 +44,7 @@ exports[`overlay > should not show initially, then show on an error and allow to
+"
-
- Compiled with problems:
-
×
-
-
+
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 > \`;
-
-
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -176,7 +203,7 @@ exports[`overlay > should not show initially, then show on an error, then hide o
+"
-
- Compiled with problems:
-
×
-
-
+
ERROR
-
- 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 > \`;
-
-
+ 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
+
> \`;
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -308,7 +362,7 @@ exports[`overlay > should not show initially, then show on an error, then show o
+"
-
- Compiled with problems:
-
×
-
-
+
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 > \`;
-
-
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -424,7 +505,7 @@ exports[`overlay > should not show initially, then show on an error, then show o
+"
-
- Compiled with problems:
-
×
-
-
+
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
-
-
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -556,7 +664,7 @@ exports[`overlay > should show a warning after invalidation 1`] = `
+"
-
- Compiled with problems:
-
×
-
-
+
WARNING
-
- WARNING
-
-
- Warning from compilation
-
-
+ in Warning from compilation
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -667,7 +776,7 @@ exports[`overlay > should show a warning and error for initial compilation 1`] =
+"
-
- Compiled with problems:
-
×
-
-
+
ERROR
-
- ERROR
-
-
- Warning from compilation
-
-
-
+
+
ERROR
-
- ERROR
-
-
- Warning from compilation
-
-
-
+
+
ERROR
-
- ERROR
-
-
- Error from compilation. Can't find 'test' module.
-
-
-
+
+
ERROR
-
- ERROR
-
-
- Error from compilation. Can't find 'test' module.
-
-
-
+
+
ERROR
-
- ERROR
-
-
- Error from compilation. Can't find 'test' module.
-
-
+ in Error from compilation. Can't find 'test' module.
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -886,7 +936,7 @@ exports[`overlay > should show a warning and error for initial compilation and p
+"
-
- Compiled with problems:
-
×
-
-
+
ERROR
-
- ERROR
-
-
- <strong>strong</strong>
-
-
-
+
+
ERROR
-
- ERROR
-
-
- <strong>strong</strong>
-
-
+ in <strong>strong</strong>
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -1024,7 +1060,7 @@ exports[`overlay > should show a warning and hide them after closing connection
+"
-
- Compiled with problems:
-
×
-
-
+
WARNING
-
- WARNING
-
-
- Warning from compilation
-
-
+ in Warning from compilation
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -1143,7 +1180,7 @@ exports[`overlay > should show a warning for initial compilation 1`] = `
+"
-
- Compiled with problems:
-
×
-
-
+
WARNING
-
- WARNING
-
-
- Warning from compilation
-
-
+ in Warning from compilation
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -1254,7 +1292,7 @@ exports[`overlay > should show a warning when \"client.overlay.errors\" is \"tru
+"
-
- Compiled with problems:
-
×
-
-
+
WARNING
-
- WARNING
-
-
- Warning from compilation
-
-
+ in Warning from compilation
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -1365,7 +1404,7 @@ exports[`overlay > should show a warning when \"client.overlay.warnings\" is \"t
+"
-
- Compiled with problems:
-
×
-
-
+
WARNING
-
- WARNING
-
-
- Warning from compilation
-
-
+ in Warning from compilation
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -1476,7 +1516,7 @@ exports[`overlay > should show a warning when \"client.overlay\" is \"true\" 1`]
+"
-
- Compiled with problems:
-
×
-
-
+
WARNING
-
- WARNING
-
-
- Warning from compilation
-
-
+ in Warning from compilation
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -1587,7 +1628,7 @@ exports[`overlay > should show an ansi formatted error for initial compilation 1
+"
-
- Compiled with problems:
-
×
-
-
+
ERROR
+ in
+
-
- ERROR
-
-
-
- 18 |
- Render
- ansi formatted text
-
-
+
18 |
+
Render
+
ansi formatted text
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -1709,7 +1752,7 @@ exports[`overlay > should show an error after invalidation 1`] = `
+"
-
- Compiled with problems:
-
×
-
-
+
ERROR
-
- ERROR
-
-
- Error from compilation
-
-
+ in Error from compilation
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -1820,7 +1864,7 @@ exports[`overlay > should show an error for initial compilation 1`] = `
+"
-
- Compiled with problems:
-
×
-
-
+
ERROR
-
- ERROR
-
-
- Error from compilation. Can't find 'test' module.
-
-
+ in Error from compilation. Can't find 'test' module.
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -1931,7 +1976,7 @@ exports[`overlay > should show an error when \"client.overlay.errors\" is \"true
+"
-
- Compiled with problems:
-
×
-
-
+
ERROR
-
- ERROR
-
-
- Error from compilation. Can't find 'test' module.
-
-
+ in Error from compilation. Can't find 'test' module.
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -2042,7 +2088,7 @@ exports[`overlay > should show an error when \"client.overlay.warnings\" is \"tr
+"
-
- Compiled with problems:
-
×
-
-
+
WARNING
-
- WARNING
-
-
- Warning from compilation
-
-
+ in Warning from compilation
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -2153,7 +2200,7 @@ exports[`overlay > should show an error when \"client.overlay\" is \"true\" 1`]
+"
-
- Compiled with problems:
-
×
-
-
+
ERROR
-
- ERROR
-
-
- Error from compilation. Can't find 'test' module.
-
-
+ in Error from compilation. Can't find 'test' module.
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -2259,86 +2307,86 @@ exports[`overlay > should show an error when \"client.overlay\" is \"true\" 2`]
`;
exports[`overlay > should show error for uncaught promise rejection 1`] = `
-"
+"
-
- Uncaught runtime errors:
-
×
-
-
+
ERROR
-
- ERROR
-
-
- Async error at <anonymous>:3:26
-
-
+ in Async error at <anonymous>:3:26
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -2346,87 +2394,87 @@ exports[`overlay > should show error for uncaught promise rejection 1`] = `
`;
exports[`overlay > should show error for uncaught runtime error 1`] = `
-"
+"
-
- Uncaught runtime errors:
-
×
-
-
+
ERROR
-
- ERROR
-
-
- Injected error at throwError (<anonymous>:2:15) at
- <anonymous>:3:9
-
-
+ in Injected error at throwError (<anonymous>:2:15) at
+ <anonymous>:3:9
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -2439,7 +2487,7 @@ exports[`overlay > should show error when it is not filtered 1`] = `
@@ -2458,86 +2507,86 @@ exports[`overlay > should show error when it is not filtered 1`] = `
`;
exports[`overlay > should show error when it is not filtered 2`] = `
-"
+"
-
- Compiled with problems:
-
×
-
-
+
ERROR
-
- ERROR
-
-
- Unfiltered error
-
-
+ in Unfiltered error
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -2550,7 +2599,7 @@ exports[`overlay > should show overlay when Trusted Types are enabled 1`] = `
@@ -2569,86 +2619,86 @@ exports[`overlay > should show overlay when Trusted Types are enabled 1`] = `
`;
exports[`overlay > should show overlay when Trusted Types are enabled 2`] = `
-"
+"
-
- Compiled with problems:
-
×
-
-
+
ERROR
-
- ERROR
-
-
- Error from compilation. Can't find 'test' module.
-
-
+ in Error from compilation. Can't find 'test' module.
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -2661,7 +2711,7 @@ exports[`overlay > should show overlay when Trusted Types are enabled and the \"
@@ -2680,86 +2731,86 @@ exports[`overlay > should show overlay when Trusted Types are enabled and the \"
`;
exports[`overlay > should show overlay when Trusted Types are enabled and the \"require-trusted-types-for 'script'\" header was used 2`] = `
-"
+"
-
- Compiled with problems:
-
×
-
-
+
ERROR
-
- ERROR
-
-
- Error from compilation. Can't find 'test' module.
-
-
+ in Error from compilation. Can't find 'test' module.
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -2772,7 +2823,7 @@ exports[`overlay > should show overlay when \"Content-Security-Policy\" is \"def
@@ -2791,86 +2843,86 @@ exports[`overlay > should show overlay when \"Content-Security-Policy\" is \"def
`;
exports[`overlay > should show overlay when \"Content-Security-Policy\" is \"default-src 'self'\" was used 2`] = `
-"
+"
-
- Compiled with problems:
-
X
-
-
+
ERROR
-
- ERROR
-
-
- Error from compilation. Can't find 'test' module.
-
-
+ in Error from compilation. Can't find 'test' module.
+
+
+ Click outside, press Esc, or fix the code to dismiss.
@@ -2883,7 +2935,7 @@ exports[`overlay > should show warning when it is not filtered 1`] = `
@@ -2902,86 +2955,86 @@ exports[`overlay > should show warning when it is not filtered 1`] = `
`;
exports[`overlay > should show warning when it is not filtered 2`] = `
-"
+"
-
- Compiled with problems:
-
×
-
-
+
WARNING
-
- WARNING
-
-
- Unfiltered warning
-
-
+ in Unfiltered warning
+
+
+ Click outside, press Esc, or fix the code to dismiss.
diff --git a/test/e2e/overlay.test.js b/test/e2e/overlay.test.js
index 0861c62771..b9d7ab8d94 100644
--- a/test/e2e/overlay.test.js
+++ b/test/e2e/overlay.test.js
@@ -114,11 +114,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -159,11 +161,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -208,11 +212,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -255,11 +261,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -301,11 +309,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -340,7 +350,7 @@ describe("overlay", () => {
});
let pageHtml = await page.evaluate(() => document.body.outerHTML);
- let overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ let overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
t.assert.snapshot(
@@ -351,15 +361,17 @@ describe("overlay", () => {
fs.writeFileSync(pathToOverlayFixture, "`;");
- await page.waitForSelector("#webpack-dev-server-client-overlay");
+ await page.waitForSelector("#webpack-dev-middleware-hot-overlay");
- overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
pageHtml = await page.evaluate(() => document.body.outerHTML);
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -374,12 +386,12 @@ describe("overlay", () => {
fs.writeFileSync(pathToOverlayFixture, overlayFixtureCode);
- await page.waitForSelector("#webpack-dev-server-client-overlay", {
+ await page.waitForSelector("#webpack-dev-middleware-hot-overlay", {
hidden: true,
});
pageHtml = await page.evaluate(() => document.body.outerHTML);
- overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
t.assert.snapshot(
@@ -410,7 +422,7 @@ describe("overlay", () => {
});
let pageHtml = await page.evaluate(() => document.body.outerHTML);
- let overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ let overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
t.assert.snapshot(
@@ -421,15 +433,17 @@ describe("overlay", () => {
fs.writeFileSync(pathToOverlayFixture, "`;");
- await page.waitForSelector("#webpack-dev-server-client-overlay");
+ await page.waitForSelector("#webpack-dev-middleware-hot-overlay");
- overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
pageHtml = await page.evaluate(() => document.body.outerHTML);
let overlayFrame = await overlayHandle.contentFrame();
- let overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ let overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -444,16 +458,18 @@ describe("overlay", () => {
fs.writeFileSync(pathToOverlayFixture, "`;a");
- await page.waitForSelector("#webpack-dev-server-client-overlay", {
+ await page.waitForSelector("#webpack-dev-middleware-hot-overlay", {
hidden: true,
});
- await page.waitForSelector("#webpack-dev-server-client-overlay");
+ await page.waitForSelector("#webpack-dev-middleware-hot-overlay");
- overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
pageHtml = await page.evaluate(() => document.body.outerHTML);
overlayFrame = await overlayHandle.contentFrame();
- overlayHtml = await overlayFrame.evaluate(() => document.body.outerHTML);
+ overlayHtml = (await overlayFrame.evaluate(() => document.body.outerHTML))
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -468,12 +484,12 @@ describe("overlay", () => {
fs.writeFileSync(pathToOverlayFixture, overlayFixtureCode);
- await page.waitForSelector("#webpack-dev-server-client-overlay", {
+ await page.waitForSelector("#webpack-dev-middleware-hot-overlay", {
hidden: true,
});
pageHtml = await page.evaluate(() => document.body.outerHTML);
- overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
t.assert.snapshot(
@@ -504,7 +520,7 @@ describe("overlay", () => {
});
let pageHtml = await page.evaluate(() => document.body.outerHTML);
- let overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ let overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
t.assert.snapshot(
@@ -515,15 +531,17 @@ describe("overlay", () => {
fs.writeFileSync(pathToOverlayFixture, "`;");
- await page.waitForSelector("#webpack-dev-server-client-overlay");
+ await page.waitForSelector("#webpack-dev-middleware-hot-overlay");
- overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
pageHtml = await page.evaluate(() => document.body.outerHTML);
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -538,18 +556,18 @@ describe("overlay", () => {
const frame = await page
.frames()
- .find((item) => item.name() === "webpack-dev-server-client-overlay");
+ .find((item) => item.name() === "webpack-dev-middleware-hot-overlay");
const buttonHandle = await frame.$("button");
await buttonHandle.click();
- await page.waitForSelector("#webpack-dev-server-client-overlay", {
+ await page.waitForSelector("#webpack-dev-middleware-hot-overlay", {
hidden: true,
});
pageHtml = await page.evaluate(() => document.body.outerHTML);
- overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
t.assert.snapshot(
@@ -588,13 +606,13 @@ describe("overlay", () => {
fs.writeFileSync(pathToOverlayFixture, "`;");
- await page.waitForSelector("#webpack-dev-server-client-overlay");
+ await page.waitForSelector("#webpack-dev-middleware-hot-overlay");
const frame = page
.frames()
- .find((item) => item.name() === "webpack-dev-server-client-overlay");
+ .find((item) => item.name() === "webpack-dev-middleware-hot-overlay");
- const errorHandle = await frame.$("[data-can-open]");
+ const errorHandle = await frame.$("[data-open-file]");
await errorHandle.click();
@@ -636,7 +654,7 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
t.assert.snapshot(
@@ -678,7 +696,7 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
t.assert.snapshot(
@@ -725,7 +743,7 @@ describe("overlay", () => {
// Delay for the overlay to appear
await delay(1000);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
} finally {
@@ -764,11 +782,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -812,11 +832,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -862,11 +884,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -912,11 +936,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -960,7 +986,7 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
t.assert.snapshot(
@@ -1002,7 +1028,7 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
t.assert.snapshot(
@@ -1050,7 +1076,7 @@ describe("overlay", () => {
// Delay for the overlay to appear
await delay(1000);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
} finally {
@@ -1089,11 +1115,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -1137,11 +1165,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -1193,11 +1223,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
expect(
consoleMessages.filter((item) =>
@@ -1260,11 +1292,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
await page.goto(`http://localhost:${port}/`, {
waitUntil: "networkidle0",
@@ -1319,7 +1353,7 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
t.assert.snapshot(
await format(pageHtml, {
@@ -1360,11 +1394,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -1410,11 +1446,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -1459,11 +1497,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -1537,14 +1577,16 @@ describe("overlay", () => {
// Delay for the overlay to appear
await delay(1000);
- await page.waitForSelector("#webpack-dev-server-client-overlay");
+ await page.waitForSelector("#webpack-dev-middleware-hot-overlay");
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -1596,14 +1638,16 @@ describe("overlay", () => {
// Delay for the overlay to appear
await delay(1000);
- await page.waitForSelector("#webpack-dev-server-client-overlay");
+ await page.waitForSelector("#webpack-dev-middleware-hot-overlay");
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
@@ -1649,11 +1693,13 @@ describe("overlay", () => {
// Delay for the overlay to appear
await delay(1000);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(overlayHtml, {
@@ -1699,7 +1745,7 @@ describe("overlay", () => {
// Delay for the overlay to appear
await delay(1000);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
} finally {
@@ -1738,11 +1784,13 @@ describe("overlay", () => {
// Delay for the overlay to appear
await delay(1000);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(overlayHtml, {
@@ -1790,7 +1838,7 @@ describe("overlay", () => {
// Delay for the overlay to appear
await delay(1000);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
} finally {
@@ -1835,7 +1883,7 @@ describe("overlay", () => {
// Delay for the overlay to appear
await delay(1000);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
expect(overlayHandle).toBeNull();
} finally {
@@ -1879,11 +1927,13 @@ describe("overlay", () => {
await delay(1000);
const pageHtml = await page.evaluate(() => document.body.outerHTML);
- const overlayHandle = await page.$("#webpack-dev-server-client-overlay");
+ const overlayHandle = await page.$("#webpack-dev-middleware-hot-overlay");
const overlayFrame = await overlayHandle.contentFrame();
- const overlayHtml = await overlayFrame.evaluate(
- () => document.body.outerHTML,
- );
+ const overlayHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
t.assert.snapshot(
await format(pageHtml, {
From 6f2bb16b697046db1486dbd9907e7a11c5938d58 Mon Sep 17 00:00:00 2001
From: Sebastian Beltran
Date: Sat, 5 Sep 2026 23:26:23 -0500
Subject: [PATCH 2/4] fix: enable pagination in the shared overlay
---
client-src/overlay.js | 60 ++--
test/client/overlay.test.js | 43 +++
.../overlay.test.js.snap.webpack5 | 338 ++++++++++++++----
test/e2e/overlay.test.js | 12 +
4 files changed, 363 insertions(+), 90 deletions(-)
diff --git a/client-src/overlay.js b/client-src/overlay.js
index 710379014e..b7d74d6b92 100644
--- a/client-src/overlay.js
+++ b/client-src/overlay.js
@@ -282,7 +282,7 @@ const createOverlay = (options) => {
trustedTypesPolicyName:
options.trustedTypesPolicyName || "webpack-dev-server#overlay",
openEditorEndpoint: "/webpack-dev-server/open-editor",
- paginate: false,
+ paginate: true,
});
/** @type {(event: KeyboardEvent) => void} */
@@ -297,6 +297,35 @@ const createOverlay = (options) => {
sharedOverlay.clear("webpack-dev-server");
};
+ /** @type {Document | null | undefined} */
+ let overlayDocument;
+ /** @type {(string | Message)[]} */
+ let overlayMessages = [];
+
+ // Pagination renders new links. Resolve them after mouse or keyboard
+ // navigation as well as when compiler messages first arrive.
+ const updateEditorLinks = () => {
+ const links = overlayDocument?.querySelectorAll("[data-open-file]");
+ links?.forEach((link) => {
+ const file = link.getAttribute("data-open-file") || "";
+ overlayMessages.forEach((message) => {
+ if (typeof message === "string" || !message.moduleIdentifier) {
+ return;
+ }
+ const name = (message.moduleName || message.file || "").replace(
+ /^(\s|\S)*!/,
+ "",
+ );
+ if (name && file.indexOf(`${name}:`) === 0) {
+ link.setAttribute(
+ "data-open-file",
+ `${message.moduleIdentifier.replace(/^[^|]*\|/, "").replace(/^(\s|\S)*!/, "")}${file.slice(name.length)}`,
+ );
+ }
+ });
+ });
+ };
+
const overlayService = createOverlayMachine({
showOverlay: ({ level = "error", messages }) => {
sharedOverlay.showProblems(
@@ -309,31 +338,16 @@ const createOverlay = (options) => {
"webpack-dev-server",
);
- // The shared renderer links display paths. Resolve those links to the
- // compiler's module identifiers, which can be outside the server cwd.
+ overlayMessages = messages;
const frame = /** @type {HTMLIFrameElement | null} */ (
document.getElementById("webpack-dev-middleware-hot-overlay")
);
- const links =
- frame?.contentDocument?.querySelectorAll("[data-open-file]");
- links?.forEach((link) => {
- const file = link.getAttribute("data-open-file") || "";
- messages.forEach((message) => {
- if (typeof message === "string" || !message.moduleIdentifier) {
- return;
- }
- const name = (message.moduleName || message.file || "").replace(
- /^(\s|\S)*!/,
- "",
- );
- if (name && file.indexOf(`${name}:`) === 0) {
- link.setAttribute(
- "data-open-file",
- `${message.moduleIdentifier.replace(/^[^|]*\|/, "").replace(/^(\s|\S)*!/, "")}${file.slice(name.length)}`,
- );
- }
- });
- });
+ if (frame?.contentDocument !== overlayDocument) {
+ overlayDocument = frame?.contentDocument;
+ overlayDocument?.addEventListener("click", updateEditorLinks);
+ overlayDocument?.addEventListener("keydown", updateEditorLinks);
+ }
+ updateEditorLinks();
},
hideOverlay: hideOverlayWithEscCleanup,
});
diff --git a/test/client/overlay.test.js b/test/client/overlay.test.js
index b40e0c6994..1bef1a28d0 100644
--- a/test/client/overlay.test.js
+++ b/test/client/overlay.test.js
@@ -35,6 +35,49 @@ describe("shared overlay", () => {
expect(globalThis.document.querySelector(selector)).toBeNull();
});
+ it("should paginate problems with buttons and arrow keys and resolve each page's editor links", () => {
+ const overlay = createOverlay({ catchRuntimeError: false });
+ overlay.send({
+ type: "BUILD_ERROR",
+ level: "error",
+ messages: [
+ {
+ moduleName: "./first.js",
+ moduleIdentifier: "/project/first.js",
+ loc: "1:1",
+ message: "First problem",
+ },
+ {
+ moduleName: "./second.js",
+ moduleIdentifier: "/project/second.js",
+ loc: "2:3",
+ message: "Second problem",
+ },
+ ],
+ });
+ const frameDocument = document.querySelector(selector).contentDocument;
+ expect(frameDocument.body.textContent).toContain("1 / 2");
+ expect(frameDocument.body.textContent).toContain("First problem");
+ expect(frameDocument.body.textContent).not.toContain("Second problem");
+
+ frameDocument.querySelector('[aria-label="Next problem"]').click();
+ expect(frameDocument.body.textContent).toContain("2 / 2");
+ expect(frameDocument.body.textContent).toContain("Second problem");
+ expect(frameDocument.body.textContent).not.toContain("First problem");
+ expect(
+ frameDocument.querySelector("[data-open-file]").dataset.openFile,
+ ).toBe("/project/second.js:2:3");
+
+ frameDocument.dispatchEvent(
+ new KeyboardEvent("keydown", { key: "ArrowLeft" }),
+ );
+ expect(frameDocument.body.textContent).toContain("1 / 2");
+ expect(
+ frameDocument.querySelector("[data-open-file]").dataset.openFile,
+ ).toBe("/project/first.js:1:1");
+ overlay.send({ type: "DISMISS" });
+ });
+
it("should preserve problems reported by another client on a clean build", () => {
showProblems("errors", ["Other client error"], "other-client");
const overlay = createOverlay({ catchRuntimeError: false });
diff --git a/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 b/test/e2e/__snapshots__/overlay.test.js.snap.webpack5
index cbe57e8a71..db26b505b4 100644
--- a/test/e2e/__snapshots__/overlay.test.js.snap.webpack5
+++ b/test/e2e/__snapshots__/overlay.test.js.snap.webpack5
@@ -854,65 +854,221 @@ exports[`overlay > should show a warning and error for initial compilation 2`] =
>
×
-
+
ERROR ERROR
+ in Warning from compilation
- in Warning from compilation
-
-
- ERROR
- in Warning from compilation
+
+ ‹ 1 / 5
+ ›
+
+
-
-
ERROR
- in Error from compilation. Can't find 'test' module.
+
+ Use ‹ › or the arrow keys to navigate. Click outside, press Esc, or fix
+ the code to dismiss.
-
+
+
+"
+`;
+
+exports[`overlay > should show a warning and error for initial compilation 3`] = `
+"
+
+
+ ×
+
+
ERROR ERROR
+ in Warning from compilation
- in Error from compilation. Can't find 'test' module.
-
-
- ERROR
- in Error from compilation. Can't find 'test' module.
+
+ ‹ 2 / 5
+ ›
+
+
- Click outside, press Esc, or fix the code to dismiss.
+ Use ‹ › or the arrow keys to navigate. Click outside, press Esc, or fix
+ the code to dismiss.
@@ -1014,29 +1171,75 @@ exports[`overlay > should show a warning and error for initial compilation and p
>
×
-
+
ERROR ERROR
+ in <strong>strong</strong>
- in <strong>strong</strong>
-
-
- ERROR
- in <strong>strong</strong>
+
+ ‹ 1 / 2
+ ›
+
+
- Click outside, press Esc, or fix the code to dismiss.
+ Use ‹ › or the arrow keys to navigate. Click outside, press Esc, or fix
+ the code to dismiss.
diff --git a/test/e2e/overlay.test.js b/test/e2e/overlay.test.js
index b9d7ab8d94..250143a39e 100644
--- a/test/e2e/overlay.test.js
+++ b/test/e2e/overlay.test.js
@@ -230,6 +230,18 @@ describe("overlay", () => {
parser: "html",
}),
);
+ await overlayFrame.click('[aria-label="Next problem"]');
+ const nextPageHtml = (
+ await overlayFrame.evaluate(() => document.body.outerHTML)
+ )
+ .replaceAll(config.context.replaceAll("\\", "/"), "")
+ .replaceAll(config.context, "");
+ t.assert.snapshot(await format(nextPageHtml, { parser: "html" }));
+
+ await overlayFrame.click('[aria-label="Previous problem"]');
+ expect(await overlayFrame.evaluate(() => document.body.outerHTML)).toBe(
+ overlayHtml,
+ );
} finally {
await browser.close();
await server.stop();
From d4fc4fd6b1a8aac96f09963ae5b02cc56792af8d Mon Sep 17 00:00:00 2001
From: Sebastian Beltran
Date: Sat, 5 Sep 2026 23:41:04 -0500
Subject: [PATCH 3/4] refactor: delegate overlay state and runtime errors to
dev-middleware
---
client-src/globals.d.ts | 1 +
client-src/overlay.js | 365 +++---------------
test/client/ReactErrorBoundary.test.js | 191 ++++-----
test/client/overlay.test.js | 28 ++
.../overlay.test.js.snap.webpack5 | 90 +----
test/e2e/overlay.test.js | 6 +-
6 files changed, 179 insertions(+), 502 deletions(-)
diff --git a/client-src/globals.d.ts b/client-src/globals.d.ts
index 4a454b86c2..ed72e003ba 100644
--- a/client-src/globals.d.ts
+++ b/client-src/globals.d.ts
@@ -18,6 +18,7 @@ declare module "webpack-dev-middleware/client/overlay" {
trustedTypesPolicyName?: string;
openEditorEndpoint?: string;
paginate?: boolean;
+ catchRuntimeError?: boolean | ((error: Error) => boolean);
}): {
showProblems(
type: "errors" | "warnings",
diff --git a/client-src/overlay.js b/client-src/overlay.js
index b7d74d6b92..4c4d02b1aa 100644
--- a/client-src/overlay.js
+++ b/client-src/overlay.js
@@ -1,229 +1,5 @@
import configureOverlay from "webpack-dev-middleware/client/overlay";
-/** @typedef {import("./index.js").EXPECTED_ANY} EXPECTED_ANY */
-
-/**
- * @typedef {object} Context
- * @property {"warning" | "error"} level level
- * @property {(string | Message)[]} messages messages
- * @property {"build" | "runtime"} messageSource message source
- */
-
-/** @typedef {{ type: string } & Record} Event */
-
-/**
- * @typedef {object} Options
- * @property {{ [state: string]: { on: Record } }} states states
- * @property {Context} context context
- * @property {string} initial initial
- */
-
-/**
- * @typedef {object} Implementation
- * @property {{ [actionName: string]: (ctx: Context, event: Event) => Context | void }} actions actions
- */
-
-/**
- * @typedef {{ send: (event: Event) => void }} StateMachine
- */
-
-/**
- * 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 options
- * @param {Implementation} implementation implementation
- * @returns {StateMachine} state machine
- */
-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,
- };
- }
- });
- }
- }
- },
- };
-}
-
-/**
- * @typedef {object} ShowOverlayData
- * @property {"warning" | "error"} level level
- * @property {(string | Message)[]} messages messages
- * @property {"build" | "runtime"} messageSource message source
- */
-
-/**
- * @typedef {object} CreateOverlayMachineOptions
- * @property {(data: ShowOverlayData) => void} showOverlay show overlay
- * @property {() => void} hideOverlay hide overlay
- */
-
-/**
- * @param {CreateOverlayMachineOptions} options options
- * @returns {StateMachine} state machine
- */
-const createOverlayMachine = (options) => {
- const { hideOverlay, showOverlay } = options;
-
- return createMachine(
- {
- initial: "hidden",
- context: {
- level: "error",
- messages: [],
- messageSource: "build",
- },
- 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",
- messageSource: "build",
- };
- },
- appendMessages: (context, event) => {
- return {
- messages: context.messages.concat(event.messages),
- level: event.level || context.level,
- messageSource: event.type === "RUNTIME_ERROR" ? "runtime" : "build",
- };
- },
- setMessages: (context, event) => {
- return {
- messages: event.messages,
- level: event.level || context.level,
- messageSource: event.type === "RUNTIME_ERROR" ? "runtime" : "build",
- };
- },
- hideOverlay,
- showOverlay,
- },
- },
- );
-};
-
-/**
- * @param {Error} error error
- * @returns {undefined | string[]} stack
- */
-const 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 callback
- * @returns {() => void} cleanup
- */
-const listenToRuntimeError = (callback) => {
- window.addEventListener("error", callback);
-
- return function cleanup() {
- window.removeEventListener("error", callback);
- };
-};
-
-/**
- * @callback UnhandledRejectionCallback
- * @param {PromiseRejectionEvent} rejectionEvent
- * @returns {void}
- */
-
-/**
- * @param {UnhandledRejectionCallback} callback callback
- * @returns {() => void} cleanup
- */
-const listenToUnhandledRejection = (callback) => {
- window.addEventListener("unhandledrejection", callback);
-
- return function cleanup() {
- window.removeEventListener("unhandledrejection", callback);
- };
-};
-
/** @typedef {Error & { file?: string, moduleName?: string, moduleIdentifier?: string, loc?: string, message?: string, stack?: string | string[] }} Message */
/**
@@ -267,36 +43,64 @@ const formatProblem = (type, item) => {
return { header, body };
};
+/** @typedef {{ type: "DISMISS" } | { type: "BUILD_ERROR", level: "warning" | "error", messages: (string | Message)[] }} OverlayEvent */
+
+// The middleware wraps non-Error throws without `cause`. Preserve the original
+// value for dev-server's runtimeErrors filters, including rejected objects.
+/** @type {unknown} */
+let runtimeErrorCause;
+let captureRuntimeErrorCause = false;
+
/**
* @typedef {object} CreateOverlayOptions
* @property {(false | string)=} trustedTypesPolicyName trusted types policy name
- * @property {(boolean | ((error: Error) => void))=} catchRuntimeError runtime error catcher
+ * @property {(boolean | ((error: Error) => boolean))=} catchRuntimeError runtime error catcher
*/
/**
* @param {CreateOverlayOptions} options options
- * @returns {StateMachine} overlay
+ * @returns {{ send: (event: OverlayEvent) => void }} overlay
*/
const createOverlay = (options) => {
+ if (
+ !captureRuntimeErrorCause &&
+ typeof options.catchRuntimeError === "function"
+ ) {
+ window.addEventListener(
+ "error",
+ (event) => {
+ runtimeErrorCause = event.error;
+ },
+ true,
+ );
+ window.addEventListener(
+ "unhandledrejection",
+ (event) => {
+ runtimeErrorCause = event.reason;
+ },
+ true,
+ );
+ captureRuntimeErrorCause = true;
+ }
+
const sharedOverlay = configureOverlay({
trustedTypesPolicyName:
options.trustedTypesPolicyName || "webpack-dev-server#overlay",
openEditorEndpoint: "/webpack-dev-server/open-editor",
paginate: true,
+ catchRuntimeError: (error) => {
+ const cause = runtimeErrorCause;
+ runtimeErrorCause = undefined;
+ return typeof options.catchRuntimeError === "function"
+ ? options.catchRuntimeError(
+ cause instanceof Error
+ ? error
+ : new Error(error.message, { cause }),
+ )
+ : Boolean(options.catchRuntimeError);
+ },
});
- /** @type {(event: KeyboardEvent) => void} */
- let handleEscapeKey;
-
- /**
- * @returns {void}
- */
-
- const hideOverlayWithEscCleanup = () => {
- window.removeEventListener("keydown", handleEscapeKey);
- sharedOverlay.clear("webpack-dev-server");
- };
-
/** @type {Document | null | undefined} */
let overlayDocument;
/** @type {(string | Message)[]} */
@@ -326,8 +130,17 @@ const createOverlay = (options) => {
});
};
- const overlayService = createOverlayMachine({
- showOverlay: ({ level = "error", messages }) => {
+ return {
+ send(event) {
+ if (event.type === "DISMISS") {
+ sharedOverlay.clear("webpack-dev-server:warning");
+ sharedOverlay.clear("webpack-dev-server:error");
+ sharedOverlay.clear("runtime");
+ overlayMessages = [];
+ return;
+ }
+
+ const { level, messages } = event;
sharedOverlay.showProblems(
level === "warning" ? "warnings" : "errors",
messages.map((message) => {
@@ -335,7 +148,7 @@ const createOverlay = (options) => {
const location = header.replace(/^(ERROR|WARNING)( in )?/, "");
return location ? `${location}\n${body}` : body;
}),
- "webpack-dev-server",
+ `webpack-dev-server:${level}`,
);
overlayMessages = messages;
@@ -349,77 +162,7 @@ const createOverlay = (options) => {
}
updateEditorLinks();
},
- hideOverlay: hideOverlayWithEscCleanup,
- });
- /**
- * ESC key press to dismiss the overlay.
- * @param {KeyboardEvent} event Keydown event
- */
- handleEscapeKey = (event) => {
- if (event.key === "Escape" || event.key === "Esc" || event.keyCode === 27) {
- overlayService.send({ type: "DISMISS" });
- }
};
-
- window.addEventListener("keydown", handleEscapeKey);
-
- if (options.catchRuntimeError) {
- /**
- * @param {Error | undefined} error error
- * @param {string} fallbackMessage fallback message
- */
- const handleError = (error, fallbackMessage) => {
- const errorObject =
- error instanceof Error
- ? error
- : new Error(error || fallbackMessage, { cause: error });
-
- const shouldDisplay =
- typeof options.catchRuntimeError === "function"
- ? options.catchRuntimeError(errorObject)
- : true;
-
- if (shouldDisplay) {
- overlayService.send({
- type: "RUNTIME_ERROR",
- messages: [
- {
- message: errorObject.message,
- stack: parseErrorToStacks(errorObject),
- },
- ],
- });
- }
- };
-
- listenToRuntimeError((errorEvent) => {
- // error property may be empty in older browser like IE
- const { error, message } = errorEvent;
-
- if (!error && !message) {
- return;
- }
-
- // if error stack indicates a React error boundary caught the error, do not show overlay.
- if (
- error &&
- error.stack &&
- error.stack.includes("invokeGuardedCallbackDev")
- ) {
- return;
- }
-
- handleError(error, message);
- });
-
- listenToUnhandledRejection((promiseRejectionEvent) => {
- const { reason } = promiseRejectionEvent;
-
- handleError(reason, "Unknown promise rejection reason");
- });
- }
-
- return overlayService;
};
export { createOverlay, formatProblem };
diff --git a/test/client/ReactErrorBoundary.test.js b/test/client/ReactErrorBoundary.test.js
index bcbd8552bf..c91a49db5b 100644
--- a/test/client/ReactErrorBoundary.test.js
+++ b/test/client/ReactErrorBoundary.test.js
@@ -1,24 +1,17 @@
import "../helpers/jsdom-setup.js";
-import { afterEach, beforeEach, describe, it, mock } from "node:test";
+import { afterEach, describe, it } from "node:test";
import { expect } from "expect";
-import { spyOn } from "jest-mock";
+import { clear } from "webpack-dev-middleware/client/overlay";
import { createOverlay } from "../../client-src/overlay.js";
describe("createOverlay", () => {
- beforeEach(() => {
- mock.timers.enable();
- });
-
- afterEach(() => {
- mock.timers.reset();
- mock.reset();
- });
+ const selector = "#webpack-dev-middleware-hot-overlay";
+ afterEach(() => clear());
it("should not show overlay for errors caught by React error boundaries", () => {
const options = { trustedTypesPolicyName: null, catchRuntimeError: true };
- const overlay = createOverlay(options);
- const showOverlayMock = spyOn(overlay, "send");
+ createOverlay(options);
const reactError = new Error(
"Error inside React render\n" +
@@ -41,14 +34,12 @@ describe("createOverlay", () => {
});
globalThis.dispatchEvent(errorEvent);
- expect(showOverlayMock).not.toHaveBeenCalled();
- showOverlayMock.mockRestore();
+ expect(document.querySelector(selector)).toBeNull();
});
it("should show overlay for normal uncaught errors", () => {
const options = { trustedTypesPolicyName: null, catchRuntimeError: true };
- const overlay = createOverlay(options);
- const showOverlayMock = spyOn(overlay, "send");
+ createOverlay(options);
const regularError = new Error(
"Error inside React render\n" +
@@ -65,22 +56,14 @@ describe("createOverlay", () => {
});
globalThis.dispatchEvent(errorEvent);
- expect(showOverlayMock).toHaveBeenCalledWith({
- type: "RUNTIME_ERROR",
- messages: [
- {
- message: regularError.message,
- stack: expect.anything(),
- },
- ],
- });
- showOverlayMock.mockRestore();
+ expect(
+ document.querySelector(selector).contentDocument.body.textContent,
+ ).toContain(regularError.message);
});
it("should show overlay for normal uncaught errors (when null is thrown)", () => {
const options = { trustedTypesPolicyName: null, catchRuntimeError: true };
- const overlay = createOverlay(options);
- const showOverlayMock = spyOn(overlay, "send");
+ createOverlay(options);
const errorEvent = new ErrorEvent("error", {
error: null,
@@ -88,16 +71,9 @@ describe("createOverlay", () => {
});
globalThis.dispatchEvent(errorEvent);
- expect(showOverlayMock).toHaveBeenCalledWith({
- type: "RUNTIME_ERROR",
- messages: [
- {
- message: "error",
- stack: expect.anything(),
- },
- ],
- });
- showOverlayMock.mockRestore();
+ expect(
+ document.querySelector(selector).contentDocument.body.textContent,
+ ).toContain("error");
});
it("should show overlay for normal uncaught errors when catchRuntimeError is a function that return true", () => {
@@ -105,8 +81,7 @@ describe("createOverlay", () => {
trustedTypesPolicyName: null,
catchRuntimeError: () => true,
};
- const overlay = createOverlay(options);
- const showOverlayMock = spyOn(overlay, "send");
+ createOverlay(options);
const regularError = new Error("Regular test error");
const errorEvent = new ErrorEvent("error", {
@@ -115,16 +90,9 @@ describe("createOverlay", () => {
});
globalThis.dispatchEvent(errorEvent);
- expect(showOverlayMock).toHaveBeenCalledWith({
- type: "RUNTIME_ERROR",
- messages: [
- {
- message: regularError.message,
- stack: expect.anything(),
- },
- ],
- });
- showOverlayMock.mockRestore();
+ expect(
+ document.querySelector(selector).contentDocument.body.textContent,
+ ).toContain(regularError.message);
});
it("should not show overlay for normal uncaught errors when catchRuntimeError is a function that return false", () => {
@@ -132,8 +100,7 @@ describe("createOverlay", () => {
trustedTypesPolicyName: null,
catchRuntimeError: () => false,
};
- const overlay = createOverlay(options);
- const showOverlayMock = spyOn(overlay, "send");
+ createOverlay(options);
const regularError = new Error("Regular test error");
const errorEvent = new ErrorEvent("error", {
@@ -142,14 +109,12 @@ describe("createOverlay", () => {
});
globalThis.dispatchEvent(errorEvent);
- expect(showOverlayMock).not.toHaveBeenCalled();
- showOverlayMock.mockRestore();
+ expect(document.querySelector(selector)).toBeNull();
});
it("should not show the overlay for errors with stack containing 'invokeGuardedCallbackDev'", () => {
const options = { trustedTypesPolicyName: null, catchRuntimeError: true };
- const overlay = createOverlay(options);
- const showOverlayMock = spyOn(overlay, "send");
+ createOverlay(options);
const reactInternalError = new Error("React internal error");
reactInternalError.stack = "invokeGuardedCallbackDev\n at somefile.js";
@@ -159,14 +124,12 @@ describe("createOverlay", () => {
});
globalThis.dispatchEvent(errorEvent);
- expect(showOverlayMock).not.toHaveBeenCalled();
- showOverlayMock.mockRestore();
+ expect(document.querySelector(selector)).toBeNull();
});
it("should show overlay for unhandled rejections", () => {
const options = { trustedTypesPolicyName: null, catchRuntimeError: true };
- const overlay = createOverlay(options);
- const showOverlayMock = spyOn(overlay, "send");
+ createOverlay(options);
const rejectionReason = new Error("Promise rejection reason");
const rejectionEvent = new Event("unhandledrejection");
@@ -174,72 +137,72 @@ describe("createOverlay", () => {
globalThis.dispatchEvent(rejectionEvent);
- expect(showOverlayMock).toHaveBeenCalledWith({
- type: "RUNTIME_ERROR",
- messages: [
- {
- message: rejectionReason.message,
- stack: expect.anything(),
- },
- ],
- });
- showOverlayMock.mockRestore();
+ expect(
+ document.querySelector(selector).contentDocument.body.textContent,
+ ).toContain(rejectionReason.message);
});
it("should show overlay for unhandled rejections with string reason", () => {
const options = { trustedTypesPolicyName: null, catchRuntimeError: true };
- const overlay = createOverlay(options);
- const showOverlayMock = spyOn(overlay, "send");
+ createOverlay(options);
const rejectionEvent = new Event("unhandledrejection");
rejectionEvent.reason = "some reason";
globalThis.dispatchEvent(rejectionEvent);
- expect(showOverlayMock).toHaveBeenCalledWith({
- type: "RUNTIME_ERROR",
- messages: [
- {
- message: "some reason",
- stack: expect.anything(),
- },
- ],
- });
- showOverlayMock.mockRestore();
+ expect(
+ document.querySelector(selector).contentDocument.body.textContent,
+ ).toContain("some reason");
});
- // ESC key test cases
-
- it("should dismiss overlay when ESC key is pressed", () => {
- const options = { trustedTypesPolicyName: null, catchRuntimeError: true };
- const overlay = createOverlay(options);
- const showOverlayMock = spyOn(overlay, "send");
-
- const escEvent = new KeyboardEvent("keydown", { key: "Escape" });
- globalThis.window.dispatchEvent(escEvent);
-
- expect(showOverlayMock).toHaveBeenCalledWith({ type: "DISMISS" });
- showOverlayMock.mockRestore();
+ for (const target of ["page", "frame"]) {
+ it(`should dismiss the overlay with Escape from the ${target}`, () => {
+ const overlay = createOverlay({ catchRuntimeError: true });
+ overlay.send({
+ type: "BUILD_ERROR",
+ level: "error",
+ messages: ["Build failed"],
+ });
+ const frame = document.querySelector(selector);
+ const targetDocument =
+ target === "page" ? document : frame.contentDocument;
+ targetDocument.dispatchEvent(
+ new KeyboardEvent("keydown", { key: "Escape" }),
+ );
+ expect(document.querySelector(selector)).toBeNull();
+ });
+ }
+
+ it("should preserve the cause of a rejected value for runtime error filters", () => {
+ const reason = { error: new Error("Rejected object") };
+ let received;
+ createOverlay({
+ catchRuntimeError: (error) => {
+ received = error;
+ return false;
+ },
+ });
+ const event = new Event("unhandledrejection");
+ event.reason = reason;
+ globalThis.dispatchEvent(event);
+ expect(received.cause).toBe(reason);
+ expect(document.querySelector(selector)).toBeNull();
});
- it("should dismiss overlay when 'Esc' key is pressed (older browsers)", () => {
- const options = { trustedTypesPolicyName: null, catchRuntimeError: true };
- const overlay = createOverlay(options);
- const showOverlayMock = spyOn(overlay, "send");
-
- const escEvent = new KeyboardEvent("keydown", { key: "Esc" });
- globalThis.window.dispatchEvent(escEvent);
-
- expect(showOverlayMock).toHaveBeenCalledWith({ type: "DISMISS" });
- showOverlayMock.mockRestore();
+ it("should stop displaying runtime errors when capture is disabled", () => {
+ createOverlay({ catchRuntimeError: true });
+ createOverlay({ catchRuntimeError: false });
+ globalThis.dispatchEvent(
+ new ErrorEvent("error", { error: new Error("Ignored") }),
+ );
+ expect(document.querySelector(selector)).toBeNull();
});
- it("should not dismiss overlay for other keys", () => {
- const options = { trustedTypesPolicyName: null, catchRuntimeError: true };
- const overlay = createOverlay(options);
- const showOverlayMock = spyOn(overlay, "send");
-
- const otherKeyEvent = new KeyboardEvent("keydown", { key: "Enter" });
- globalThis.window.dispatchEvent(otherKeyEvent);
-
- expect(showOverlayMock).not.toHaveBeenCalled();
- showOverlayMock.mockRestore();
+ it("should clear runtime problems on the next build", () => {
+ const overlay = createOverlay({ catchRuntimeError: true });
+ globalThis.dispatchEvent(
+ new ErrorEvent("error", { error: new Error("Runtime failure") }),
+ );
+ expect(document.querySelector(selector)).not.toBeNull();
+ overlay.send({ type: "DISMISS" });
+ expect(document.querySelector(selector)).toBeNull();
});
});
diff --git a/test/client/overlay.test.js b/test/client/overlay.test.js
index 1bef1a28d0..16471a0628 100644
--- a/test/client/overlay.test.js
+++ b/test/client/overlay.test.js
@@ -78,6 +78,34 @@ describe("shared overlay", () => {
overlay.send({ type: "DISMISS" });
});
+ it("should replace build problems without retaining dismissed messages", () => {
+ const overlay = createOverlay({ catchRuntimeError: false });
+ overlay.send({
+ type: "BUILD_ERROR",
+ level: "error",
+ messages: ["First failure"],
+ });
+ overlay.send({
+ type: "BUILD_ERROR",
+ level: "error",
+ messages: ["Latest failure"],
+ });
+ const frameDocument = document.querySelector(selector).contentDocument;
+ expect(frameDocument.body.textContent).toContain("Latest failure");
+ expect(frameDocument.body.textContent).not.toContain("First failure");
+
+ frameDocument.querySelector('[aria-label="Close"]').click();
+ overlay.send({
+ type: "BUILD_ERROR",
+ level: "error",
+ messages: ["New failure"],
+ });
+ expect(
+ document.querySelector(selector).contentDocument.body.textContent,
+ ).not.toContain("Latest failure");
+ overlay.send({ type: "DISMISS" });
+ });
+
it("should preserve problems reported by another client on a clean build", () => {
showProblems("errors", ["Other client error"], "other-client");
const overlay = createOverlay({ catchRuntimeError: false });
diff --git a/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 b/test/e2e/__snapshots__/overlay.test.js.snap.webpack5
index db26b505b4..182e34a545 100644
--- a/test/e2e/__snapshots__/overlay.test.js.snap.webpack5
+++ b/test/e2e/__snapshots__/overlay.test.js.snap.webpack5
@@ -875,7 +875,7 @@ exports[`overlay > should show a warning and error for initial compilation 2`] =
"
>ERROR
- in Warning from compilation
‹
1 / 5 1 / 3 should show a warning and error for initial compilation 3`] =
"
>ERROR
- in Warning from compilation
‹
2 / 5 2 / 3 should show a warning and error for initial compilation and p
>
×
-
+
ERROR
- in <strong>strong</strong>
-
ERROR
-
- ‹ 1 / 2
- ›
-
-
+ in <strong>strong</strong>
- 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.