From 0313243d20bcb9d4b037d2470863e425eadeb3bf Mon Sep 17 00:00:00 2001 From: Malcolm Kee Date: Wed, 21 Sep 2022 18:01:02 +1000 Subject: [PATCH] refactor: tweak style for overlay --- client-src/overlay.js | 84 ++++----- client-src/overlay/styles.js | 89 +++++++++ examples/client/overlay-complex/README.md | 20 ++ examples/client/overlay-complex/src/app.ts | 3 + examples/client/overlay-complex/tsconfig.json | 7 + .../client/overlay-complex/webpack.config.js | 27 +++ package-lock.json | 177 ++++++++++++++++++ package.json | 1 + 8 files changed, 363 insertions(+), 45 deletions(-) create mode 100644 client-src/overlay/styles.js create mode 100644 examples/client/overlay-complex/README.md create mode 100644 examples/client/overlay-complex/src/app.ts create mode 100644 examples/client/overlay-complex/tsconfig.json create mode 100644 examples/client/overlay-complex/webpack.config.js diff --git a/client-src/overlay.js b/client-src/overlay.js index 9c92e88b67..ca506c8ac8 100644 --- a/client-src/overlay.js +++ b/client-src/overlay.js @@ -3,6 +3,15 @@ import ansiHTML from "ansi-html-community"; import { encode } from "html-entities"; +import { + containerStyle, + dismissButtonStyle, + headerStyle, + iframeStyle, + msgStyles, + msgTextStyle, + msgTypeStyle, +} from "./overlay/styles.js"; const colors = { reset: ["transparent", "transparent"], @@ -28,6 +37,17 @@ let overlayTrustedTypesPolicy; ansiHTML.setColors(colors); +/** + * + * @param {HTMLElement} element + * @param {CSSStyleDeclaration} style + */ +function applyStyle(element, style) { + Object.keys(style).forEach((prop) => { + element.style[prop] = style[prop]; + }); +} + /** * @param {string | null} trustedTypesPolicyName */ @@ -45,15 +65,7 @@ function createContainer(trustedTypesPolicyName) { iframeContainerElement = document.createElement("iframe"); iframeContainerElement.id = "webpack-dev-server-client-overlay"; iframeContainerElement.src = "about:blank"; - iframeContainerElement.style.position = "fixed"; - iframeContainerElement.style.left = 0; - iframeContainerElement.style.top = 0; - iframeContainerElement.style.right = 0; - iframeContainerElement.style.bottom = 0; - iframeContainerElement.style.width = "100vw"; - iframeContainerElement.style.height = "100vh"; - iframeContainerElement.style.border = "none"; - iframeContainerElement.style.zIndex = 9999999999; + applyStyle(iframeContainerElement, iframeStyle); iframeContainerElement.onload = () => { containerElement = /** @type {Document} */ @@ -61,48 +73,27 @@ function createContainer(trustedTypesPolicyName) { /** @type {HTMLIFrameElement} */ (iframeContainerElement).contentDocument ).createElement("div"); + containerElement.id = "webpack-dev-server-client-overlay-div"; - containerElement.style.position = "fixed"; - containerElement.style.boxSizing = "border-box"; - containerElement.style.left = 0; - containerElement.style.top = 0; - containerElement.style.right = 0; - containerElement.style.bottom = 0; - containerElement.style.width = "100vw"; - containerElement.style.height = "100vh"; - containerElement.style.backgroundColor = "rgba(0, 0, 0, 0.85)"; - containerElement.style.color = "#E8E8E8"; - containerElement.style.fontFamily = "Menlo, Consolas, monospace"; - containerElement.style.fontSize = "large"; - containerElement.style.padding = "2rem"; - containerElement.style.lineHeight = "1.2"; - containerElement.style.whiteSpace = "pre-wrap"; - containerElement.style.overflow = "auto"; - - const headerElement = document.createElement("span"); + applyStyle(containerElement, containerStyle); + + const headerElement = document.createElement("div"); headerElement.innerText = "Compiled with problems:"; + applyStyle(headerElement, headerStyle); const closeButtonElement = document.createElement("button"); - closeButtonElement.innerText = "X"; - closeButtonElement.style.background = "transparent"; - closeButtonElement.style.border = "none"; - closeButtonElement.style.fontSize = "20px"; - closeButtonElement.style.fontWeight = "bold"; - closeButtonElement.style.color = "white"; - closeButtonElement.style.cursor = "pointer"; - closeButtonElement.style.cssFloat = "right"; - // @ts-ignore - closeButtonElement.style.styleFloat = "right"; + applyStyle(closeButtonElement, dismissButtonStyle); + + closeButtonElement.innerText = "×"; + closeButtonElement.ariaLabel = "Dismiss"; closeButtonElement.addEventListener("click", () => { hide(); }); containerElement.appendChild(headerElement); containerElement.appendChild(closeButtonElement); - containerElement.appendChild(document.createElement("br")); - containerElement.appendChild(document.createElement("br")); /** @type {Document} */ ( @@ -200,26 +191,29 @@ function show(type, messages, trustedTypesPolicyName) { ensureOverlayExists(() => { messages.forEach((message) => { const entryElement = document.createElement("div"); - const typeElement = document.createElement("span"); + 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; - typeElement.style.color = `#${colors.red}`; + applyStyle(typeElement, msgTypeStyle); // Make it look similar to our terminal. const text = ansiHTML(encode(body)); const messageTextNode = document.createElement("div"); + applyStyle(messageTextNode, msgTextStyle); messageTextNode.innerHTML = overlayTrustedTypesPolicy ? overlayTrustedTypesPolicy.createHTML(text) : text; entryElement.appendChild(typeElement); - entryElement.appendChild(document.createElement("br")); - entryElement.appendChild(document.createElement("br")); entryElement.appendChild(messageTextNode); - entryElement.appendChild(document.createElement("br")); - entryElement.appendChild(document.createElement("br")); /** @type {HTMLDivElement} */ (containerElement).appendChild(entryElement); diff --git a/client-src/overlay/styles.js b/client-src/overlay/styles.js new file mode 100644 index 0000000000..4786c1b62e --- /dev/null +++ b/client-src/overlay/styles.js @@ -0,0 +1,89 @@ +// 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: 0, + left: 0, + right: 0, + bottom: 0, + width: "100vw", + height: "100vh", + border: "none", + "z-index": 9999999999, +}; + +const containerStyle = { + position: "fixed", + boxSizing: "border-box", + left: 0, + top: 0, + right: 0, + bottom: 0, + 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: 0, + top: 0, + 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", +}; + +export { + msgStyles, + iframeStyle, + containerStyle, + headerStyle, + dismissButtonStyle, + msgTypeStyle, + msgTextStyle, +}; diff --git a/examples/client/overlay-complex/README.md b/examples/client/overlay-complex/README.md new file mode 100644 index 0000000000..724b9afd15 --- /dev/null +++ b/examples/client/overlay-complex/README.md @@ -0,0 +1,20 @@ +# client.overlay option + +**webpack.config.js** + +```js +module.exports = { + // ... + devServer: { + client: { + overlay: true, + }, + }, +}; +``` + +Usage via CLI: + +```shell +npx webpack serve --open --client-overlay +``` diff --git a/examples/client/overlay-complex/src/app.ts b/examples/client/overlay-complex/src/app.ts new file mode 100644 index 0000000000..cc0e641dd5 --- /dev/null +++ b/examples/client/overlay-complex/src/app.ts @@ -0,0 +1,3 @@ +let string = "hello"; + +string = 5; diff --git a/examples/client/overlay-complex/tsconfig.json b/examples/client/overlay-complex/tsconfig.json new file mode 100644 index 0000000000..800b4facaf --- /dev/null +++ b/examples/client/overlay-complex/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "sourceMap": true + }, + "include": ["src"] +} diff --git a/examples/client/overlay-complex/webpack.config.js b/examples/client/overlay-complex/webpack.config.js new file mode 100644 index 0000000000..a159c0df8e --- /dev/null +++ b/examples/client/overlay-complex/webpack.config.js @@ -0,0 +1,27 @@ +"use strict"; + +// our setup function adds behind-the-scenes bits to the config that all of our +// examples need +const { setup } = require("../../util"); + +module.exports = setup({ + context: __dirname, + entry: "./src/app.ts", + devtool: "inline-source-map", + devServer: { + client: { + overlay: true, + }, + }, + resolve: { + extensions: [".ts", ".tsx", ".js"], + }, + module: { + rules: [ + { + test: /\.tsx?$/, + loader: "ts-loader", + }, + ], + }, +}); diff --git a/package-lock.json b/package-lock.json index 3cffb2768f..e3f6b8df75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,6 +92,7 @@ "style-loader": "^2.0.0", "supertest": "^6.1.3", "tcp-port-used": "^1.0.2", + "ts-loader": "^9.4.1", "typescript": "^4.7.2", "url-loader": "^4.1.1", "webpack": "^5.71.0", @@ -14841,6 +14842,110 @@ "node": ">=8" } }, + "node_modules/ts-loader": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.1.tgz", + "integrity": "sha512-384TYAqGs70rn9F0VBnh6BPTfhga7yFNdC5gXbQpDrBj9/KsT4iRkGqKXhziofHOlE2j6YEaiTYVGKKvPhGWvw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ts-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ts-loader/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-loader/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -26899,6 +27004,78 @@ "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, + "ts-loader": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.1.tgz", + "integrity": "sha512-384TYAqGs70rn9F0VBnh6BPTfhga7yFNdC5gXbQpDrBj9/KsT4iRkGqKXhziofHOlE2j6YEaiTYVGKKvPhGWvw==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", diff --git a/package.json b/package.json index e46204a8d3..e3ba2e91ad 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,7 @@ "style-loader": "^2.0.0", "supertest": "^6.1.3", "tcp-port-used": "^1.0.2", + "ts-loader": "^9.4.1", "typescript": "^4.7.2", "url-loader": "^4.1.1", "webpack": "^5.71.0",