diff --git a/.cspell.json b/.cspell.json index 4a7bc2be12..30ba8bbcf0 100644 --- a/.cspell.json +++ b/.cspell.json @@ -3,6 +3,7 @@ "language": "en,en-gb", "words": [ "apos", + "beforeunload", "camelcase", "tapable", "sockjs", diff --git a/client-src/index.js b/client-src/index.js index eeee5b717e..e8a2ff47c5 100644 --- a/client-src/index.js +++ b/client-src/index.js @@ -287,6 +287,7 @@ const overlay = */ const reloadApp = ({ hot, liveReload }, currentStatus) => { if (currentStatus.isUnloading) { + log.warn("HMR blocked. Please refresh the page."); return; } diff --git a/examples/client/beforeunload/README.md b/examples/client/beforeunload/README.md new file mode 100644 index 0000000000..dd9d1bae5a --- /dev/null +++ b/examples/client/beforeunload/README.md @@ -0,0 +1,33 @@ +# Beforeunload Example + +**webpack.config.js** + +```js +module.exports = { + devServer: { + hot: true, + liveReload: true, // Both HMR and Live Reload can be affected + }, +}; +``` + +Usage via CLI: + +```console +npx webpack serve --open +``` + +This example demonstrates an issue where webpack-dev-server's `isUnloading` flag gets stuck after canceling the browser's "Leave site?" dialog, blocking both HMR and Live Reload updates. + +## What Should Happen + +The script should open `http://localhost:8080/` in your default browser. + +1. Click **"Add Beforeunload Event"** button +2. Try to reload the page and click **"Cancel"** in the dialog +3. Edit `app.js` file to trigger rebuild +4. **Issue**: Page updates are blocked until manual refresh + +## How to Fix + +**Manually refresh the page** (F5/Ctrl+R) to restore HMR and Live Reload functionality. diff --git a/examples/client/beforeunload/app.js b/examples/client/beforeunload/app.js new file mode 100644 index 0000000000..bb0860fba8 --- /dev/null +++ b/examples/client/beforeunload/app.js @@ -0,0 +1,57 @@ +"use strict"; + +const target = document.querySelector("#target"); + +function beforeunloadHandler(event) { + event.preventDefault(); + event.returnValue = ""; + return ""; +} + +let isEventRegistered = false; + +const addEventButton = document.createElement("button"); +addEventButton.textContent = "Add Beforeunload Event"; +addEventButton.style.cssText = + "padding: 10px 20px; margin: 10px; font-size: 16px; cursor: pointer; background-color: #28a745; color: white; border: none; border-radius: 4px;"; +addEventButton.addEventListener("click", function () { + if (!isEventRegistered) { + window.addEventListener("beforeunload", beforeunloadHandler); + isEventRegistered = true; + updateStatus(); + console.log("[webpack-dev-server] beforeunload event added"); + } +}); + +const reloadButton = document.createElement("button"); +reloadButton.textContent = "Reload Page"; +reloadButton.style.cssText = + "padding: 10px 20px; margin: 10px; font-size: 16px; cursor: pointer; background-color: #007bff; color: white; border: none; border-radius: 4px;"; +reloadButton.addEventListener("click", function () { + window.location.reload(); +}); + +const statusDisplay = document.createElement("div"); +statusDisplay.style.cssText = + "margin: 10px; padding: 10px; border: 2px solid #ccc; border-radius: 4px; font-weight: bold;"; + +function updateStatus() { + statusDisplay.textContent = isEventRegistered + ? "Status: Beforeunload event is ACTIVE - Page exit will be blocked" + : "Status: Beforeunload event is INACTIVE - Page exit will not be blocked"; + statusDisplay.style.backgroundColor = isEventRegistered + ? "#d4edda" + : "#f8d7da"; + statusDisplay.style.borderColor = isEventRegistered ? "#28a745" : "#dc3545"; +} + +target.classList.add("pass"); +target.innerHTML = "Beforeunload Event Controller"; +target.appendChild(document.createElement("br")); +target.appendChild(document.createElement("br")); +target.appendChild(statusDisplay); +target.appendChild(document.createElement("br")); +target.appendChild(addEventButton); +target.appendChild(reloadButton); + +updateStatus(); diff --git a/examples/client/beforeunload/webpack.config.js b/examples/client/beforeunload/webpack.config.js new file mode 100644 index 0000000000..802f72b24f --- /dev/null +++ b/examples/client/beforeunload/webpack.config.js @@ -0,0 +1,14 @@ +"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: "./app.js", + devServer: { + hot: true, + liveReload: true, + }, +});