diff --git a/client-src/progress.js b/client-src/progress.js index 21a98e7209..02e74ef781 100644 --- a/client-src/progress.js +++ b/client-src/progress.js @@ -1,31 +1,40 @@ -class WebpackDevServerProgress extends HTMLElement { - constructor() { - super(); - this.attachShadow({ mode: "open" }); - this.maxDashOffset = -219.99078369140625; - this.animationTimer = null; +export function isProgressSupported() { + return "customElements" in self && !!HTMLElement.prototype.attachShadow; +} + +export function defineProgressElement() { + if (customElements.get("wds-progress")) { + return; } - #reset() { - clearTimeout(this.animationTimer); - this.animationTimer = null; + class WebpackDevServerProgress extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + this.maxDashOffset = -219.99078369140625; + this.animationTimer = null; + } - const typeAttr = this.getAttribute("type")?.toLowerCase(); - this.type = typeAttr === "circular" ? "circular" : "linear"; + #reset() { + clearTimeout(this.animationTimer); + this.animationTimer = null; - const innerHTML = - this.type === "circular" - ? WebpackDevServerProgress.#circularTemplate() - : WebpackDevServerProgress.#linearTemplate(); - this.shadowRoot.innerHTML = innerHTML; + const typeAttr = this.getAttribute("type")?.toLowerCase(); + this.type = typeAttr === "circular" ? "circular" : "linear"; - this.initialProgress = Number(this.getAttribute("progress")) ?? 0; + const innerHTML = + this.type === "circular" + ? WebpackDevServerProgress.#circularTemplate() + : WebpackDevServerProgress.#linearTemplate(); + this.shadowRoot.innerHTML = innerHTML; - this.#update(this.initialProgress); - } + this.initialProgress = Number(this.getAttribute("progress")) ?? 0; - static #circularTemplate() { - return ` + this.#update(this.initialProgress); + } + + static #circularTemplate() { + return `
`; - } - - connectedCallback() { - this.#reset(); - } - - static get observedAttributes() { - return ["progress", "type"]; - } + } - attributeChangedCallback(name, oldValue, newValue) { - if (name === "progress") { - this.#update(Number(newValue)); - } else if (name === "type") { + connectedCallback() { this.#reset(); } - } - #update(percent) { - const element = this.shadowRoot.querySelector("#progress"); - if (this.type === "circular") { - const path = this.shadowRoot.querySelector("path"); - const value = this.shadowRoot.querySelector("#percent-value"); - const offset = ((100 - percent) / 100) * this.maxDashOffset; - - path.style.strokeDashoffset = offset; - value.textContent = percent; - } else { - element.style.width = `${percent}%`; + static get observedAttributes() { + return ["progress", "type"]; } - if (percent >= 100) { - this.#hide(); - } else if (percent > 0) { - this.#show(); + attributeChangedCallback(name, oldValue, newValue) { + if (name === "progress") { + this.#update(Number(newValue)); + } else if (name === "type") { + this.#reset(); + } } - } - - #show() { - const element = this.shadowRoot.querySelector("#progress"); - element.classList.remove("hidden"); - } - #hide() { - const element = this.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); + #update(percent) { + const element = this.shadowRoot.querySelector("#progress"); + if (this.type === "circular") { + const path = this.shadowRoot.querySelector("path"); + const value = this.shadowRoot.querySelector("#percent-value"); + const offset = ((100 - percent) / 100) * this.maxDashOffset; + + path.style.strokeDashoffset = offset; + value.textContent = percent; + } else { + element.style.width = `${percent}%`; + } + + if (percent >= 100) { + this.#hide(); + } else if (percent > 0) { + this.#show(); + } } - } -} -export function isProgressSupported() { - return "customElements" in window && !!HTMLElement.prototype.attachShadow; -} + #show() { + const element = this.shadowRoot.querySelector("#progress"); + element.classList.remove("hidden"); + } -export function defineProgressElement() { - if (customElements.get("wds-progress")) { - return; + #hide() { + const element = this.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); + } + } } customElements.define("wds-progress", WebpackDevServerProgress); diff --git a/lib/Server.js b/lib/Server.js index ba85abd41e..82feaa6a41 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -560,7 +560,11 @@ class Server { * @returns bool */ static isWebTarget(compiler) { - // TODO improve for the next major version - we should store `web` and other targets in `compiler.options.environment` + if (compiler.platform && compiler.platform.web) { + return compiler.platform.web; + } + + // TODO improve for the next major version and keep only `webTargets` to fallback for old versions if ( compiler.options.externalsPresets && compiler.options.externalsPresets.web @@ -580,6 +584,7 @@ class Server { "webworker", "electron-preload", "electron-renderer", + "nwjs", "node-webkit", // eslint-disable-next-line no-undefined undefined, diff --git a/test/e2e/__snapshots__/target.test.js.snap.webpack5 b/test/e2e/__snapshots__/target.test.js.snap.webpack5 index e60b75533c..4040d1fcfb 100644 --- a/test/e2e/__snapshots__/target.test.js.snap.webpack5 +++ b/test/e2e/__snapshots__/target.test.js.snap.webpack5 @@ -71,3 +71,16 @@ exports[`target should work using "webworker" target: console messages 1`] = ` `; exports[`target should work using "webworker" target: page errors 1`] = `[]`; + +exports[`target should work using multi compiler mode with \`web\` and \`webworker\` targets: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Worker said: I'm working before postMessage", + "Worker said: Message sent: message", +] +`; + +exports[`target should work using multi compiler mode with \`web\` and \`webworker\` targets: page errors 1`] = `[]`; diff --git a/test/e2e/target.test.js b/test/e2e/target.test.js index 5a13872f95..7a779c4aee 100644 --- a/test/e2e/target.test.js +++ b/test/e2e/target.test.js @@ -3,6 +3,7 @@ const webpack = require("webpack"); const Server = require("../../lib/Server"); const config = require("../fixtures/client-config/webpack.config"); +const workerConfig = require("../fixtures/worker-config/webpack.config"); const runBrowser = require("../helpers/run-browser"); const port = require("../ports-map").target; @@ -89,4 +90,44 @@ describe("target", () => { } }); } + + it("should work using multi compiler mode with `web` and `webworker` targets", async () => { + const compiler = webpack(workerConfig); + const devServerOptions = { + port, + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", (message) => { + consoleMessages.push(message); + }) + .on("pageerror", (error) => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0", + }); + + expect(consoleMessages.map((message) => message.text())).toMatchSnapshot( + "console messages", + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); }); diff --git a/test/fixtures/client-config/webpack.config.js b/test/fixtures/client-config/webpack.config.js index 5ff6b93c31..41cfbc5928 100644 --- a/test/fixtures/client-config/webpack.config.js +++ b/test/fixtures/client-config/webpack.config.js @@ -3,7 +3,7 @@ const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); module.exports = { - devtool: "eval-nosources-cheap-source-map", + devtool: false, mode: "development", context: __dirname, stats: "none", diff --git a/test/fixtures/worker-config/index.js b/test/fixtures/worker-config/index.js new file mode 100644 index 0000000000..e14970b9ec --- /dev/null +++ b/test/fixtures/worker-config/index.js @@ -0,0 +1,9 @@ +"use strict"; + +const myWorker = new Worker("./worker.js"); + +myWorker.onmessage = (event) => { + console.log(`Worker said: ${event.data}`); +}; + +myWorker.postMessage("message"); diff --git a/test/fixtures/worker-config/webpack.config.js b/test/fixtures/worker-config/webpack.config.js new file mode 100644 index 0000000000..a4b8cc5940 --- /dev/null +++ b/test/fixtures/worker-config/webpack.config.js @@ -0,0 +1,45 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = [ + { + name: "app", + dependencies: ["worker"], + devtool: false, + target: "web", + entry: "./index.js", + mode: "development", + context: __dirname, + stats: "none", + output: { + path: "/", + }, + infrastructureLogging: { + level: "info", + stream: { + write: () => {}, + }, + }, + plugins: [new HTMLGeneratorPlugin()], + }, + { + name: "worker", + devtool: false, + target: "webworker", + entry: "./worker.js", + mode: "development", + context: __dirname, + stats: "none", + output: { + path: "/", + filename: "worker.js", + }, + infrastructureLogging: { + level: "info", + stream: { + write: () => {}, + }, + }, + }, +]; diff --git a/test/fixtures/worker-config/worker.js b/test/fixtures/worker-config/worker.js new file mode 100644 index 0000000000..37bab96133 --- /dev/null +++ b/test/fixtures/worker-config/worker.js @@ -0,0 +1,7 @@ +"use strict"; + +postMessage("I'm working before postMessage"); + +onmessage = (event) => { + postMessage(`Message sent: ${event.data}`); +};