Skip to content

Commit aab01b3

Browse files
authored
feat: allow filter overlay errors/warnings with function (#4813)
1 parent ee748a7 commit aab01b3

14 files changed

+1322
-189
lines changed

client-src/index.js

+64-15
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ import sendMessage from "./utils/sendMessage.js";
1010
import reloadApp from "./utils/reloadApp.js";
1111
import createSocketURL from "./utils/createSocketURL.js";
1212

13+
/**
14+
* @typedef {Object} OverlayOptions
15+
* @property {boolean | (error: Error) => boolean} [warnings]
16+
* @property {boolean | (error: Error) => boolean} [errors]
17+
* @property {boolean | (error: Error) => boolean} [runtimeErrors]
18+
* @property {string} [trustedTypesPolicyName]
19+
*/
20+
1321
/**
1422
* @typedef {Object} Options
1523
* @property {boolean} hot
1624
* @property {boolean} liveReload
1725
* @property {boolean} progress
18-
* @property {boolean | { warnings?: boolean, errors?: boolean, runtimeErrors?: boolean, trustedTypesPolicyName?: string }} overlay
26+
* @property {boolean | OverlayOptions} overlay
1927
* @property {string} [logging]
2028
* @property {number} [reconnect]
2129
*/
@@ -27,6 +35,30 @@ import createSocketURL from "./utils/createSocketURL.js";
2735
* @property {string} [previousHash]
2836
*/
2937

38+
/**
39+
* @param {boolean | { warnings?: boolean | string; errors?: boolean | string; runtimeErrors?: boolean | string; }} overlayOptions
40+
*/
41+
const decodeOverlayOptions = (overlayOptions) => {
42+
if (typeof overlayOptions === "object") {
43+
["warnings", "errors", "runtimeErrors"].forEach((property) => {
44+
if (typeof overlayOptions[property] === "string") {
45+
const overlayFilterFunctionString = decodeURIComponent(
46+
overlayOptions[property]
47+
);
48+
49+
// eslint-disable-next-line no-new-func
50+
const overlayFilterFunction = new Function(
51+
"message",
52+
`var callback = ${overlayFilterFunctionString}
53+
return callback(message)`
54+
);
55+
56+
overlayOptions[property] = overlayFilterFunction;
57+
}
58+
});
59+
}
60+
};
61+
3062
/**
3163
* @type {Status}
3264
*/
@@ -83,6 +115,8 @@ if (parsedResourceQuery.overlay) {
83115
runtimeErrors: true,
84116
...options.overlay,
85117
};
118+
119+
decodeOverlayOptions(options.overlay);
86120
}
87121
enabledFeatures.Overlay = true;
88122
}
@@ -173,6 +207,7 @@ const onSocketMessage = {
173207
}
174208

175209
options.overlay = value;
210+
decodeOverlayOptions(options.overlay);
176211
},
177212
/**
178213
* @param {number} value
@@ -266,17 +301,24 @@ const onSocketMessage = {
266301
log.warn(printableWarnings[i]);
267302
}
268303

269-
const needShowOverlayForWarnings =
304+
const overlayWarningsSetting =
270305
typeof options.overlay === "boolean"
271306
? options.overlay
272307
: options.overlay && options.overlay.warnings;
273308

274-
if (needShowOverlayForWarnings) {
275-
overlay.send({
276-
type: "BUILD_ERROR",
277-
level: "warning",
278-
messages: warnings,
279-
});
309+
if (overlayWarningsSetting) {
310+
const warningsToDisplay =
311+
typeof overlayWarningsSetting === "function"
312+
? warnings.filter(overlayWarningsSetting)
313+
: warnings;
314+
315+
if (warningsToDisplay.length) {
316+
overlay.send({
317+
type: "BUILD_ERROR",
318+
level: "warning",
319+
messages: warnings,
320+
});
321+
}
280322
}
281323

282324
if (params && params.preventReloading) {
@@ -303,17 +345,24 @@ const onSocketMessage = {
303345
log.error(printableErrors[i]);
304346
}
305347

306-
const needShowOverlayForErrors =
348+
const overlayErrorsSettings =
307349
typeof options.overlay === "boolean"
308350
? options.overlay
309351
: options.overlay && options.overlay.errors;
310352

311-
if (needShowOverlayForErrors) {
312-
overlay.send({
313-
type: "BUILD_ERROR",
314-
level: "error",
315-
messages: errors,
316-
});
353+
if (overlayErrorsSettings) {
354+
const errorsToDisplay =
355+
typeof overlayErrorsSettings === "function"
356+
? errors.filter(overlayErrorsSettings)
357+
: errors;
358+
359+
if (errorsToDisplay.length) {
360+
overlay.send({
361+
type: "BUILD_ERROR",
362+
level: "error",
363+
messages: errors,
364+
});
365+
}
317366
}
318367
},
319368
/**

client-src/overlay.js

+30-14
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function formatProblem(type, item) {
7878
/**
7979
* @typedef {Object} CreateOverlayOptions
8080
* @property {string | null} trustedTypesPolicyName
81-
* @property {boolean} [catchRuntimeError]
81+
* @property {boolean | (error: Error) => void} [catchRuntimeError]
8282
*/
8383

8484
/**
@@ -90,6 +90,8 @@ const createOverlay = (options) => {
9090
let iframeContainerElement;
9191
/** @type {HTMLDivElement | null | undefined} */
9292
let containerElement;
93+
/** @type {HTMLDivElement | null | undefined} */
94+
let headerElement;
9395
/** @type {Array<(element: HTMLDivElement) => void>} */
9496
let onLoadQueue = [];
9597
/** @type {TrustedTypePolicy | undefined} */
@@ -124,6 +126,7 @@ const createOverlay = (options) => {
124126
iframeContainerElement.id = "webpack-dev-server-client-overlay";
125127
iframeContainerElement.src = "about:blank";
126128
applyStyle(iframeContainerElement, iframeStyle);
129+
127130
iframeContainerElement.onload = () => {
128131
const contentElement =
129132
/** @type {Document} */
@@ -141,7 +144,7 @@ const createOverlay = (options) => {
141144
contentElement.id = "webpack-dev-server-client-overlay-div";
142145
applyStyle(contentElement, containerStyle);
143146

144-
const headerElement = document.createElement("div");
147+
headerElement = document.createElement("div");
145148

146149
headerElement.innerText = "Compiled with problems:";
147150
applyStyle(headerElement, headerStyle);
@@ -219,9 +222,15 @@ const createOverlay = (options) => {
219222
* @param {string} type
220223
* @param {Array<string | { moduleIdentifier?: string, moduleName?: string, loc?: string, message?: string }>} messages
221224
* @param {string | null} trustedTypesPolicyName
225+
* @param {'build' | 'runtime'} messageSource
222226
*/
223-
function show(type, messages, trustedTypesPolicyName) {
227+
function show(type, messages, trustedTypesPolicyName, messageSource) {
224228
ensureOverlayExists(() => {
229+
headerElement.innerText =
230+
messageSource === "runtime"
231+
? "Uncaught runtime errors:"
232+
: "Compiled with problems:";
233+
225234
messages.forEach((message) => {
226235
const entryElement = document.createElement("div");
227236
const msgStyle =
@@ -267,8 +276,8 @@ const createOverlay = (options) => {
267276
}
268277

269278
const overlayService = createOverlayMachine({
270-
showOverlay: ({ level = "error", messages }) =>
271-
show(level, messages, options.trustedTypesPolicyName),
279+
showOverlay: ({ level = "error", messages, messageSource }) =>
280+
show(level, messages, options.trustedTypesPolicyName, messageSource),
272281
hideOverlay: hide,
273282
});
274283

@@ -284,15 +293,22 @@ const createOverlay = (options) => {
284293
const errorObject =
285294
error instanceof Error ? error : new Error(error || message);
286295

287-
overlayService.send({
288-
type: "RUNTIME_ERROR",
289-
messages: [
290-
{
291-
message: errorObject.message,
292-
stack: parseErrorToStacks(errorObject),
293-
},
294-
],
295-
});
296+
const shouldDisplay =
297+
typeof options.catchRuntimeError === "function"
298+
? options.catchRuntimeError(errorObject)
299+
: true;
300+
301+
if (shouldDisplay) {
302+
overlayService.send({
303+
type: "RUNTIME_ERROR",
304+
messages: [
305+
{
306+
message: errorObject.message,
307+
stack: parseErrorToStacks(errorObject),
308+
},
309+
],
310+
});
311+
}
296312
});
297313
}
298314

client-src/overlay/state-machine.js

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import createMachine from "./fsm.js";
44
* @typedef {Object} ShowOverlayData
55
* @property {'warning' | 'error'} level
66
* @property {Array<string | { moduleIdentifier?: string, moduleName?: string, loc?: string, message?: string }>} messages
7+
* @property {'build' | 'runtime'} messageSource
78
*/
89

910
/**
@@ -23,6 +24,7 @@ const createOverlayMachine = (options) => {
2324
context: {
2425
level: "error",
2526
messages: [],
27+
messageSource: "build",
2628
},
2729
states: {
2830
hidden: {
@@ -73,18 +75,21 @@ const createOverlayMachine = (options) => {
7375
return {
7476
messages: [],
7577
level: "error",
78+
messageSource: "build",
7679
};
7780
},
7881
appendMessages: (context, event) => {
7982
return {
8083
messages: context.messages.concat(event.messages),
8184
level: event.level || context.level,
85+
messageSource: event.type === "RUNTIME_ERROR" ? "runtime" : "build",
8286
};
8387
},
8488
setMessages: (context, event) => {
8589
return {
8690
messages: event.messages,
8791
level: event.level || context.level,
92+
messageSource: event.type === "RUNTIME_ERROR" ? "runtime" : "build",
8893
};
8994
},
9095
hideOverlay,

examples/client/overlay/app.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@ const createErrorBtn = require("./error-button");
55

66
const target = document.querySelector("#target");
77

8-
target.insertAdjacentElement("afterend", createErrorBtn());
8+
target.insertAdjacentElement(
9+
"afterend",
10+
createErrorBtn("Click to throw error", "Error message thrown from JS")
11+
);
12+
13+
target.insertAdjacentElement(
14+
"afterend",
15+
createErrorBtn("Click to throw ignored error", "something something")
16+
);
917

1018
// eslint-disable-next-line import/no-unresolved, import/extensions
1119
const invalid = require("./invalid.js");
+14-8
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
"use strict";
22

3-
function unsafeOperation() {
4-
throw new Error("Error message thrown from JS");
5-
}
3+
/**
4+
*
5+
* @param {string} label
6+
* @param {string} errorMessage
7+
* @returns HTMLButtonElement
8+
*/
9+
module.exports = function createErrorButton(label, errorMessage) {
10+
function unsafeOperation() {
11+
throw new Error(errorMessage);
12+
}
613

7-
function handleButtonClick() {
8-
unsafeOperation();
9-
}
14+
function handleButtonClick() {
15+
unsafeOperation();
16+
}
1017

11-
module.exports = function createErrorButton() {
1218
const errorBtn = document.createElement("button");
1319

1420
errorBtn.addEventListener("click", handleButtonClick);
15-
errorBtn.innerHTML = "Click to throw error";
21+
errorBtn.innerHTML = label;
1622

1723
return errorBtn;
1824
};

examples/client/overlay/webpack.config.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,26 @@ module.exports = setup({
1010
entry: "./app.js",
1111
devServer: {
1212
client: {
13-
overlay: true,
13+
overlay: {
14+
warnings: false,
15+
runtimeErrors: (msg) => {
16+
if (msg) {
17+
let msgString;
18+
19+
if (msg instanceof Error) {
20+
msgString = msg.message;
21+
} else if (typeof msg === "string") {
22+
msgString = msg;
23+
}
24+
25+
if (msgString) {
26+
return !/something/i.test(msgString);
27+
}
28+
}
29+
30+
return true;
31+
},
32+
},
1433
},
1534
},
1635
// uncomment to test for IE

0 commit comments

Comments
 (0)