Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-overlay-dismissed-on-initial-load.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"webpack-dev-server": patch
---

Keep the overlay for a runtime error thrown while the page is still loading.
6 changes: 4 additions & 2 deletions client-src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,8 @@ const onSocketMessage = {
log.info("App updated. Recompiling...");

// Fixes #1042. overlay doesn't clear if errors are fixed but warnings remain.
// A rebuild replaces the code a runtime error came from too, so unlike
// `ok`/`still-ok` this clears that overlay as well.
if (options.overlay) {
overlay.send({ type: "DISMISS" });
}
Expand Down Expand Up @@ -521,7 +523,7 @@ const onSocketMessage = {
log.info("Nothing changed.");

if (options.overlay) {
overlay.send({ type: "DISMISS" });
overlay.send({ type: "BUILD_OK" });
}

sendMessage("StillOk");
Expand All @@ -530,7 +532,7 @@ const onSocketMessage = {
sendMessage("Ok");

if (options.overlay) {
overlay.send({ type: "DISMISS" });
overlay.send({ type: "BUILD_OK" });
}

reloadApp(options, status);
Expand Down
9 changes: 9 additions & 0 deletions client-src/overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ const createOverlayMachine = (options) => {
target: "hidden",
actions: ["dismissMessages", "hideOverlay"],
},
// WHY: A compilation reporting success says nothing about a runtime
// error, so it clears build errors only — no state but this one
// handles it, and an unhandled event is a no-op. Dismissing on the
// initial handshake's `ok` is what closed an overlay raised while
// the page was still loading (#5024).
BUILD_OK: {
target: "hidden",
actions: ["dismissMessages", "hideOverlay"],
},
BUILD_ERROR: {
target: "displayBuildError",
actions: ["appendMessages", "showOverlay"],
Expand Down
21 changes: 19 additions & 2 deletions test/client/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,15 @@ describe("index", () => {

t.assert.snapshot(log.log.info.mock.calls[1][0]);
t.assert.snapshot(sendMessage.mock.calls[0][0]);
expect(overlay.send).not.toHaveBeenCalledWith({ type: "DISMISS" });
expect(overlay.send).not.toHaveBeenCalledWith({ type: "BUILD_OK" });

// change flags
onSocketMessage.overlay(true);
onSocketMessage["still-ok"]();

expect(overlay.send).toHaveBeenCalledWith({ type: "DISMISS" });
// An unchanged compilation clears a build error only, never a runtime one.
expect(overlay.send).toHaveBeenCalledWith({ type: "BUILD_OK" });
expect(overlay.send).not.toHaveBeenCalledWith({ type: "DISMISS" });
});

it("should run onSocketMessage.progress and onSocketMessage['progress-update']", (t) => {
Expand Down Expand Up @@ -158,6 +160,21 @@ describe("index", () => {
expect(res).toBeUndefined();
});

it("should clear only build errors when a compilation succeeds", () => {
onSocketMessage.overlay(true);

// A rebuild replaces the code a runtime error came from, so it clears both.
onSocketMessage.invalid();

expect(overlay.send).toHaveBeenCalledWith({ type: "DISMISS" });

// A successful compilation says nothing about a runtime error — see #5024.
onSocketMessage.ok();

expect(overlay.send).toHaveBeenCalledWith({ type: "BUILD_OK" });
expect(overlay.send.mock.calls).toHaveLength(2);
});

it("should run onSocketMessage['static-changed']", (t) => {
onSocketMessage["static-changed"]();

Expand Down
29 changes: 29 additions & 0 deletions test/client/overlay-lifecycle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ const buildError = (messages) => ({
messages,
});

const runtimeError = (messages) => ({
type: "RUNTIME_ERROR",
level: "error",
messages,
});

const loadOverlay = () => {
const iframe = document.querySelector("#webpack-dev-server-client-overlay");

Expand Down Expand Up @@ -68,6 +74,29 @@ describe("overlay lifecycle", () => {
expect(document.body.contains(iframe)).toBe(false);
});

it("lets a successful compilation clear a build error but not a runtime one", () => {
const overlay = createOverlay({});

overlay.send(buildError(["build error"]));
const buildIframe = loadOverlay();

overlay.send({ type: "BUILD_OK" });
expect(document.body.contains(buildIframe)).toBe(false);

overlay.send(runtimeError(["runtime error"]));
const runtimeIframe = loadOverlay();

// The compilation succeeded, so it knows nothing about this error — see #5024.
overlay.send({ type: "BUILD_OK" });
expect(document.body.contains(runtimeIframe)).toBe(true);
expect(runtimeIframe.contentDocument.body.textContent).toContain(
"runtime error",
);

overlay.send({ type: "DISMISS" });
expect(document.body.contains(runtimeIframe)).toBe(false);
});

it("reuses its Trusted Types policy and encodes editor paths", () => {
const createPolicy = fn(() => ({ createHTML: (value) => value }));
globalThis.trustedTypes = { createPolicy };
Expand Down
45 changes: 45 additions & 0 deletions test/e2e/overlay.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1709,6 +1709,51 @@ describe("overlay", () => {
}
});

it("should keep the overlay for a runtime error thrown during the initial load", async () => {
const compiler = webpack({
...config,
entry: "./throw-on-initial-load.js",
});

const server = new Server(
{
port,
},
compiler,
);

await server.start();

const { page, browser } = await runBrowser();

try {
await page.goto(`http://localhost:${port}/`, {
waitUntil: "networkidle0",
});

// The entry throws while the page is still loading, so the overlay is up
// before the socket handshake reports the (successful) compilation. That
// `ok` used to dismiss it milliseconds later — see #5024.
await waitForExpect(async () => {
const overlayHandle = await page.$(
"#webpack-dev-server-client-overlay",
);

expect(overlayHandle).not.toBeNull();

const overlayFrame = await overlayHandle.contentFrame();
const overlayText = await overlayFrame.evaluate(
() => document.body.textContent,
);

expect(overlayText).toContain("Injected error");
});
} finally {
await browser.close();
await server.stop();
}
});

it("should not show filtered runtime error", async () => {
const compiler = webpack(config);

Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/overlay-config/throw-on-initial-load.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Throws while the entry is still evaluating, i.e. before the client's socket
// handshake completes. Compilation succeeds, so the handshake reports `ok`.
throw new Error("Injected error");
Loading