diff --git a/client-src/index.js b/client-src/index.js index 3a948884b5..7cd441bf4b 100644 --- a/client-src/index.js +++ b/client-src/index.js @@ -35,6 +35,30 @@ import createSocketURL from "./utils/createSocketURL.js"; * @property {string} [previousHash] */ +/** + * @param {boolean | { warnings?: boolean | string; errors?: boolean | string; runtimeErrors?: boolean | string; }} overlayOptions + */ +const decodeOverlayOptions = (overlayOptions) => { + if (typeof overlayOptions === "object") { + ["warnings", "errors", "runtimeErrors"].forEach((property) => { + if (typeof overlayOptions[property] === "string") { + const overlayFilterFunctionString = decodeURIComponent( + overlayOptions[property] + ); + + // eslint-disable-next-line no-new-func + const overlayFilterFunction = new Function( + "message", + `var callback = ${overlayFilterFunctionString} + return callback(message)` + ); + + overlayOptions[property] = overlayFilterFunction; + } + }); + } +}; + /** * @type {Status} */ @@ -92,22 +116,7 @@ if (parsedResourceQuery.overlay) { ...options.overlay, }; - ["errors", "warnings", "runtimeErrors"].forEach((property) => { - if (typeof options.overlay[property] === "string") { - const overlayFilterFunctionString = decodeURIComponent( - options.overlay[property] - ); - - // eslint-disable-next-line no-new-func - const overlayFilterFunction = new Function( - "message", - `var callback = ${overlayFilterFunctionString} - return callback(message)` - ); - - options.overlay[property] = overlayFilterFunction; - } - }); + decodeOverlayOptions(options.overlay); } enabledFeatures.Overlay = true; } @@ -198,6 +207,7 @@ const onSocketMessage = { } options.overlay = value; + decodeOverlayOptions(options.overlay); }, /** * @param {number} value diff --git a/lib/Server.js b/lib/Server.js index 03deff124e..2d67d2b72a 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -240,6 +240,16 @@ const memoize = (fn) => { const getExpress = memoize(() => require("express")); +/** + * + * @param {OverlayMessageOptions} [setting] + * @returns + */ +const encodeOverlaySettings = (setting) => + typeof setting === "function" + ? encodeURIComponent(setting.toString()) + : setting; + class Server { /** * @param {Configuration | Compiler | MultiCompiler} options @@ -658,16 +668,6 @@ class Server { } if (typeof client.overlay !== "undefined") { - /** - * - * @param {OverlayMessageOptions} [setting] - * @returns - */ - const encodeOverlaySettings = (setting) => - typeof setting === "function" - ? encodeURIComponent(setting.toString()) - : setting; - const overlayString = typeof client.overlay === "boolean" ? String(client.overlay) @@ -2647,11 +2647,26 @@ class Server { /** @type {ClientConfiguration} */ (this.options.client).overlay ) { + const overlayConfig = /** @type {ClientConfiguration} */ ( + this.options.client + ).overlay; + this.sendMessage( [client], "overlay", - /** @type {ClientConfiguration} */ - (this.options.client).overlay + typeof overlayConfig === "object" + ? { + errors: + overlayConfig.errors && + encodeOverlaySettings(overlayConfig.errors), + warnings: + overlayConfig.warnings && + encodeOverlaySettings(overlayConfig.warnings), + runtimeErrors: + overlayConfig.runtimeErrors && + encodeOverlaySettings(overlayConfig.runtimeErrors), + } + : overlayConfig ); } diff --git a/test/e2e/__snapshots__/overlay.test.js.snap.webpack4 b/test/e2e/__snapshots__/overlay.test.js.snap.webpack4 index eca0d83aba..44a1317cf9 100644 --- a/test/e2e/__snapshots__/overlay.test.js.snap.webpack4 +++ b/test/e2e/__snapshots__/overlay.test.js.snap.webpack4 @@ -2221,3 +2221,213 @@ exports[`overlay should show error for uncaught runtime error: overlay html 1`] " `; + +exports[`overlay should show error when it is not filtered: overlay html 1`] = ` +" +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR +
+
+ Unfiltered error +
+
+
+
+ +" +`; + +exports[`overlay should show error when it is not filtered: page html 1`] = ` +" +

webpack-dev-server is running...

+ + + + +" +`; + +exports[`overlay should show warning when it is not filtered: overlay html 1`] = ` +" +
+
+ Compiled with problems: +
+ +
+
+
+ WARNING +
+
+ Unfiltered warning +
+
+
+
+ +" +`; + +exports[`overlay should show warning when it is not filtered: page html 1`] = ` +" +

webpack-dev-server is running...

+ + + + +" +`; diff --git a/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 b/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 index 39555dfd65..49990c3d43 100644 --- a/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 +++ b/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 @@ -2238,6 +2238,111 @@ exports[`overlay should show error for uncaught runtime error: overlay html 1`] " `; +exports[`overlay should show error when it is not filtered: overlay html 1`] = ` +" +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR +
+
+ Unfiltered error +
+
+
+
+ +" +`; + +exports[`overlay should show error when it is not filtered: page html 1`] = ` +" +

webpack-dev-server is running...

+ + + + +" +`; + exports[`overlay should show overlay when Trusted Types are enabled: overlay html 1`] = ` "
" `; + +exports[`overlay should show warning when it is not filtered: overlay html 1`] = ` +" +
+
+ Compiled with problems: +
+ +
+
+
+ WARNING +
+
+ Unfiltered warning +
+
+
+
+ +" +`; + +exports[`overlay should show warning when it is not filtered: page html 1`] = ` +" +

webpack-dev-server is running...

+ + + + +" +`; diff --git a/test/e2e/overlay.test.js b/test/e2e/overlay.test.js index 11f9d3bd4a..6ea8cb60c5 100644 --- a/test/e2e/overlay.test.js +++ b/test/e2e/overlay.test.js @@ -596,6 +596,90 @@ describe("overlay", () => { await server.stop(); }); + it("should not show warning when it is filtered", async () => { + const compiler = webpack(config); + + new WarningPlugin("My special warning").apply(compiler); + + const server = new Server( + { + port, + client: { + overlay: { + warnings: (error) => { + // error is string in webpack 4 + const message = typeof error === "string" ? error : error.message; + return message !== "My special warning"; + }, + }, + }, + }, + compiler + ); + + await server.start(); + + const { page, browser } = await runBrowser(); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0", + }); + + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + + await browser.close(); + await server.stop(); + }); + + it("should show warning when it is not filtered", async () => { + const compiler = webpack(config); + + new WarningPlugin("Unfiltered warning").apply(compiler); + + const server = new Server( + { + port, + client: { + overlay: { + warnings: () => true, + }, + }, + }, + compiler + ); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0", + }); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect(prettier.format(pageHtml, { parser: "html" })).toMatchSnapshot( + "page html" + ); + expect(prettier.format(overlayHtml, { parser: "html" })).toMatchSnapshot( + "overlay html" + ); + } catch (error) { + console.error(error); + } + + await browser.close(); + await server.stop(); + }); + it('should show a warning when "client.overlay" is "true"', async () => { const compiler = webpack(config); @@ -785,6 +869,95 @@ describe("overlay", () => { await server.stop(); }); + it("should not show error when it is filtered", async () => { + const compiler = webpack(config); + + new ErrorPlugin("My special error").apply(compiler); + + const server = new Server( + { + port, + client: { + overlay: { + errors: (error) => { + // error is string in webpack 4 + const message = typeof error === "string" ? error : error.message; + + return message !== "My special error"; + }, + }, + }, + }, + compiler + ); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0", + }); + + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + } catch (error) { + console.error(error); + } + + await browser.close(); + await server.stop(); + }); + + it("should show error when it is not filtered", async () => { + const compiler = webpack(config); + + new ErrorPlugin("Unfiltered error").apply(compiler); + + const server = new Server( + { + port, + client: { + overlay: { + errors: () => true, + }, + }, + }, + compiler + ); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0", + }); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect(prettier.format(pageHtml, { parser: "html" })).toMatchSnapshot( + "page html" + ); + expect(prettier.format(overlayHtml, { parser: "html" })).toMatchSnapshot( + "overlay html" + ); + } catch (error) { + console.error(error); + } + + await browser.close(); + await server.stop(); + }); + it('should show an error when "client.overlay" is "true"', async () => { const compiler = webpack(config); @@ -1184,7 +1357,7 @@ describe("overlay", () => { await server.stop(); }); - it("show not show filtered runtime error", async () => { + it("should not show filtered runtime error", async () => { const compiler = webpack(config); const server = new Server( diff --git a/types/lib/Server.d.ts b/types/lib/Server.d.ts index 7361906c51..ab8c6e3d08 100644 --- a/types/lib/Server.d.ts +++ b/types/lib/Server.d.ts @@ -665,9 +665,16 @@ declare class Server { } | { description: string; + /** + * @private + * @type {RequestHandler[]} + */ multiple: boolean; path: string; type: string; + /** + * @type {Socket[]} + */ } )[]; description: string; @@ -774,6 +781,9 @@ declare class Server { simpleType: string; multiple: boolean; }; + /** + * @type {string | undefined} + */ "https-ca": { configs: { type: string; @@ -808,10 +818,6 @@ declare class Server { multiple: boolean; }; "https-cacert-reset": { - /** - * @private - * @param {Compiler} compiler - */ configs: { description: string; multiple: boolean; @@ -826,7 +832,7 @@ declare class Server { configs: { type: string; multiple: boolean; - description: string; + /** @type {ClientConfiguration} */ description: string; path: string; }[]; description: string; @@ -886,7 +892,7 @@ declare class Server { }[]; description: string; multiple: boolean; - simpleType: string; + /** @type {string} */ simpleType: string; }; "https-passphrase": { configs: { @@ -897,7 +903,7 @@ declare class Server { }[]; description: string; simpleType: string; - /** @type {ClientConfiguration} */ multiple: boolean; + multiple: boolean; }; "https-pfx": { configs: { @@ -983,12 +989,12 @@ declare class Server { type: string; multiple: boolean; description: string; + /** @type {any} */ path: string; } | { type: string; multiple: boolean; - /** @type {any} */ description: string; negatedDescription: string; path: string; @@ -1018,8 +1024,9 @@ declare class Server { }[]; description: string; simpleType: string; - multiple: boolean; + multiple: boolean /** @type {MultiCompiler} */; }; + /** @type {MultiCompiler} */ "open-app-name-reset": { configs: { type: string; @@ -1091,6 +1098,10 @@ declare class Server { path: string; type: string; }[]; + /** + * @param {string | Static | undefined} [optionsForStatic] + * @returns {NormalizedStatic} + */ description: string; multiple: boolean; simpleType: string; @@ -1261,7 +1272,7 @@ declare class Server { } | { type: string; - /** @type {ServerConfiguration} */ multiple: boolean; + multiple: boolean; description: string; negatedDescription: string; path: string; @@ -1278,15 +1289,15 @@ declare class Server { description: string; path: string; }[]; - description: string; + /** @type {ServerOptions} */ description: string; simpleType: string; - multiple: boolean /** @type {ServerOptions} */; + multiple: boolean; }; "static-public-path": { configs: { type: string; multiple: boolean; - /** @type {ServerOptions} */ description: string; + description: string; path: string; }[]; description: string; @@ -2126,10 +2137,6 @@ declare class Server { } | { type: string; - /** - * @private - * @type {{ name: string | symbol, listener: (...args: any[]) => void}[] }} - */ additionalProperties: boolean; properties: { passphrase: { @@ -2143,6 +2150,10 @@ declare class Server { negatedDescription: string; }; }; + /** + * @private + * @type {string | undefined} + */ ca: { anyOf: ( | { @@ -2406,6 +2417,7 @@ declare class Server { | { type: string; description: string; + /** @type {{ type: WebSocketServerConfiguration["type"], options: NonNullable }} */ link: string; cli?: undefined; } @@ -2440,7 +2452,7 @@ declare class Server { } | { enum: string[]; - type?: undefined; + /** @type {string} */ type?: undefined; cli?: undefined; } )[]; @@ -2469,7 +2481,7 @@ declare class Server { cli: { negatedDescription: string; }; - link: string /** @type {number | string} */; + link: string; }; MagicHTML: { type: string; @@ -2606,12 +2618,6 @@ declare class Server { | { type: string; minLength: number; - /** - * prependEntry Method for webpack 4 - * @param {any} originalEntry - * @param {any} newAdditionalEntries - * @returns {any} - */ minimum?: undefined; maximum?: undefined; enum?: undefined; @@ -2688,7 +2694,7 @@ declare class Server { $ref: string; }; }; - /** @type {MultiCompiler} */ additionalProperties: boolean; + additionalProperties: boolean; }; ServerOptions: { type: string; @@ -2696,7 +2702,7 @@ declare class Server { properties: { passphrase: { type: string; - /** @type {MultiCompiler} */ description: string; + description: string; }; requestCert: { type: string; @@ -2744,10 +2750,6 @@ declare class Server { anyOf: ( | { type: string; - /** - * @private - * @returns {Promise} - */ instanceof?: undefined; } | { @@ -2769,10 +2771,6 @@ declare class Server { items?: undefined; } )[]; - /** - * @param {WatchOptions & { aggregateTimeout?: number, ignored?: WatchOptions["ignored"], poll?: number | boolean }} watchOptions - * @returns {WatchOptions} - */ description: string; }; cert: { @@ -3126,6 +3124,7 @@ declare class Server { }; WebSocketServerObject: { type: string; + /** @type {ServerOptions} */ properties: { type: { anyOf: { @@ -3135,12 +3134,13 @@ declare class Server { options: { type: string; additionalProperties: boolean; + /** @type {ServerOptions} */ cli: { exclude: boolean; }; }; }; - /** @type {ServerOptions} */ additionalProperties: boolean; + additionalProperties: boolean; }; WebSocketServerString: { type: string; @@ -3195,8 +3195,9 @@ declare class Server { $ref: string; }; onBeforeSetupMiddleware: { - $ref: string; + $ref: string /** @type {any} */; }; + /** @type {any} */ onListening: { $ref: string; };