diff --git a/client-src/index.js b/client-src/index.js index 106ab1f8e..d72e5de1b 100644 --- a/client-src/index.js +++ b/client-src/index.js @@ -57,7 +57,13 @@ function setOverrides(overrides) { } if (overrides.dynamicPublicPath) { - options.path = __webpack_public_path__ + options.path; + // Avoid a double slash when the public path has a trailing slash and the + // SSE path a leading one (e.g. "https://host/" + "/__webpack_hmr"). + const publicPath = __webpack_public_path__; + options.path = + publicPath.endsWith("/") && options.path.startsWith("/") + ? publicPath + options.path.slice(1) + : publicPath + options.path; } if (overrides.ansiColors) { diff --git a/test/client.test.js b/test/client.test.js index e310b30b0..fe644e340 100644 --- a/test/client.test.js +++ b/test/client.test.js @@ -536,6 +536,36 @@ describe("client", () => { }); }); + describe("with dynamicPublicPath", () => { + let EventSourceStub; + + beforeEach(() => { + EventSourceStub = makeEventSourceStub(); + globalThis.EventSource = EventSourceStub; + jest.spyOn(console, "info").mockImplementation(() => {}); + jest.spyOn(console, "log").mockImplementation(() => {}); + }); + + afterEach(() => { + delete globalThis.__webpack_public_path__; + jest.restoreAllMocks(); + }); + + it("prepends the public path to the SSE path", () => { + globalThis.__webpack_public_path__ = "/assets"; + loadClient("?dynamicPublicPath=true"); + expect(EventSourceStub.lastInstance().url).toBe("/assets/__webpack_hmr"); + }); + + it("does not produce a double slash when the public path has a trailing slash", () => { + globalThis.__webpack_public_path__ = "https://localhost:3000/assets/"; + loadClient("?dynamicPublicPath=true"); + expect(EventSourceStub.lastInstance().url).toBe( + "https://localhost:3000/assets/__webpack_hmr", + ); + }); + }); + describe("connection lifecycle", () => { let EventSourceStub; let client;