Skip to content

Commit

Permalink
feat: allow filter overlay errors/warnings with function (#4813)
Browse files Browse the repository at this point in the history
  • Loading branch information
malcolm-kee committed May 6, 2023
1 parent ee748a7 commit aab01b3
Show file tree
Hide file tree
Showing 14 changed files with 1,322 additions and 189 deletions.
79 changes: 64 additions & 15 deletions client-src/index.js
Expand Up @@ -10,12 +10,20 @@ import sendMessage from "./utils/sendMessage.js";
import reloadApp from "./utils/reloadApp.js";
import createSocketURL from "./utils/createSocketURL.js";

/**
* @typedef {Object} OverlayOptions
* @property {boolean | (error: Error) => boolean} [warnings]
* @property {boolean | (error: Error) => boolean} [errors]
* @property {boolean | (error: Error) => boolean} [runtimeErrors]
* @property {string} [trustedTypesPolicyName]
*/

/**
* @typedef {Object} Options
* @property {boolean} hot
* @property {boolean} liveReload
* @property {boolean} progress
* @property {boolean | { warnings?: boolean, errors?: boolean, runtimeErrors?: boolean, trustedTypesPolicyName?: string }} overlay
* @property {boolean | OverlayOptions} overlay
* @property {string} [logging]
* @property {number} [reconnect]
*/
Expand All @@ -27,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}
*/
Expand Down Expand Up @@ -83,6 +115,8 @@ if (parsedResourceQuery.overlay) {
runtimeErrors: true,
...options.overlay,
};

decodeOverlayOptions(options.overlay);
}
enabledFeatures.Overlay = true;
}
Expand Down Expand Up @@ -173,6 +207,7 @@ const onSocketMessage = {
}

options.overlay = value;
decodeOverlayOptions(options.overlay);
},
/**
* @param {number} value
Expand Down Expand Up @@ -266,17 +301,24 @@ const onSocketMessage = {
log.warn(printableWarnings[i]);
}

const needShowOverlayForWarnings =
const overlayWarningsSetting =
typeof options.overlay === "boolean"
? options.overlay
: options.overlay && options.overlay.warnings;

if (needShowOverlayForWarnings) {
overlay.send({
type: "BUILD_ERROR",
level: "warning",
messages: warnings,
});
if (overlayWarningsSetting) {
const warningsToDisplay =
typeof overlayWarningsSetting === "function"
? warnings.filter(overlayWarningsSetting)
: warnings;

if (warningsToDisplay.length) {
overlay.send({
type: "BUILD_ERROR",
level: "warning",
messages: warnings,
});
}
}

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

const needShowOverlayForErrors =
const overlayErrorsSettings =
typeof options.overlay === "boolean"
? options.overlay
: options.overlay && options.overlay.errors;

if (needShowOverlayForErrors) {
overlay.send({
type: "BUILD_ERROR",
level: "error",
messages: errors,
});
if (overlayErrorsSettings) {
const errorsToDisplay =
typeof overlayErrorsSettings === "function"
? errors.filter(overlayErrorsSettings)
: errors;

if (errorsToDisplay.length) {
overlay.send({
type: "BUILD_ERROR",
level: "error",
messages: errors,
});
}
}
},
/**
Expand Down
44 changes: 30 additions & 14 deletions client-src/overlay.js
Expand Up @@ -78,7 +78,7 @@ function formatProblem(type, item) {
/**
* @typedef {Object} CreateOverlayOptions
* @property {string | null} trustedTypesPolicyName
* @property {boolean} [catchRuntimeError]
* @property {boolean | (error: Error) => void} [catchRuntimeError]
*/

/**
Expand All @@ -90,6 +90,8 @@ const createOverlay = (options) => {
let iframeContainerElement;
/** @type {HTMLDivElement | null | undefined} */
let containerElement;
/** @type {HTMLDivElement | null | undefined} */
let headerElement;
/** @type {Array<(element: HTMLDivElement) => void>} */
let onLoadQueue = [];
/** @type {TrustedTypePolicy | undefined} */
Expand Down Expand Up @@ -124,6 +126,7 @@ const createOverlay = (options) => {
iframeContainerElement.id = "webpack-dev-server-client-overlay";
iframeContainerElement.src = "about:blank";
applyStyle(iframeContainerElement, iframeStyle);

iframeContainerElement.onload = () => {
const contentElement =
/** @type {Document} */
Expand All @@ -141,7 +144,7 @@ const createOverlay = (options) => {
contentElement.id = "webpack-dev-server-client-overlay-div";
applyStyle(contentElement, containerStyle);

const headerElement = document.createElement("div");
headerElement = document.createElement("div");

headerElement.innerText = "Compiled with problems:";
applyStyle(headerElement, headerStyle);
Expand Down Expand Up @@ -219,9 +222,15 @@ const createOverlay = (options) => {
* @param {string} type
* @param {Array<string | { moduleIdentifier?: string, moduleName?: string, loc?: string, message?: string }>} messages
* @param {string | null} trustedTypesPolicyName
* @param {'build' | 'runtime'} messageSource
*/
function show(type, messages, trustedTypesPolicyName) {
function show(type, messages, trustedTypesPolicyName, messageSource) {
ensureOverlayExists(() => {
headerElement.innerText =
messageSource === "runtime"
? "Uncaught runtime errors:"
: "Compiled with problems:";

messages.forEach((message) => {
const entryElement = document.createElement("div");
const msgStyle =
Expand Down Expand Up @@ -267,8 +276,8 @@ const createOverlay = (options) => {
}

const overlayService = createOverlayMachine({
showOverlay: ({ level = "error", messages }) =>
show(level, messages, options.trustedTypesPolicyName),
showOverlay: ({ level = "error", messages, messageSource }) =>
show(level, messages, options.trustedTypesPolicyName, messageSource),
hideOverlay: hide,
});

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

overlayService.send({
type: "RUNTIME_ERROR",
messages: [
{
message: errorObject.message,
stack: parseErrorToStacks(errorObject),
},
],
});
const shouldDisplay =
typeof options.catchRuntimeError === "function"
? options.catchRuntimeError(errorObject)
: true;

if (shouldDisplay) {
overlayService.send({
type: "RUNTIME_ERROR",
messages: [
{
message: errorObject.message,
stack: parseErrorToStacks(errorObject),
},
],
});
}
});
}

Expand Down
5 changes: 5 additions & 0 deletions client-src/overlay/state-machine.js
Expand Up @@ -4,6 +4,7 @@ import createMachine from "./fsm.js";
* @typedef {Object} ShowOverlayData
* @property {'warning' | 'error'} level
* @property {Array<string | { moduleIdentifier?: string, moduleName?: string, loc?: string, message?: string }>} messages
* @property {'build' | 'runtime'} messageSource
*/

/**
Expand All @@ -23,6 +24,7 @@ const createOverlayMachine = (options) => {
context: {
level: "error",
messages: [],
messageSource: "build",
},
states: {
hidden: {
Expand Down Expand Up @@ -73,18 +75,21 @@ const createOverlayMachine = (options) => {
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,
Expand Down
10 changes: 9 additions & 1 deletion examples/client/overlay/app.js
Expand Up @@ -5,7 +5,15 @@ const createErrorBtn = require("./error-button");

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

target.insertAdjacentElement("afterend", createErrorBtn());
target.insertAdjacentElement(
"afterend",
createErrorBtn("Click to throw error", "Error message thrown from JS")
);

target.insertAdjacentElement(
"afterend",
createErrorBtn("Click to throw ignored error", "something something")
);

// eslint-disable-next-line import/no-unresolved, import/extensions
const invalid = require("./invalid.js");
Expand Down
22 changes: 14 additions & 8 deletions examples/client/overlay/error-button.js
@@ -1,18 +1,24 @@
"use strict";

function unsafeOperation() {
throw new Error("Error message thrown from JS");
}
/**
*
* @param {string} label
* @param {string} errorMessage
* @returns HTMLButtonElement
*/
module.exports = function createErrorButton(label, errorMessage) {
function unsafeOperation() {
throw new Error(errorMessage);
}

function handleButtonClick() {
unsafeOperation();
}
function handleButtonClick() {
unsafeOperation();
}

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

errorBtn.addEventListener("click", handleButtonClick);
errorBtn.innerHTML = "Click to throw error";
errorBtn.innerHTML = label;

return errorBtn;
};
21 changes: 20 additions & 1 deletion examples/client/overlay/webpack.config.js
Expand Up @@ -10,7 +10,26 @@ module.exports = setup({
entry: "./app.js",
devServer: {
client: {
overlay: true,
overlay: {
warnings: false,
runtimeErrors: (msg) => {
if (msg) {
let msgString;

if (msg instanceof Error) {
msgString = msg.message;
} else if (typeof msg === "string") {
msgString = msg;
}

if (msgString) {
return !/something/i.test(msgString);
}
}

return true;
},
},
},
},
// uncomment to test for IE
Expand Down

0 comments on commit aab01b3

Please sign in to comment.