diff --git a/README.md b/README.md index daacd04e6..2eff576fe 100644 --- a/README.md +++ b/README.md @@ -384,7 +384,7 @@ entry: [ | `logging` | `string` | `"info"` | Logger level — one of `"none"`, `"error"`, `"warn"`, `"info"`, `"log"`, `"verbose"`. Uses webpack's runtime logger. | | `name` | `string` | `""` | Restrict updates to a specific compilation name (useful with multi-compiler). | | `autoConnect` | `boolean` | `true` | Connect on load; set to `false` and call `setOptionsAndConnect()` manually. | -| `dynamicPublicPath` | `boolean` | `false` | Prefix `path` with `__webpack_public_path__` at runtime. | +| `dynamicPublicPath` | `boolean` | `false` | Prefix `path` with `__webpack_public_path__` at runtime. The leading slash of `path` is stripped and no other normalization is applied, so the public path should end with `/`. | ### Programmatic API diff --git a/client-src/index.js b/client-src/index.js index 0cbf56bf1..75bbc608a 100644 --- a/client-src/index.js +++ b/client-src/index.js @@ -113,7 +113,9 @@ function setOverrides(overrides) { } if (overrides.dynamicPublicPath) { - options.path = __webpack_public_path__ + options.path; + // `path` is appended like a filename (no leading slash); the public path + // itself is not normalized. + options.path = __webpack_public_path__ + options.path.replace(/^\//, ""); } setLogLevel(options.logging); diff --git a/test/client.test.js b/test/client.test.js index 763d62436..34e05d24f 100644 --- a/test/client.test.js +++ b/test/client.test.js @@ -702,6 +702,44 @@ 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("appends the SSE path to the public path like webpack appends filenames", () => { + globalThis.__webpack_public_path__ = "/assets/"; + loadClient("?dynamicPublicPath=true"); + expect(EventSourceStub.lastInstance().url).toBe("/assets/__webpack_hmr"); + }); + + it("preserves intentional double slashes inside the public path", () => { + globalThis.__webpack_public_path__ = "https://host//rewritten/"; + loadClient("?dynamicPublicPath=true"); + expect(EventSourceStub.lastInstance().url).toBe( + "https://host//rewritten/__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;