feat: add browser client runtime for HMR - #2323
Conversation
Ports the browser client into `client-src/`, mirroring the layout used
in `webpack-dev-server` (source in `client-src/`, built to `client/`).
The client connects to the SSE endpoint via `EventSource`, parses
query-string options from `__resourceQuery`, dispatches `building`,
`built` and `sync` payloads, applies HMR through `process-update.js`
and renders compile-time errors and warnings through an in-page overlay
(`overlay.js`).
Exposed via the `./client` subpath export so users can wire it as a
webpack entry: `require('webpack-dev-middleware/client')`. The source
is transpiled with a browser-targeted babel override and the resulting
files are shipped under `/client`.
Covers the public client API and key SSE handling paths in jsdom: EventSource connection on default and custom paths, ignored heartbeat messages, dispatch of building/built/sync to subscribers, custom handler for unknown actions, warnings on invalid JSON, EventSource wrapper caching across multiple entries, and timeout-driven reconnect.
Adds tests covering the original webpack-hot-middleware client suite (processUpdate invocations on built/sync, errored/warning behavior, overlay show/hide transitions, the overlayWarnings option, the name filter), while keeping the new coverage for heartbeat handling, invalid JSON warnings, EventSource wrapper caching across entries and timeout-driven reconnects.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## hot-middleware #2323 +/- ##
===============================================
Coverage 92.70% 92.70%
===============================================
Files 3 3
Lines 1001 1001
Branches 311 311
===============================================
Hits 928 928
Misses 65 65
Partials 8 8 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
The ported logic called `module.hot.check`/`apply` with both a callback and a Promise-handling branch to support webpack < 2. In webpack 5 both paths fire, so the callback ran twice, triggering a redundant `module.hot.apply` on every update. Drop the legacy callback path and use the Promise API exclusively, which is the canonical webpack 5 contract and matches our peer dependency.
Mirrors the layout webpack-dev-server uses: a separate `tsconfig.client.json` (`noEmit`, browser-targeted libs, `webpack/module` augmentation) runs over `client-src/` via a new `lint:types-client` script, with a small `client-src/globals.d.ts` declaring `ansi-html-community` and the per-page singletons the client stores on `window`. Refines the JSDoc annotations in `client-src/index.js` and `client-src/process-update.js` so `module.hot`, `window` extensions and the HMR `ApplyOptions` type-check cleanly.
Adds a 'Hot Module Replacement client' section explaining how to wire `webpack-dev-middleware/client` as a webpack entry, the query-string options that the runtime understands, and the programmatic `subscribe` / `subscribeAll` / `useCustomOverlay` / `setOptionsAndConnect` exports.
Switches the client-src lint config to the dedicated preset `eslint-config-webpack` ships for browser-targeted CommonJS code, the same family of preset webpack-dev-server uses for its own client. Brings the per-directory rule overrides down to two: `no-console` (legitimately used for HMR status messages) and `no-use-before-define` (relaxed for hoisted function declarations). Adjusts the source to satisfy the rest of the preset directly: adds `use strict` headers, fills in JSDoc for every function, renames `EventSourceWrapper` to `createEventSourceWrapper` (per `new-cap`), names the anonymous module exports, and reorders `performReload` before `handleError` so it is declared before use.
Wraps `webpack/lib/logging/runtime` in a small `utils/log.js` module that exposes a level-based logger registered under the `webpack-dev-middleware` name (matching the infrastructure logger the server side already uses). Replaces every `console.log`/ `console.warn` call in the client and HMR update path with the equivalent `log.info`/`log.warn`/`log.error` calls so output is prefixed and gated by a single `logging` level. User-facing API: - `logging` query-string option accepts `none|error|warn|info|log|verbose` - The previous `log`, `warn`, `noInfo` and `quiet` flags are dropped in favour of `logging` Other cleanups enabled by this: - Drop the `no-console: off` exception from the client-src ESLint config - Update README's client option table accordingly - Add tests covering the new `logging` levels and the logger prefix
Replaces regex-based `some(([msg]) => /.../).toBe(true)` checks on the mocked console with `toMatchSnapshot()` over `mock.calls`. The snapshots capture the exact log lines including the `[webpack-dev-middleware]` prefix and per-call argument count, so any change to the log format surfaces in the test output instead of silently passing. Adds explicit assertions to the existing error / warning flow tests so `console.error` / `console.warn` mocks are not only silenced but also verified to be called with the expected output.
`eslint-config-webpack@4.9.6` still ships `browser-outdated-recommended-commonjs` with `configs["javascript/es5"]` and no parser override, so `const` is rejected. The module variant of the same preset patches this upstream — we replicate the patch locally until the commonjs variant does the same.
* feat: add browser client runtime for HMR
Ports the browser client into `client-src/`, mirroring the layout used
in `webpack-dev-server` (source in `client-src/`, built to `client/`).
The client connects to the SSE endpoint via `EventSource`, parses
query-string options from `__resourceQuery`, dispatches `building`,
`built` and `sync` payloads, applies HMR through `process-update.js`
and renders compile-time errors and warnings through an in-page overlay
(`overlay.js`).
Exposed via the `./client` subpath export so users can wire it as a
webpack entry: `require('webpack-dev-middleware/client')`. The source
is transpiled with a browser-targeted babel override and the resulting
files are shipped under `/client`.
* test: add browser client runtime tests
Covers the public client API and key SSE handling paths in jsdom:
EventSource connection on default and custom paths, ignored heartbeat
messages, dispatch of building/built/sync to subscribers, custom
handler for unknown actions, warnings on invalid JSON, EventSource
wrapper caching across multiple entries, and timeout-driven reconnect.
* test: expand browser client coverage to mirror webpack-hot-middleware
Adds tests covering the original webpack-hot-middleware client suite
(processUpdate invocations on built/sync, errored/warning behavior,
overlay show/hide transitions, the overlayWarnings option, the name
filter), while keeping the new coverage for heartbeat handling,
invalid JSON warnings, EventSource wrapper caching across entries and
timeout-driven reconnects.
* ci: run on push and PRs against the hot-middleware umbrella branch
* refactor(client): switch process-update to promise-only HMR API
The ported logic called `module.hot.check`/`apply` with both a callback
and a Promise-handling branch to support webpack < 2. In webpack 5 both
paths fire, so the callback ran twice, triggering a redundant
`module.hot.apply` on every update.
Drop the legacy callback path and use the Promise API exclusively, which
is the canonical webpack 5 contract and matches our peer dependency.
* chore(client): type-check client-src with a dedicated tsconfig
Mirrors the layout webpack-dev-server uses: a separate
`tsconfig.client.json` (`noEmit`, browser-targeted libs,
`webpack/module` augmentation) runs over `client-src/` via a new
`lint:types-client` script, with a small `client-src/globals.d.ts`
declaring `ansi-html-community` and the per-page singletons the client
stores on `window`.
Refines the JSDoc annotations in `client-src/index.js` and
`client-src/process-update.js` so `module.hot`, `window` extensions
and the HMR `ApplyOptions` type-check cleanly.
* docs: document the browser client runtime in README
Adds a 'Hot Module Replacement client' section explaining how to wire
`webpack-dev-middleware/client` as a webpack entry, the query-string
options that the runtime understands, and the programmatic
`subscribe` / `subscribeAll` / `useCustomOverlay` /
`setOptionsAndConnect` exports.
* chore(client): adopt browser-outdated-recommended-commonjs eslint preset
Switches the client-src lint config to the dedicated preset
`eslint-config-webpack` ships for browser-targeted CommonJS code, the
same family of preset webpack-dev-server uses for its own client.
Brings the per-directory rule overrides down to two:
`no-console` (legitimately used for HMR status messages) and
`no-use-before-define` (relaxed for hoisted function declarations).
Adjusts the source to satisfy the rest of the preset directly: adds
`use strict` headers, fills in JSDoc for every function, renames
`EventSourceWrapper` to `createEventSourceWrapper` (per `new-cap`),
names the anonymous module exports, and reorders `performReload`
before `handleError` so it is declared before use.
* refactor(client): route logging through webpack's runtime logger
Wraps `webpack/lib/logging/runtime` in a small `utils/log.js` module
that exposes a level-based logger registered under the
`webpack-dev-middleware` name (matching the infrastructure logger the
server side already uses). Replaces every `console.log`/
`console.warn` call in the client and HMR update path with the
equivalent `log.info`/`log.warn`/`log.error` calls so output is
prefixed and gated by a single `logging` level.
User-facing API:
- `logging` query-string option accepts `none|error|warn|info|log|verbose`
- The previous `log`, `warn`, `noInfo` and `quiet` flags are dropped
in favour of `logging`
Other cleanups enabled by this:
- Drop the `no-console: off` exception from the client-src ESLint config
- Update README's client option table accordingly
- Add tests covering the new `logging` levels and the logger prefix
* test(client): use snapshots for logger output assertions
Replaces regex-based `some(([msg]) => /.../).toBe(true)` checks on the
mocked console with `toMatchSnapshot()` over `mock.calls`. The
snapshots capture the exact log lines including the
`[webpack-dev-middleware]` prefix and per-call argument count, so any
change to the log format surfaces in the test output instead of silently
passing.
Adds explicit assertions to the existing error / warning flow tests so
`console.error` / `console.warn` mocks are not only silenced but also
verified to be called with the expected output.
* chore: document why client-src needs the ecmaVersion override
`eslint-config-webpack@4.9.6` still ships `browser-outdated-recommended-commonjs`
with `configs["javascript/es5"]` and no parser override, so `const`
is rejected. The module variant of the same preset patches this
upstream — we replicate the patch locally until the commonjs variant
does the same.
* refactor(client): migrate to ES modules and update Babel configuration
* fixup!
|
@bjohansebas Please don't merge such things in future without approving, at least 1 approve, I don't make it strict to be more flexibility, but it doesn't mean we should ignore it, I don't merge then because we need to change to architecture problems on webpack and dev server side |
|
this isn't merged into |
|
Yeah, I see it, just for future, with other branches will be good to make the right architecture too, but it was already merged, so we will work with branch together, I don't like it because it makes a big diff and often unreadable |
* feat: add browser client runtime for HMR
Ports the browser client into `client-src/`, mirroring the layout used
in `webpack-dev-server` (source in `client-src/`, built to `client/`).
The client connects to the SSE endpoint via `EventSource`, parses
query-string options from `__resourceQuery`, dispatches `building`,
`built` and `sync` payloads, applies HMR through `process-update.js`
and renders compile-time errors and warnings through an in-page overlay
(`overlay.js`).
Exposed via the `./client` subpath export so users can wire it as a
webpack entry: `require('webpack-dev-middleware/client')`. The source
is transpiled with a browser-targeted babel override and the resulting
files are shipped under `/client`.
* test: add browser client runtime tests
Covers the public client API and key SSE handling paths in jsdom:
EventSource connection on default and custom paths, ignored heartbeat
messages, dispatch of building/built/sync to subscribers, custom
handler for unknown actions, warnings on invalid JSON, EventSource
wrapper caching across multiple entries, and timeout-driven reconnect.
* test: expand browser client coverage to mirror webpack-hot-middleware
Adds tests covering the original webpack-hot-middleware client suite
(processUpdate invocations on built/sync, errored/warning behavior,
overlay show/hide transitions, the overlayWarnings option, the name
filter), while keeping the new coverage for heartbeat handling,
invalid JSON warnings, EventSource wrapper caching across entries and
timeout-driven reconnects.
* ci: run on push and PRs against the hot-middleware umbrella branch
* refactor(client): switch process-update to promise-only HMR API
The ported logic called `module.hot.check`/`apply` with both a callback
and a Promise-handling branch to support webpack < 2. In webpack 5 both
paths fire, so the callback ran twice, triggering a redundant
`module.hot.apply` on every update.
Drop the legacy callback path and use the Promise API exclusively, which
is the canonical webpack 5 contract and matches our peer dependency.
* chore(client): type-check client-src with a dedicated tsconfig
Mirrors the layout webpack-dev-server uses: a separate
`tsconfig.client.json` (`noEmit`, browser-targeted libs,
`webpack/module` augmentation) runs over `client-src/` via a new
`lint:types-client` script, with a small `client-src/globals.d.ts`
declaring `ansi-html-community` and the per-page singletons the client
stores on `window`.
Refines the JSDoc annotations in `client-src/index.js` and
`client-src/process-update.js` so `module.hot`, `window` extensions
and the HMR `ApplyOptions` type-check cleanly.
* docs: document the browser client runtime in README
Adds a 'Hot Module Replacement client' section explaining how to wire
`webpack-dev-middleware/client` as a webpack entry, the query-string
options that the runtime understands, and the programmatic
`subscribe` / `subscribeAll` / `useCustomOverlay` /
`setOptionsAndConnect` exports.
* chore(client): adopt browser-outdated-recommended-commonjs eslint preset
Switches the client-src lint config to the dedicated preset
`eslint-config-webpack` ships for browser-targeted CommonJS code, the
same family of preset webpack-dev-server uses for its own client.
Brings the per-directory rule overrides down to two:
`no-console` (legitimately used for HMR status messages) and
`no-use-before-define` (relaxed for hoisted function declarations).
Adjusts the source to satisfy the rest of the preset directly: adds
`use strict` headers, fills in JSDoc for every function, renames
`EventSourceWrapper` to `createEventSourceWrapper` (per `new-cap`),
names the anonymous module exports, and reorders `performReload`
before `handleError` so it is declared before use.
* refactor(client): route logging through webpack's runtime logger
Wraps `webpack/lib/logging/runtime` in a small `utils/log.js` module
that exposes a level-based logger registered under the
`webpack-dev-middleware` name (matching the infrastructure logger the
server side already uses). Replaces every `console.log`/
`console.warn` call in the client and HMR update path with the
equivalent `log.info`/`log.warn`/`log.error` calls so output is
prefixed and gated by a single `logging` level.
User-facing API:
- `logging` query-string option accepts `none|error|warn|info|log|verbose`
- The previous `log`, `warn`, `noInfo` and `quiet` flags are dropped
in favour of `logging`
Other cleanups enabled by this:
- Drop the `no-console: off` exception from the client-src ESLint config
- Update README's client option table accordingly
- Add tests covering the new `logging` levels and the logger prefix
* test(client): use snapshots for logger output assertions
Replaces regex-based `some(([msg]) => /.../).toBe(true)` checks on the
mocked console with `toMatchSnapshot()` over `mock.calls`. The
snapshots capture the exact log lines including the
`[webpack-dev-middleware]` prefix and per-call argument count, so any
change to the log format surfaces in the test output instead of silently
passing.
Adds explicit assertions to the existing error / warning flow tests so
`console.error` / `console.warn` mocks are not only silenced but also
verified to be called with the expected output.
* chore: document why client-src needs the ecmaVersion override
`eslint-config-webpack@4.9.6` still ships `browser-outdated-recommended-commonjs`
with `configs["javascript/es5"]` and no parser override, so `const`
is rejected. The module variant of the same preset patches this
upstream — we replicate the patch locally until the commonjs variant
does the same.
* refactor(client): migrate to ES modules and update Babel configuration
* fixup!
* feat: add browser client runtime for HMR
Ports the browser client into `client-src/`, mirroring the layout used
in `webpack-dev-server` (source in `client-src/`, built to `client/`).
The client connects to the SSE endpoint via `EventSource`, parses
query-string options from `__resourceQuery`, dispatches `building`,
`built` and `sync` payloads, applies HMR through `process-update.js`
and renders compile-time errors and warnings through an in-page overlay
(`overlay.js`).
Exposed via the `./client` subpath export so users can wire it as a
webpack entry: `require('webpack-dev-middleware/client')`. The source
is transpiled with a browser-targeted babel override and the resulting
files are shipped under `/client`.
* test: add browser client runtime tests
Covers the public client API and key SSE handling paths in jsdom:
EventSource connection on default and custom paths, ignored heartbeat
messages, dispatch of building/built/sync to subscribers, custom
handler for unknown actions, warnings on invalid JSON, EventSource
wrapper caching across multiple entries, and timeout-driven reconnect.
* test: expand browser client coverage to mirror webpack-hot-middleware
Adds tests covering the original webpack-hot-middleware client suite
(processUpdate invocations on built/sync, errored/warning behavior,
overlay show/hide transitions, the overlayWarnings option, the name
filter), while keeping the new coverage for heartbeat handling,
invalid JSON warnings, EventSource wrapper caching across entries and
timeout-driven reconnects.
* ci: run on push and PRs against the hot-middleware umbrella branch
* refactor(client): switch process-update to promise-only HMR API
The ported logic called `module.hot.check`/`apply` with both a callback
and a Promise-handling branch to support webpack < 2. In webpack 5 both
paths fire, so the callback ran twice, triggering a redundant
`module.hot.apply` on every update.
Drop the legacy callback path and use the Promise API exclusively, which
is the canonical webpack 5 contract and matches our peer dependency.
* chore(client): type-check client-src with a dedicated tsconfig
Mirrors the layout webpack-dev-server uses: a separate
`tsconfig.client.json` (`noEmit`, browser-targeted libs,
`webpack/module` augmentation) runs over `client-src/` via a new
`lint:types-client` script, with a small `client-src/globals.d.ts`
declaring `ansi-html-community` and the per-page singletons the client
stores on `window`.
Refines the JSDoc annotations in `client-src/index.js` and
`client-src/process-update.js` so `module.hot`, `window` extensions
and the HMR `ApplyOptions` type-check cleanly.
* docs: document the browser client runtime in README
Adds a 'Hot Module Replacement client' section explaining how to wire
`webpack-dev-middleware/client` as a webpack entry, the query-string
options that the runtime understands, and the programmatic
`subscribe` / `subscribeAll` / `useCustomOverlay` /
`setOptionsAndConnect` exports.
* chore(client): adopt browser-outdated-recommended-commonjs eslint preset
Switches the client-src lint config to the dedicated preset
`eslint-config-webpack` ships for browser-targeted CommonJS code, the
same family of preset webpack-dev-server uses for its own client.
Brings the per-directory rule overrides down to two:
`no-console` (legitimately used for HMR status messages) and
`no-use-before-define` (relaxed for hoisted function declarations).
Adjusts the source to satisfy the rest of the preset directly: adds
`use strict` headers, fills in JSDoc for every function, renames
`EventSourceWrapper` to `createEventSourceWrapper` (per `new-cap`),
names the anonymous module exports, and reorders `performReload`
before `handleError` so it is declared before use.
* refactor(client): route logging through webpack's runtime logger
Wraps `webpack/lib/logging/runtime` in a small `utils/log.js` module
that exposes a level-based logger registered under the
`webpack-dev-middleware` name (matching the infrastructure logger the
server side already uses). Replaces every `console.log`/
`console.warn` call in the client and HMR update path with the
equivalent `log.info`/`log.warn`/`log.error` calls so output is
prefixed and gated by a single `logging` level.
User-facing API:
- `logging` query-string option accepts `none|error|warn|info|log|verbose`
- The previous `log`, `warn`, `noInfo` and `quiet` flags are dropped
in favour of `logging`
Other cleanups enabled by this:
- Drop the `no-console: off` exception from the client-src ESLint config
- Update README's client option table accordingly
- Add tests covering the new `logging` levels and the logger prefix
* test(client): use snapshots for logger output assertions
Replaces regex-based `some(([msg]) => /.../).toBe(true)` checks on the
mocked console with `toMatchSnapshot()` over `mock.calls`. The
snapshots capture the exact log lines including the
`[webpack-dev-middleware]` prefix and per-call argument count, so any
change to the log format surfaces in the test output instead of silently
passing.
Adds explicit assertions to the existing error / warning flow tests so
`console.error` / `console.warn` mocks are not only silenced but also
verified to be called with the expected output.
* chore: document why client-src needs the ecmaVersion override
`eslint-config-webpack@4.9.6` still ships `browser-outdated-recommended-commonjs`
with `configs["javascript/es5"]` and no parser override, so `const`
is rejected. The module variant of the same preset patches this
upstream — we replicate the patch locally until the commonjs variant
does the same.
* refactor(client): migrate to ES modules and update Babel configuration
* fixup!
* feat: add browser client runtime for HMR
Ports the browser client into `client-src/`, mirroring the layout used
in `webpack-dev-server` (source in `client-src/`, built to `client/`).
The client connects to the SSE endpoint via `EventSource`, parses
query-string options from `__resourceQuery`, dispatches `building`,
`built` and `sync` payloads, applies HMR through `process-update.js`
and renders compile-time errors and warnings through an in-page overlay
(`overlay.js`).
Exposed via the `./client` subpath export so users can wire it as a
webpack entry: `require('webpack-dev-middleware/client')`. The source
is transpiled with a browser-targeted babel override and the resulting
files are shipped under `/client`.
* test: add browser client runtime tests
Covers the public client API and key SSE handling paths in jsdom:
EventSource connection on default and custom paths, ignored heartbeat
messages, dispatch of building/built/sync to subscribers, custom
handler for unknown actions, warnings on invalid JSON, EventSource
wrapper caching across multiple entries, and timeout-driven reconnect.
* test: expand browser client coverage to mirror webpack-hot-middleware
Adds tests covering the original webpack-hot-middleware client suite
(processUpdate invocations on built/sync, errored/warning behavior,
overlay show/hide transitions, the overlayWarnings option, the name
filter), while keeping the new coverage for heartbeat handling,
invalid JSON warnings, EventSource wrapper caching across entries and
timeout-driven reconnects.
* ci: run on push and PRs against the hot-middleware umbrella branch
* refactor(client): switch process-update to promise-only HMR API
The ported logic called `module.hot.check`/`apply` with both a callback
and a Promise-handling branch to support webpack < 2. In webpack 5 both
paths fire, so the callback ran twice, triggering a redundant
`module.hot.apply` on every update.
Drop the legacy callback path and use the Promise API exclusively, which
is the canonical webpack 5 contract and matches our peer dependency.
* chore(client): type-check client-src with a dedicated tsconfig
Mirrors the layout webpack-dev-server uses: a separate
`tsconfig.client.json` (`noEmit`, browser-targeted libs,
`webpack/module` augmentation) runs over `client-src/` via a new
`lint:types-client` script, with a small `client-src/globals.d.ts`
declaring `ansi-html-community` and the per-page singletons the client
stores on `window`.
Refines the JSDoc annotations in `client-src/index.js` and
`client-src/process-update.js` so `module.hot`, `window` extensions
and the HMR `ApplyOptions` type-check cleanly.
* docs: document the browser client runtime in README
Adds a 'Hot Module Replacement client' section explaining how to wire
`webpack-dev-middleware/client` as a webpack entry, the query-string
options that the runtime understands, and the programmatic
`subscribe` / `subscribeAll` / `useCustomOverlay` /
`setOptionsAndConnect` exports.
* chore(client): adopt browser-outdated-recommended-commonjs eslint preset
Switches the client-src lint config to the dedicated preset
`eslint-config-webpack` ships for browser-targeted CommonJS code, the
same family of preset webpack-dev-server uses for its own client.
Brings the per-directory rule overrides down to two:
`no-console` (legitimately used for HMR status messages) and
`no-use-before-define` (relaxed for hoisted function declarations).
Adjusts the source to satisfy the rest of the preset directly: adds
`use strict` headers, fills in JSDoc for every function, renames
`EventSourceWrapper` to `createEventSourceWrapper` (per `new-cap`),
names the anonymous module exports, and reorders `performReload`
before `handleError` so it is declared before use.
* refactor(client): route logging through webpack's runtime logger
Wraps `webpack/lib/logging/runtime` in a small `utils/log.js` module
that exposes a level-based logger registered under the
`webpack-dev-middleware` name (matching the infrastructure logger the
server side already uses). Replaces every `console.log`/
`console.warn` call in the client and HMR update path with the
equivalent `log.info`/`log.warn`/`log.error` calls so output is
prefixed and gated by a single `logging` level.
User-facing API:
- `logging` query-string option accepts `none|error|warn|info|log|verbose`
- The previous `log`, `warn`, `noInfo` and `quiet` flags are dropped
in favour of `logging`
Other cleanups enabled by this:
- Drop the `no-console: off` exception from the client-src ESLint config
- Update README's client option table accordingly
- Add tests covering the new `logging` levels and the logger prefix
* test(client): use snapshots for logger output assertions
Replaces regex-based `some(([msg]) => /.../).toBe(true)` checks on the
mocked console with `toMatchSnapshot()` over `mock.calls`. The
snapshots capture the exact log lines including the
`[webpack-dev-middleware]` prefix and per-call argument count, so any
change to the log format surfaces in the test output instead of silently
passing.
Adds explicit assertions to the existing error / warning flow tests so
`console.error` / `console.warn` mocks are not only silenced but also
verified to be called with the expected output.
* chore: document why client-src needs the ecmaVersion override
`eslint-config-webpack@4.9.6` still ships `browser-outdated-recommended-commonjs`
with `configs["javascript/es5"]` and no parser override, so `const`
is rejected. The module variant of the same preset patches this
upstream — we replicate the patch locally until the commonjs variant
does the same.
* refactor(client): migrate to ES modules and update Babel configuration
* fixup!
* feat: add browser client runtime for HMR
Ports the browser client into `client-src/`, mirroring the layout used
in `webpack-dev-server` (source in `client-src/`, built to `client/`).
The client connects to the SSE endpoint via `EventSource`, parses
query-string options from `__resourceQuery`, dispatches `building`,
`built` and `sync` payloads, applies HMR through `process-update.js`
and renders compile-time errors and warnings through an in-page overlay
(`overlay.js`).
Exposed via the `./client` subpath export so users can wire it as a
webpack entry: `require('webpack-dev-middleware/client')`. The source
is transpiled with a browser-targeted babel override and the resulting
files are shipped under `/client`.
* test: add browser client runtime tests
Covers the public client API and key SSE handling paths in jsdom:
EventSource connection on default and custom paths, ignored heartbeat
messages, dispatch of building/built/sync to subscribers, custom
handler for unknown actions, warnings on invalid JSON, EventSource
wrapper caching across multiple entries, and timeout-driven reconnect.
* test: expand browser client coverage to mirror webpack-hot-middleware
Adds tests covering the original webpack-hot-middleware client suite
(processUpdate invocations on built/sync, errored/warning behavior,
overlay show/hide transitions, the overlayWarnings option, the name
filter), while keeping the new coverage for heartbeat handling,
invalid JSON warnings, EventSource wrapper caching across entries and
timeout-driven reconnects.
* ci: run on push and PRs against the hot-middleware umbrella branch
* refactor(client): switch process-update to promise-only HMR API
The ported logic called `module.hot.check`/`apply` with both a callback
and a Promise-handling branch to support webpack < 2. In webpack 5 both
paths fire, so the callback ran twice, triggering a redundant
`module.hot.apply` on every update.
Drop the legacy callback path and use the Promise API exclusively, which
is the canonical webpack 5 contract and matches our peer dependency.
* chore(client): type-check client-src with a dedicated tsconfig
Mirrors the layout webpack-dev-server uses: a separate
`tsconfig.client.json` (`noEmit`, browser-targeted libs,
`webpack/module` augmentation) runs over `client-src/` via a new
`lint:types-client` script, with a small `client-src/globals.d.ts`
declaring `ansi-html-community` and the per-page singletons the client
stores on `window`.
Refines the JSDoc annotations in `client-src/index.js` and
`client-src/process-update.js` so `module.hot`, `window` extensions
and the HMR `ApplyOptions` type-check cleanly.
* docs: document the browser client runtime in README
Adds a 'Hot Module Replacement client' section explaining how to wire
`webpack-dev-middleware/client` as a webpack entry, the query-string
options that the runtime understands, and the programmatic
`subscribe` / `subscribeAll` / `useCustomOverlay` /
`setOptionsAndConnect` exports.
* chore(client): adopt browser-outdated-recommended-commonjs eslint preset
Switches the client-src lint config to the dedicated preset
`eslint-config-webpack` ships for browser-targeted CommonJS code, the
same family of preset webpack-dev-server uses for its own client.
Brings the per-directory rule overrides down to two:
`no-console` (legitimately used for HMR status messages) and
`no-use-before-define` (relaxed for hoisted function declarations).
Adjusts the source to satisfy the rest of the preset directly: adds
`use strict` headers, fills in JSDoc for every function, renames
`EventSourceWrapper` to `createEventSourceWrapper` (per `new-cap`),
names the anonymous module exports, and reorders `performReload`
before `handleError` so it is declared before use.
* refactor(client): route logging through webpack's runtime logger
Wraps `webpack/lib/logging/runtime` in a small `utils/log.js` module
that exposes a level-based logger registered under the
`webpack-dev-middleware` name (matching the infrastructure logger the
server side already uses). Replaces every `console.log`/
`console.warn` call in the client and HMR update path with the
equivalent `log.info`/`log.warn`/`log.error` calls so output is
prefixed and gated by a single `logging` level.
User-facing API:
- `logging` query-string option accepts `none|error|warn|info|log|verbose`
- The previous `log`, `warn`, `noInfo` and `quiet` flags are dropped
in favour of `logging`
Other cleanups enabled by this:
- Drop the `no-console: off` exception from the client-src ESLint config
- Update README's client option table accordingly
- Add tests covering the new `logging` levels and the logger prefix
* test(client): use snapshots for logger output assertions
Replaces regex-based `some(([msg]) => /.../).toBe(true)` checks on the
mocked console with `toMatchSnapshot()` over `mock.calls`. The
snapshots capture the exact log lines including the
`[webpack-dev-middleware]` prefix and per-call argument count, so any
change to the log format surfaces in the test output instead of silently
passing.
Adds explicit assertions to the existing error / warning flow tests so
`console.error` / `console.warn` mocks are not only silenced but also
verified to be called with the expected output.
* chore: document why client-src needs the ecmaVersion override
`eslint-config-webpack@4.9.6` still ships `browser-outdated-recommended-commonjs`
with `configs["javascript/es5"]` and no parser override, so `const`
is rejected. The module variant of the same preset patches this
upstream — we replicate the patch locally until the commonjs variant
does the same.
* refactor(client): migrate to ES modules and update Babel configuration
* fixup!
* feat: add browser client runtime for HMR
Ports the browser client into `client-src/`, mirroring the layout used
in `webpack-dev-server` (source in `client-src/`, built to `client/`).
The client connects to the SSE endpoint via `EventSource`, parses
query-string options from `__resourceQuery`, dispatches `building`,
`built` and `sync` payloads, applies HMR through `process-update.js`
and renders compile-time errors and warnings through an in-page overlay
(`overlay.js`).
Exposed via the `./client` subpath export so users can wire it as a
webpack entry: `require('webpack-dev-middleware/client')`. The source
is transpiled with a browser-targeted babel override and the resulting
files are shipped under `/client`.
* test: add browser client runtime tests
Covers the public client API and key SSE handling paths in jsdom:
EventSource connection on default and custom paths, ignored heartbeat
messages, dispatch of building/built/sync to subscribers, custom
handler for unknown actions, warnings on invalid JSON, EventSource
wrapper caching across multiple entries, and timeout-driven reconnect.
* test: expand browser client coverage to mirror webpack-hot-middleware
Adds tests covering the original webpack-hot-middleware client suite
(processUpdate invocations on built/sync, errored/warning behavior,
overlay show/hide transitions, the overlayWarnings option, the name
filter), while keeping the new coverage for heartbeat handling,
invalid JSON warnings, EventSource wrapper caching across entries and
timeout-driven reconnects.
* ci: run on push and PRs against the hot-middleware umbrella branch
* refactor(client): switch process-update to promise-only HMR API
The ported logic called `module.hot.check`/`apply` with both a callback
and a Promise-handling branch to support webpack < 2. In webpack 5 both
paths fire, so the callback ran twice, triggering a redundant
`module.hot.apply` on every update.
Drop the legacy callback path and use the Promise API exclusively, which
is the canonical webpack 5 contract and matches our peer dependency.
* chore(client): type-check client-src with a dedicated tsconfig
Mirrors the layout webpack-dev-server uses: a separate
`tsconfig.client.json` (`noEmit`, browser-targeted libs,
`webpack/module` augmentation) runs over `client-src/` via a new
`lint:types-client` script, with a small `client-src/globals.d.ts`
declaring `ansi-html-community` and the per-page singletons the client
stores on `window`.
Refines the JSDoc annotations in `client-src/index.js` and
`client-src/process-update.js` so `module.hot`, `window` extensions
and the HMR `ApplyOptions` type-check cleanly.
* docs: document the browser client runtime in README
Adds a 'Hot Module Replacement client' section explaining how to wire
`webpack-dev-middleware/client` as a webpack entry, the query-string
options that the runtime understands, and the programmatic
`subscribe` / `subscribeAll` / `useCustomOverlay` /
`setOptionsAndConnect` exports.
* chore(client): adopt browser-outdated-recommended-commonjs eslint preset
Switches the client-src lint config to the dedicated preset
`eslint-config-webpack` ships for browser-targeted CommonJS code, the
same family of preset webpack-dev-server uses for its own client.
Brings the per-directory rule overrides down to two:
`no-console` (legitimately used for HMR status messages) and
`no-use-before-define` (relaxed for hoisted function declarations).
Adjusts the source to satisfy the rest of the preset directly: adds
`use strict` headers, fills in JSDoc for every function, renames
`EventSourceWrapper` to `createEventSourceWrapper` (per `new-cap`),
names the anonymous module exports, and reorders `performReload`
before `handleError` so it is declared before use.
* refactor(client): route logging through webpack's runtime logger
Wraps `webpack/lib/logging/runtime` in a small `utils/log.js` module
that exposes a level-based logger registered under the
`webpack-dev-middleware` name (matching the infrastructure logger the
server side already uses). Replaces every `console.log`/
`console.warn` call in the client and HMR update path with the
equivalent `log.info`/`log.warn`/`log.error` calls so output is
prefixed and gated by a single `logging` level.
User-facing API:
- `logging` query-string option accepts `none|error|warn|info|log|verbose`
- The previous `log`, `warn`, `noInfo` and `quiet` flags are dropped
in favour of `logging`
Other cleanups enabled by this:
- Drop the `no-console: off` exception from the client-src ESLint config
- Update README's client option table accordingly
- Add tests covering the new `logging` levels and the logger prefix
* test(client): use snapshots for logger output assertions
Replaces regex-based `some(([msg]) => /.../).toBe(true)` checks on the
mocked console with `toMatchSnapshot()` over `mock.calls`. The
snapshots capture the exact log lines including the
`[webpack-dev-middleware]` prefix and per-call argument count, so any
change to the log format surfaces in the test output instead of silently
passing.
Adds explicit assertions to the existing error / warning flow tests so
`console.error` / `console.warn` mocks are not only silenced but also
verified to be called with the expected output.
* chore: document why client-src needs the ecmaVersion override
`eslint-config-webpack@4.9.6` still ships `browser-outdated-recommended-commonjs`
with `configs["javascript/es5"]` and no parser override, so `const`
is rejected. The module variant of the same preset patches this
upstream — we replicate the patch locally until the commonjs variant
does the same.
* refactor(client): migrate to ES modules and update Babel configuration
* fixup!
* feat: add hot option for hot module replacement * feat: add browser client runtime for HMR (#2323) * feat: add browser client runtime for HMR Ports the browser client into `client-src/`, mirroring the layout used in `webpack-dev-server` (source in `client-src/`, built to `client/`). The client connects to the SSE endpoint via `EventSource`, parses query-string options from `__resourceQuery`, dispatches `building`, `built` and `sync` payloads, applies HMR through `process-update.js` and renders compile-time errors and warnings through an in-page overlay (`overlay.js`). Exposed via the `./client` subpath export so users can wire it as a webpack entry: `require('webpack-dev-middleware/client')`. The source is transpiled with a browser-targeted babel override and the resulting files are shipped under `/client`. * test: add browser client runtime tests Covers the public client API and key SSE handling paths in jsdom: EventSource connection on default and custom paths, ignored heartbeat messages, dispatch of building/built/sync to subscribers, custom handler for unknown actions, warnings on invalid JSON, EventSource wrapper caching across multiple entries, and timeout-driven reconnect. * test: expand browser client coverage to mirror webpack-hot-middleware Adds tests covering the original webpack-hot-middleware client suite (processUpdate invocations on built/sync, errored/warning behavior, overlay show/hide transitions, the overlayWarnings option, the name filter), while keeping the new coverage for heartbeat handling, invalid JSON warnings, EventSource wrapper caching across entries and timeout-driven reconnects. * ci: run on push and PRs against the hot-middleware umbrella branch * refactor(client): switch process-update to promise-only HMR API The ported logic called `module.hot.check`/`apply` with both a callback and a Promise-handling branch to support webpack < 2. In webpack 5 both paths fire, so the callback ran twice, triggering a redundant `module.hot.apply` on every update. Drop the legacy callback path and use the Promise API exclusively, which is the canonical webpack 5 contract and matches our peer dependency. * chore(client): type-check client-src with a dedicated tsconfig Mirrors the layout webpack-dev-server uses: a separate `tsconfig.client.json` (`noEmit`, browser-targeted libs, `webpack/module` augmentation) runs over `client-src/` via a new `lint:types-client` script, with a small `client-src/globals.d.ts` declaring `ansi-html-community` and the per-page singletons the client stores on `window`. Refines the JSDoc annotations in `client-src/index.js` and `client-src/process-update.js` so `module.hot`, `window` extensions and the HMR `ApplyOptions` type-check cleanly. * docs: document the browser client runtime in README Adds a 'Hot Module Replacement client' section explaining how to wire `webpack-dev-middleware/client` as a webpack entry, the query-string options that the runtime understands, and the programmatic `subscribe` / `subscribeAll` / `useCustomOverlay` / `setOptionsAndConnect` exports. * chore(client): adopt browser-outdated-recommended-commonjs eslint preset Switches the client-src lint config to the dedicated preset `eslint-config-webpack` ships for browser-targeted CommonJS code, the same family of preset webpack-dev-server uses for its own client. Brings the per-directory rule overrides down to two: `no-console` (legitimately used for HMR status messages) and `no-use-before-define` (relaxed for hoisted function declarations). Adjusts the source to satisfy the rest of the preset directly: adds `use strict` headers, fills in JSDoc for every function, renames `EventSourceWrapper` to `createEventSourceWrapper` (per `new-cap`), names the anonymous module exports, and reorders `performReload` before `handleError` so it is declared before use. * refactor(client): route logging through webpack's runtime logger Wraps `webpack/lib/logging/runtime` in a small `utils/log.js` module that exposes a level-based logger registered under the `webpack-dev-middleware` name (matching the infrastructure logger the server side already uses). Replaces every `console.log`/ `console.warn` call in the client and HMR update path with the equivalent `log.info`/`log.warn`/`log.error` calls so output is prefixed and gated by a single `logging` level. User-facing API: - `logging` query-string option accepts `none|error|warn|info|log|verbose` - The previous `log`, `warn`, `noInfo` and `quiet` flags are dropped in favour of `logging` Other cleanups enabled by this: - Drop the `no-console: off` exception from the client-src ESLint config - Update README's client option table accordingly - Add tests covering the new `logging` levels and the logger prefix * test(client): use snapshots for logger output assertions Replaces regex-based `some(([msg]) => /.../).toBe(true)` checks on the mocked console with `toMatchSnapshot()` over `mock.calls`. The snapshots capture the exact log lines including the `[webpack-dev-middleware]` prefix and per-call argument count, so any change to the log format surfaces in the test output instead of silently passing. Adds explicit assertions to the existing error / warning flow tests so `console.error` / `console.warn` mocks are not only silenced but also verified to be called with the expected output. * chore: document why client-src needs the ecmaVersion override `eslint-config-webpack@4.9.6` still ships `browser-outdated-recommended-commonjs` with `configs["javascript/es5"]` and no parser override, so `const` is rejected. The module variant of the same preset patches this upstream — we replicate the patch locally until the commonjs variant does the same. * refactor(client): migrate to ES modules and update Babel configuration * fixup! * feat: implement hot module replacement middleware (#2321) * fix(test): correct output path typo in webpack.array.warning fixture The first compiler entry used `../../outputs/...` which escaped the test directory and wrote artifacts to the repository root, outside of the `/test/outputs` paths covered by `.gitignore` and `.prettierignore`. * feat: implement hot module replacement middleware Adds a `hot: true | { path, heartbeat, log, statsOptions }` option that turns the dev middleware into a Server-Sent Events endpoint publishing `building`, `built` and `sync` payloads from the webpack compiler. The hot endpoint defaults to `/__webpack_hmr` and is served by the existing middleware - no separate `app.use()` call is required. `close()` tears down clients and the heartbeat timer. * test: add tests for hot middleware Covers schema validation of the `hot` option (success and failure cases with snapshots), unit tests for `pathMatch`, `formatErrors`, `buildModuleMap` and `createEventStream`, and integration tests that verify SSE headers, the default and custom hot paths, MultiCompiler support, `close()` teardown, and the `log` option (custom function and `log: false`). * test: add unit and integration tests for hot middleware functionality * feat: enhance honoWrapper to support Web ReadableStream for hot middleware responses * feat: add TypeScript definitions for hot module replacement functionality * test(hot): cover publish, sync-on-connect, headers and close behavior Ports the remaining unit-level cases from webpack-hot-middleware that were not already covered by the framework matrix in middleware.test.js: - the public `publish()` API broadcasts custom payloads - a client connecting after a build receives a `sync` event initialised from the last stats - HTTP/1 clients get `Connection: keep-alive`, HTTP/2 clients do not - when `stats.name` is empty the published payload falls back to `compilation.name` - a single broadcast reaches every attached client - after `close()` further compiler events do not produce writes * docs: document the hot option in README * docs: list the hot option in the README options table * refactor: replace EXPECTED_ANY with specific types in hot module definitions * docs: update README to clarify default stats options for SSE payload * refactor: remove log option from hot middleware and update related documentation * docs: update default value for hot option in README to false * docs: add hot module replacement example with Express server and client setup * feat: enhance error overlay with close button and improved styling * feat: enable reload option for HMR by default and update tests * feat: add SSE helper functions and tests for event streaming * refactor(client): inline ansi stripping and drop the strip-ansi dependency (#2350) The client runtime runs in the browser, so Node's util.stripVTControlCharacters is not an option. Inline the ansi-regex pattern (verified against strip-ansi@6 output) in a small util instead of shipping the dependency. Ref webpack/webpack-hot-middleware#465 Ref webpack/webpack-hot-middleware#474 * refactor(hot): drop the module map from the SSE payload (#2349) webpack-dev-server does not send module names over the wire: the HMR runtime logs module ids on apply. Align the SSE payload with that (name, action, time, hash, errors, warnings) instead of serializing a module id → name map that was only used for log cosmetics and was empty with the default stats options anyway. Ref webpack/webpack-hot-middleware#452 Ref webpack/webpack-hot-middleware#306 * feat(client): add disconnect() to close the SSE connection (#2351) * feat(client): add disconnect() to close the SSE connection Expose a way to close the EventSource for the current path and stop the reconnection watchdog (e.g. before tearing the page down). The cached wrapper is dropped so a later setOptionsAndConnect() opens a fresh connection. Ref webpack/webpack-hot-middleware#367 * fixup! * feat(hot): include the changed file in the building event (#2352) The compiler's invalid hook reports which file invalidated the compilation. Forward it as an optional `file` field on the `building` payload and log it in the client, so users can see what triggered a rebuild. Ref webpack/webpack-hot-middleware#173 * feat(client): bring the error overlay to parity with webpack-dev-server (#2353) * feat(client): bring the error overlay to parity with webpack-dev-server - Render inside an about:blank iframe and style exclusively through the CSSOM so the overlay works under a strict style-src CSP; inline styles from ansi-html are re-applied via style.cssText. - Support Trusted Types: innerHTML writes go through a policy (configurable via overlayTrustedTypesPolicyName). - Capture uncaught runtime errors and unhandled rejections in the overlay (overlayRuntimeErrors, default true), with the same React error boundary heuristic as dev-server. - Inline the HTML entity encoder and drop the html-entities dependency. - Expose the overlay as a standalone subpath export (webpack-dev-middleware/client/overlay) so webpack-dev-server can reuse it. Ref webpack/webpack-hot-middleware#457 * feat(client): support opening file references in the editor When `overlayOpenEditorEndpoint` is set, file chips in the overlay become clickable and issue GET <endpoint>?fileName=<file:line:column> — the same contract as webpack-dev-server's open-editor route. The endpoint implementation is left to the server integration, so no launch-editor dependency is added. * feat(client): bring the error overlay to parity with webpack-dev-server - Render inside an about:blank iframe and style exclusively through the CSSOM so the overlay works under a strict style-src CSP; inline styles from ansi-html are re-applied via style.cssText. - Support Trusted Types: innerHTML writes go through a policy (configurable via the overlay's trustedTypesPolicyName). - Capture uncaught runtime errors and unhandled rejections in the overlay, with the same React error boundary heuristic as dev-server. - Adopt dev-server's client.overlay option shape: a boolean or a JSON object with errors/warnings/runtimeErrors (booleans or filter functions) and trustedTypesPolicyName, so dev-server configs migrate as-is. Warnings are shown by default and no longer block updates, matching dev-server. - Support opening file references in the editor: when overlayOpenEditorEndpoint is set, file chips issue GET <endpoint>?fileName=<file:line:column> (same contract as dev-server's open-editor route); the endpoint implementation is left to the server integration. - Inline the HTML entity encoder and drop the html-entities dependency. - Expose the overlay as a standalone subpath export (webpack-dev-middleware/client/overlay) so webpack-dev-server can reuse it. Ref webpack/webpack-hot-middleware#457 Ref webpack/webpack-hot-middleware#184 * fix(client): track overlay problems per compilation With a MultiCompiler the client receives one event per bundle, and a successful build from one bundle used to wipe another bundle's still-valid errors from the overlay. Keep the live problems keyed by compilation name, render the union, and only clear the overlay when every compilation is clean. The console de-duplication cache is also keyed per bundle so interleaved payloads do not defeat it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(examples): add a MultiCompiler hot example Keep examples/hot as the minimal single-compiler setup (now with runtime-error buttons and an opt-in DEMO_WARNING build warning), and add examples/hot-multi-compiler: two compilers ("app" and "admin") sharing one middleware instance and a single SSE connection, with per-bundle `?name=` client scoping and recipes for the overlay's per-compilation error tracking. Each multi config sets a distinct output.uniqueName (and prefixed hot-update filenames): with both bundles on one page, a shared webpackHotUpdate global would make each bundle's updates land in the wrong runtime. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(client): nest overlay extensions inside the overlay option Move styles, ansiColors and openEditorEndpoint into the overlay object as webpack-dev-middleware extensions of dev-server's client.overlay shape, replacing the flat overlayStyles/ansiColors/ overlayOpenEditorEndpoint options. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * test(client): cover process-update Isolate the HMR runtime lookup behind a small util so process-update is loadable under jest, and add coverage for the updated-modules console output. * feat(client): collapse the updated-modules list in the console (#2361) The success path now logs a one-line summary with the module count at the default "info" level, and the per-module detail as a collapsed console group visible from the "log" level up (webpack's runtime logger gates groups below that level). The unaccepted-modules warning list stays flat: it is diagnostic output for the failing case. Ref webpack/webpack-hot-middleware#311 * feat(client): paginate the overlay problems (#2359) The overlay now shows one problem at a time, with a header row holding the problem badge and prev/next navigation (colored after the problem type), a page counter and arrow-key navigation. Each new problem set starts at its first page, accumulated runtime errors land on the newest one, and identical re-published sets (e.g. another bundle of a multi-compiler syncing) keep the current page. Enabled by default; disable with the overlay option's `paginate` extension (`?overlay={"paginate":false}`) to render the full list. Also ignore click targets re-rendered away mid-dispatch in the backdrop-dismiss listener, which otherwise closed the overlay when clicking the navigation buttons. * fix(client): avoid double slash when joining dynamicPublicPath (#2348) * fix(client): avoid double slash when joining dynamicPublicPath When output.publicPath ends with a slash (the common case) and the SSE path starts with one, the naive concatenation produced URLs like `https://host//__webpack_hmr`, which never match the server-side pathname check. Ref webpack/webpack-hot-middleware#366 * fix(client): append path to the public path like a filename in dynamicPublicPath The default `path` starts with "/", so the naive concatenation with a public path ending in "/" produced URLs like `https://host//__webpack_hmr`, which never match the server-side pathname check. Strip the leading slash from `path` and concatenate without any other normalization: the public path itself is left untouched (intentional double slashes, e.g. nginx rewrites, are preserved) and is expected to end with "/". Ref webpack/webpack-hot-middleware#366 * feat(hot): add a building indicator with optional compilation progress (#2358) The client shows a small badge (shadow DOM, CSSOM-only styles) while a rebuild is in progress, enabled by default (disable with `?progress=false` on the client entry). When the server enables the new `hot.progress` option, webpack's ProgressPlugin publishes throttled `progress` events over SSE (deduplicated by rounded percent, reset on each new build) and the badge renders a CSP-safe SVG progress ring with the compilation percentage. The palette is shared with the error overlay through a common theme module, and the indicator is exposed as a standalone subpath export (webpack-dev-middleware/client/indicator) so other tooling can reuse it. Both the indicator and the overlay now bail out safely when `document.body` does not exist yet. Ref webpack/webpack-hot-middleware#167 * docs: add HMR notes and troubleshooting section (#2362) * docs: add HMR notes and troubleshooting section Covers the recurring questions from the webpack-hot-middleware backlog: browser connection limits and HTTP/2 (webpack/webpack-hot-middleware#423, webpack/webpack-hot-middleware#298), warning filtering layers (webpack/webpack-hot-middleware#319, webpack/webpack-hot-middleware#228), absolute paths and public paths (webpack/webpack-hot-middleware#281, webpack/webpack-hot-middleware#203, webpack/webpack-hot-middleware#193), and a custom-events recipe (webpack/webpack-hot-middleware#118, webpack/webpack-hot-middleware#226). The client `reload` option now also notes its default differs from webpack-hot-middleware (webpack/webpack-hot-middleware#370). * fixup! * fix(hot): publish sync instead of built for unchanged bundles (#2357) * fix(hot): publish sync instead of built for unchanged bundles With a MultiCompiler, rebuilding one child re-emits done for every child. Clients of the unchanged bundles then try to fetch a hot-update manifest that was never emitted (404 / "Cannot find update", or an unwanted full reload with reload=true). Compare each bundle's hash against the previous build and announce unchanged bundles as `sync` instead of `built`. The first build still publishes `built`, and new clients are still caught up with `sync` — but no longer while a rebuild is in progress. Ref webpack/webpack-hot-middleware#312 * fixup! * fix(client): reload when an accept handler errors during apply (#2360) * fix(client): reload when an accept handler errors during apply An error thrown inside a module.hot.accept handler was logged and ignored, leaving the page running stale code with no recovery. When the `reload` option is enabled (the default), onErrored now falls back to a full page reload, like the other unrecoverable update paths. The HMR runtime and page-reload calls are isolated behind small utils so process-update finally has direct test coverage. A missing HMR runtime is now reported with a single actionable error instead of throwing at bundle evaluation. Ref webpack/webpack-hot-middleware#334 * fixup! * fixup! * fix(hot): scope the catch-up sync and pair bundles by name - A client connecting after a build now receives the catch-up `sync` alone instead of it being broadcast to every connected client, which re-triggered their reporters on each new tab. - `publishBundles` pairs the previous build's bundles by name instead of array index, so a changing set of compilations cannot compare a bundle against a sibling's hash. Unnamed bundles keep the positional pairing. - The client's console dedup cache is cleared per bundle, so a sibling's clean build no longer re-logs another bundle's unchanged problems. * fix(hot): end SSE requests that arrive after close() instance.close() left `context.hot` set while `handle()` silently returned, so a request to the hot path after close received no response, no `next()`, and hung until the socket timed out. Detach the SSE intercept on close so requests fall through to the regular middleware, and answer 404 from `handle()` for requests that raced the intercept. * feat(client): share the building indicator and track builds by source (#2369) The badge state lives in a window singleton (same pattern as the overlay), so a second bundled copy of the module drives the same badge instead of stacking a duplicate, and fields missing from another version's state are filled in place. show() and hide() take an optional source: each concurrent build keeps the badge alive until every source finished, so in a MultiCompiler one bundle's `built` no longer hides the badge while a sibling is still compiling — and with two clients sharing the badge, one client's hide() cannot drop the other's indication. hide() without a source still removes the badge unconditionally. Progress payloads carry no name, so the client attributes them to the most recent `building` event. * feat(client): share the overlay across bundled copies and report by source (#2368) * feat(client): share the overlay across bundled copies and report by source The overlay DOM and problem state now live in a window singleton, so a second bundled copy of the module (e.g. the webpack-dev-server client once it adopts this overlay) renders into the same iframe instead of stacking a duplicate. Fields missing from a state created by an older copy are filled in place, and the Trusted Types policy moves into the shared state too — creating two policies with the same name throws under an enforced CSP. * test(overlay): prevent re-rendering when clearing a source that reported nothing * refactor(cspell): consolidate cspell configuration into .cspell.json and remove cspell.config.json * fix(hot): name building events and repair the client reconnect lifecycle The building payload now carries the name of the compilation that invalidated (tapped per child compiler, since the MultiCompiler hook does not say which one fired), so clients can pair it with the built or sync that follows — without it the building indicator registered every build under "" and could never be hidden for named compilations. On the client, the inactivity watchdog is restarted inside init(), so it survives a reconnect instead of dying with the first clearInterval, and the reconnect timeout handle is now stored and cleared by close(), so disconnect() during the reconnect window no longer resurrects an orphaned, uncloseable connection. All three defects were inherited from webpack-hot-middleware. * fix(hot): harden the SSE handshake and defer the overlay's Escape listener The handshake now ends a response whose headers were already sent instead of crashing on writeHead, and the middleware routes handshake errors to next() — an exception there previously became an unhandled rejection that killed the process. The overlay's Escape listener on the host document is now attached lazily inside ensureOverlay (once per page, through the shared state), matching how webpack-dev-server registers it inside createOverlay, so importing the client in a non-DOM environment (SSR bundle, worker) no longer throws at evaluation time. * fix(hot): align statsOptions and heartbeat validation and pair duplicate-name bundles by occurrence statsOptions now accepts only the object form everywhere: the schema rejected string presets the types allowed, and booleans validated but were silently ignored since toBundles only merges objects over the middleware's base stats options. Schema, JSDoc, and generated types all agree now, so invalid forms fail validation instead of at runtime. heartbeat: 0 was schema-valid but silently replaced with the 10s default by a falsy check — the schema now requires >= 1 and the code uses ?? so the option and its validation tell the same story. * fix(hot): require a leading slash in the hot path and serialize publishes once A path without a leading slash validated but could never match a request, since URL pathnames always start with one — the schema now enforces it. publish() also serializes the payload once per event instead of once per connected client. * fix(hono): set headers in the SSE writeHead shim instead of appending writeHead semantics replace a header an earlier middleware may have left on the response — appending merged both values into one comma-joined header (e.g. "max-age=100, no-cache, no-transform" for Cache-Control, which intermediaries may treat as cacheable). * docs: fix the hot option reference and add a webpack-hot-middleware migration guide The changeset advertised a `log` option that fails validation (it is `progress`), `hot.statsOptions` documented the boolean form the schema no longer accepts, `hot.path` now notes the required leading slash, and the launch-editor sentence that had landed inside the `paginate` parenthetical is back with `openEditorEndpoint`, where it belongs. Also adds a "Migrating from webpack-hot-middleware" section: server and webpack-config before/after, an option mapping table, and the programmatic API equivalents. * docs: make the migration guide's entry snippet valid standalone JS eslint parses the README's fenced code blocks, and the bare `entry: [...]` object fragment failed with "Unexpected token :" — the before/after entries are now wrapped in module.exports. * build: compile the client to ES5 and keep dist on the node target The shared preset-env targets (`esmodules` + node 0.12) also applied to `src`, so `dist` was downlevelled to ES5 with inlined regenerator helpers. Target node 20.9 by default and override only `client-src`, whose output has to be parsable by an old browser. `lint:types-client` now checks the client without node types and against an ES5 `lib`, so a post-ES5 built-in is an error instead of a runtime failure. * fix(package): do not add an exports field yet Adding `exports` hides every path the package does not list (deep imports like `webpack-dev-middleware/dist/...`), which is a breaking change. The `./client` subpath resolves through the directory anyway, so the field is dropped and a TODO records it for the next major. Also drops the now unused `@babel/plugin-transform-runtime` dev dependency. * refactor(client): define everything before it is used and stay on ES5 built-ins Drops the `no-use-before-define: { functions: false }` exception: the client is reordered so every function is defined before its first reference, which also removes the `@ts-expect-error`s that hoisting forced on process-update. Replaces the built-ins an ES5 browser does not have (`Map`, `URLSearchParams`, `Object.values`, `flatMap`, `includes`, `append`, `remove`) and guards `fetch`. A non-numeric `timeout` is now ignored (`NaN` never compares greater, so the watchdog could never report a dead connection) and `dynamicPublicPath=false` no longer behaves like `true`. * fix(hot): never write to a response that already ended The heartbeat and every publish wrote to each registered client, so a response ended between the socket dying and its `close` event threw a write-after-end. Clients are skipped once they end, and a request that was already destroyed when the handshake finished is dropped instead of staying in the client map forever (`close` never fires for it again). * ci: do not add a branch to the workflow triggers * test: keep the js3 array fixture output under test/outputs Its siblings write to `../outputs/one-error-one-warning-one-success`; `js3` wrote into `test/fixtures` and left an untracked directory behind after every run. * docs: list the hot options and note what the client needs in old browsers * test(client): assert the compiled client is always ES5 Compiles every `client-src` file through the real build config (`envName: "production"`, since jest runs under the test env) and walks the acorn AST against an allowlist of ES5.1 node types, so a syntax nobody thought of fails rather than slips through. `let`, generators, computed/shorthand properties, bigint literals and post-ES5 regular expression flags are checked on the node types ES5 already had. Only the module syntax webpack consumes (and the `import.meta.webpackHot` it replaces) is allowed through. * docs: list the client overlay options in their own table * test(hot): cover the remaining lines of the SSE stream Codecov reported three uncovered lines in src/hot.js. Two are branches the review added (the catch-up write to an ended response, and the idempotent disconnect), one is the child-compilation path of extractBundles. hot.js is at 100% lines from the unit test alone now. * fix(hot): address the automated review findings - Attach the SSE stream only for GET: a HEAD request to the hot path was handed a body and left hanging until it timed out. - Reject a query string or fragment in `hot.path`: `pathMatch` compares pathnames, so such an endpoint could never match a request. - Do not subscribe the same client copy twice: with `autoConnect` on, a `setOptionsAndConnect()` call added a second listener and every message was processed twice. Keyed by path, so a call that changes `path` still subscribes. - Keep delivering to `subscribeAll` when the `name` filter rejects an event, as its documentation promises. - Walk `querySelectorAll` by index: a NodeList is not iterable in an ES5 browser, so the compiled for...of threw there. - Say in the changeset that the client ships with the package rather than being served by the middleware. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: alexander-akait <sheo13666q@gmail.com>
* feat: add hot option for hot module replacement
* feat: add browser client runtime for HMR (#2323)
* feat: add browser client runtime for HMR
Ports the browser client into `client-src/`, mirroring the layout used
in `webpack-dev-server` (source in `client-src/`, built to `client/`).
The client connects to the SSE endpoint via `EventSource`, parses
query-string options from `__resourceQuery`, dispatches `building`,
`built` and `sync` payloads, applies HMR through `process-update.js`
and renders compile-time errors and warnings through an in-page overlay
(`overlay.js`).
Exposed via the `./client` subpath export so users can wire it as a
webpack entry: `require('webpack-dev-middleware/client')`. The source
is transpiled with a browser-targeted babel override and the resulting
files are shipped under `/client`.
* test: add browser client runtime tests
Covers the public client API and key SSE handling paths in jsdom:
EventSource connection on default and custom paths, ignored heartbeat
messages, dispatch of building/built/sync to subscribers, custom
handler for unknown actions, warnings on invalid JSON, EventSource
wrapper caching across multiple entries, and timeout-driven reconnect.
* test: expand browser client coverage to mirror webpack-hot-middleware
Adds tests covering the original webpack-hot-middleware client suite
(processUpdate invocations on built/sync, errored/warning behavior,
overlay show/hide transitions, the overlayWarnings option, the name
filter), while keeping the new coverage for heartbeat handling,
invalid JSON warnings, EventSource wrapper caching across entries and
timeout-driven reconnects.
* ci: run on push and PRs against the hot-middleware umbrella branch
* refactor(client): switch process-update to promise-only HMR API
The ported logic called `module.hot.check`/`apply` with both a callback
and a Promise-handling branch to support webpack < 2. In webpack 5 both
paths fire, so the callback ran twice, triggering a redundant
`module.hot.apply` on every update.
Drop the legacy callback path and use the Promise API exclusively, which
is the canonical webpack 5 contract and matches our peer dependency.
* chore(client): type-check client-src with a dedicated tsconfig
Mirrors the layout webpack-dev-server uses: a separate
`tsconfig.client.json` (`noEmit`, browser-targeted libs,
`webpack/module` augmentation) runs over `client-src/` via a new
`lint:types-client` script, with a small `client-src/globals.d.ts`
declaring `ansi-html-community` and the per-page singletons the client
stores on `window`.
Refines the JSDoc annotations in `client-src/index.js` and
`client-src/process-update.js` so `module.hot`, `window` extensions
and the HMR `ApplyOptions` type-check cleanly.
* docs: document the browser client runtime in README
Adds a 'Hot Module Replacement client' section explaining how to wire
`webpack-dev-middleware/client` as a webpack entry, the query-string
options that the runtime understands, and the programmatic
`subscribe` / `subscribeAll` / `useCustomOverlay` /
`setOptionsAndConnect` exports.
* chore(client): adopt browser-outdated-recommended-commonjs eslint preset
Switches the client-src lint config to the dedicated preset
`eslint-config-webpack` ships for browser-targeted CommonJS code, the
same family of preset webpack-dev-server uses for its own client.
Brings the per-directory rule overrides down to two:
`no-console` (legitimately used for HMR status messages) and
`no-use-before-define` (relaxed for hoisted function declarations).
Adjusts the source to satisfy the rest of the preset directly: adds
`use strict` headers, fills in JSDoc for every function, renames
`EventSourceWrapper` to `createEventSourceWrapper` (per `new-cap`),
names the anonymous module exports, and reorders `performReload`
before `handleError` so it is declared before use.
* refactor(client): route logging through webpack's runtime logger
Wraps `webpack/lib/logging/runtime` in a small `utils/log.js` module
that exposes a level-based logger registered under the
`webpack-dev-middleware` name (matching the infrastructure logger the
server side already uses). Replaces every `console.log`/
`console.warn` call in the client and HMR update path with the
equivalent `log.info`/`log.warn`/`log.error` calls so output is
prefixed and gated by a single `logging` level.
User-facing API:
- `logging` query-string option accepts `none|error|warn|info|log|verbose`
- The previous `log`, `warn`, `noInfo` and `quiet` flags are dropped
in favour of `logging`
Other cleanups enabled by this:
- Drop the `no-console: off` exception from the client-src ESLint config
- Update README's client option table accordingly
- Add tests covering the new `logging` levels and the logger prefix
* test(client): use snapshots for logger output assertions
Replaces regex-based `some(([msg]) => /.../).toBe(true)` checks on the
mocked console with `toMatchSnapshot()` over `mock.calls`. The
snapshots capture the exact log lines including the
`[webpack-dev-middleware]` prefix and per-call argument count, so any
change to the log format surfaces in the test output instead of silently
passing.
Adds explicit assertions to the existing error / warning flow tests so
`console.error` / `console.warn` mocks are not only silenced but also
verified to be called with the expected output.
* chore: document why client-src needs the ecmaVersion override
`eslint-config-webpack@4.9.6` still ships `browser-outdated-recommended-commonjs`
with `configs["javascript/es5"]` and no parser override, so `const`
is rejected. The module variant of the same preset patches this
upstream — we replicate the patch locally until the commonjs variant
does the same.
* refactor(client): migrate to ES modules and update Babel configuration
* fixup!
* feat: implement hot module replacement middleware (#2321)
* fix(test): correct output path typo in webpack.array.warning fixture
The first compiler entry used `../../outputs/...` which escaped the test
directory and wrote artifacts to the repository root, outside of the
`/test/outputs` paths covered by `.gitignore` and `.prettierignore`.
* feat: implement hot module replacement middleware
Adds a `hot: true | { path, heartbeat, log, statsOptions }` option that
turns the dev middleware into a Server-Sent Events endpoint publishing
`building`, `built` and `sync` payloads from the webpack compiler.
The hot endpoint defaults to `/__webpack_hmr` and is served by the
existing middleware - no separate `app.use()` call is required.
`close()` tears down clients and the heartbeat timer.
* test: add tests for hot middleware
Covers schema validation of the `hot` option (success and failure cases
with snapshots), unit tests for `pathMatch`, `formatErrors`,
`buildModuleMap` and `createEventStream`, and integration tests that
verify SSE headers, the default and custom hot paths, MultiCompiler
support, `close()` teardown, and the `log` option (custom function and
`log: false`).
* test: add unit and integration tests for hot middleware functionality
* feat: enhance honoWrapper to support Web ReadableStream for hot middleware responses
* feat: add TypeScript definitions for hot module replacement functionality
* test(hot): cover publish, sync-on-connect, headers and close behavior
Ports the remaining unit-level cases from webpack-hot-middleware that
were not already covered by the framework matrix in middleware.test.js:
- the public `publish()` API broadcasts custom payloads
- a client connecting after a build receives a `sync` event
initialised from the last stats
- HTTP/1 clients get `Connection: keep-alive`, HTTP/2 clients do not
- when `stats.name` is empty the published payload falls back to
`compilation.name`
- a single broadcast reaches every attached client
- after `close()` further compiler events do not produce writes
* docs: document the hot option in README
* docs: list the hot option in the README options table
* refactor: replace EXPECTED_ANY with specific types in hot module definitions
* docs: update README to clarify default stats options for SSE payload
* refactor: remove log option from hot middleware and update related documentation
* docs: update default value for hot option in README to false
* docs: add hot module replacement example with Express server and client setup
* feat: enhance error overlay with close button and improved styling
* feat: enable reload option for HMR by default and update tests
* feat: add SSE helper functions and tests for event streaming
* refactor(client): inline ansi stripping and drop the strip-ansi dependency (#2350)
The client runtime runs in the browser, so Node's
util.stripVTControlCharacters is not an option. Inline the ansi-regex
pattern (verified against strip-ansi@6 output) in a small util instead
of shipping the dependency.
Ref webpack/webpack-hot-middleware#465
Ref webpack/webpack-hot-middleware#474
* refactor(hot): drop the module map from the SSE payload (#2349)
webpack-dev-server does not send module names over the wire: the HMR
runtime logs module ids on apply. Align the SSE payload with that
(name, action, time, hash, errors, warnings) instead of serializing a
module id → name map that was only used for log cosmetics and was
empty with the default stats options anyway.
Ref webpack/webpack-hot-middleware#452
Ref webpack/webpack-hot-middleware#306
* feat(client): add disconnect() to close the SSE connection (#2351)
* feat(client): add disconnect() to close the SSE connection
Expose a way to close the EventSource for the current path and stop
the reconnection watchdog (e.g. before tearing the page down). The
cached wrapper is dropped so a later setOptionsAndConnect() opens a
fresh connection.
Ref webpack/webpack-hot-middleware#367
* fixup!
* feat(hot): include the changed file in the building event (#2352)
The compiler's invalid hook reports which file invalidated the
compilation. Forward it as an optional `file` field on the `building`
payload and log it in the client, so users can see what triggered a
rebuild.
Ref webpack/webpack-hot-middleware#173
* feat(client): bring the error overlay to parity with webpack-dev-server (#2353)
* feat(client): bring the error overlay to parity with webpack-dev-server
- Render inside an about:blank iframe and style exclusively through
the CSSOM so the overlay works under a strict style-src CSP; inline
styles from ansi-html are re-applied via style.cssText.
- Support Trusted Types: innerHTML writes go through a policy
(configurable via overlayTrustedTypesPolicyName).
- Capture uncaught runtime errors and unhandled rejections in the
overlay (overlayRuntimeErrors, default true), with the same React
error boundary heuristic as dev-server.
- Inline the HTML entity encoder and drop the html-entities dependency.
- Expose the overlay as a standalone subpath export
(webpack-dev-middleware/client/overlay) so webpack-dev-server can
reuse it.
Ref webpack/webpack-hot-middleware#457
* feat(client): support opening file references in the editor
When `overlayOpenEditorEndpoint` is set, file chips in the overlay
become clickable and issue GET <endpoint>?fileName=<file:line:column>
— the same contract as webpack-dev-server's open-editor route. The
endpoint implementation is left to the server integration, so no
launch-editor dependency is added.
* feat(client): bring the error overlay to parity with webpack-dev-server
- Render inside an about:blank iframe and style exclusively through
the CSSOM so the overlay works under a strict style-src CSP; inline
styles from ansi-html are re-applied via style.cssText.
- Support Trusted Types: innerHTML writes go through a policy
(configurable via the overlay's trustedTypesPolicyName).
- Capture uncaught runtime errors and unhandled rejections in the
overlay, with the same React error boundary heuristic as dev-server.
- Adopt dev-server's client.overlay option shape: a boolean or a JSON
object with errors/warnings/runtimeErrors (booleans or filter
functions) and trustedTypesPolicyName, so dev-server configs migrate
as-is. Warnings are shown by default and no longer block updates,
matching dev-server.
- Support opening file references in the editor: when
overlayOpenEditorEndpoint is set, file chips issue
GET <endpoint>?fileName=<file:line:column> (same contract as
dev-server's open-editor route); the endpoint implementation is left
to the server integration.
- Inline the HTML entity encoder and drop the html-entities dependency.
- Expose the overlay as a standalone subpath export
(webpack-dev-middleware/client/overlay) so webpack-dev-server can
reuse it.
Ref webpack/webpack-hot-middleware#457
Ref webpack/webpack-hot-middleware#184
* fix(client): track overlay problems per compilation
With a MultiCompiler the client receives one event per bundle, and a
successful build from one bundle used to wipe another bundle's
still-valid errors from the overlay. Keep the live problems keyed by
compilation name, render the union, and only clear the overlay when
every compilation is clean. The console de-duplication cache is also
keyed per bundle so interleaved payloads do not defeat it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(examples): add a MultiCompiler hot example
Keep examples/hot as the minimal single-compiler setup (now with
runtime-error buttons and an opt-in DEMO_WARNING build warning), and
add examples/hot-multi-compiler: two compilers ("app" and "admin")
sharing one middleware instance and a single SSE connection, with
per-bundle `?name=` client scoping and recipes for the overlay's
per-compilation error tracking.
Each multi config sets a distinct output.uniqueName (and prefixed
hot-update filenames): with both bundles on one page, a shared
webpackHotUpdate global would make each bundle's updates land in the
wrong runtime.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(client): nest overlay extensions inside the overlay option
Move styles, ansiColors and openEditorEndpoint into the overlay object
as webpack-dev-middleware extensions of dev-server's client.overlay
shape, replacing the flat overlayStyles/ansiColors/
overlayOpenEditorEndpoint options.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* test(client): cover process-update
Isolate the HMR runtime lookup behind a small util so process-update
is loadable under jest, and add coverage for the updated-modules
console output.
* feat(client): collapse the updated-modules list in the console (#2361)
The success path now logs a one-line summary with the module count at
the default "info" level, and the per-module detail as a collapsed
console group visible from the "log" level up (webpack's runtime
logger gates groups below that level). The unaccepted-modules warning
list stays flat: it is diagnostic output for the failing case.
Ref webpack/webpack-hot-middleware#311
* feat(client): paginate the overlay problems (#2359)
The overlay now shows one problem at a time, with a header row holding
the problem badge and prev/next navigation (colored after the problem
type), a page counter and arrow-key navigation. Each new problem set
starts at its first page, accumulated runtime errors land on the
newest one, and identical re-published sets (e.g. another bundle of a
multi-compiler syncing) keep the current page. Enabled by default;
disable with the overlay option's `paginate` extension
(`?overlay={"paginate":false}`) to render the full list.
Also ignore click targets re-rendered away mid-dispatch in the
backdrop-dismiss listener, which otherwise closed the overlay when
clicking the navigation buttons.
* fix(client): avoid double slash when joining dynamicPublicPath (#2348)
* fix(client): avoid double slash when joining dynamicPublicPath
When output.publicPath ends with a slash (the common case) and the SSE
path starts with one, the naive concatenation produced URLs like
`https://host//__webpack_hmr`, which never match the server-side
pathname check.
Ref webpack/webpack-hot-middleware#366
* fix(client): append path to the public path like a filename in dynamicPublicPath
The default `path` starts with "/", so the naive concatenation with a
public path ending in "/" produced URLs like
`https://host//__webpack_hmr`, which never match the server-side
pathname check.
Strip the leading slash from `path` and concatenate without any other
normalization: the public path itself is left untouched (intentional
double slashes, e.g. nginx rewrites, are preserved) and is expected to
end with "/".
Ref webpack/webpack-hot-middleware#366
* feat(hot): add a building indicator with optional compilation progress (#2358)
The client shows a small badge (shadow DOM, CSSOM-only styles) while a
rebuild is in progress, enabled by default (disable with
`?progress=false` on the client entry). When the server enables the new
`hot.progress` option, webpack's ProgressPlugin publishes throttled
`progress` events over SSE (deduplicated by rounded percent, reset on
each new build) and the badge renders a CSP-safe SVG progress ring with
the compilation percentage.
The palette is shared with the error overlay through a common theme
module, and the indicator is exposed as a standalone subpath export
(webpack-dev-middleware/client/indicator) so other tooling can reuse
it. Both the indicator and the overlay now bail out safely when
`document.body` does not exist yet.
Ref webpack/webpack-hot-middleware#167
* docs: add HMR notes and troubleshooting section (#2362)
* docs: add HMR notes and troubleshooting section
Covers the recurring questions from the webpack-hot-middleware
backlog: browser connection limits and HTTP/2
(webpack/webpack-hot-middleware#423,
webpack/webpack-hot-middleware#298), warning filtering layers
(webpack/webpack-hot-middleware#319,
webpack/webpack-hot-middleware#228), absolute paths and public paths
(webpack/webpack-hot-middleware#281,
webpack/webpack-hot-middleware#203,
webpack/webpack-hot-middleware#193), and a custom-events recipe
(webpack/webpack-hot-middleware#118,
webpack/webpack-hot-middleware#226). The client `reload` option now
also notes its default differs from webpack-hot-middleware
(webpack/webpack-hot-middleware#370).
* fixup!
* fix(hot): publish sync instead of built for unchanged bundles (#2357)
* fix(hot): publish sync instead of built for unchanged bundles
With a MultiCompiler, rebuilding one child re-emits done for every
child. Clients of the unchanged bundles then try to fetch a hot-update
manifest that was never emitted (404 / "Cannot find update", or an
unwanted full reload with reload=true).
Compare each bundle's hash against the previous build and announce
unchanged bundles as `sync` instead of `built`. The first build still
publishes `built`, and new clients are still caught up with `sync` —
but no longer while a rebuild is in progress.
Ref webpack/webpack-hot-middleware#312
* fixup!
* fix(client): reload when an accept handler errors during apply (#2360)
* fix(client): reload when an accept handler errors during apply
An error thrown inside a module.hot.accept handler was logged and
ignored, leaving the page running stale code with no recovery. When the
`reload` option is enabled (the default), onErrored now falls back to a
full page reload, like the other unrecoverable update paths.
The HMR runtime and page-reload calls are isolated behind small utils
so process-update finally has direct test coverage. A missing HMR
runtime is now reported with a single actionable error instead of
throwing at bundle evaluation.
Ref webpack/webpack-hot-middleware#334
* fixup!
* fixup!
* fix(hot): scope the catch-up sync and pair bundles by name
- A client connecting after a build now receives the catch-up `sync`
alone instead of it being broadcast to every connected client, which
re-triggered their reporters on each new tab.
- `publishBundles` pairs the previous build's bundles by name instead of
array index, so a changing set of compilations cannot compare a bundle
against a sibling's hash. Unnamed bundles keep the positional pairing.
- The client's console dedup cache is cleared per bundle, so a sibling's
clean build no longer re-logs another bundle's unchanged problems.
* fix(hot): end SSE requests that arrive after close()
instance.close() left `context.hot` set while `handle()` silently
returned, so a request to the hot path after close received no
response, no `next()`, and hung until the socket timed out.
Detach the SSE intercept on close so requests fall through to the
regular middleware, and answer 404 from `handle()` for requests that
raced the intercept.
* feat(client): share the building indicator and track builds by source (#2369)
The badge state lives in a window singleton (same pattern as the
overlay), so a second bundled copy of the module drives the same badge
instead of stacking a duplicate, and fields missing from another
version's state are filled in place.
show() and hide() take an optional source: each concurrent build keeps
the badge alive until every source finished, so in a MultiCompiler one
bundle's `built` no longer hides the badge while a sibling is still
compiling — and with two clients sharing the badge, one client's hide()
cannot drop the other's indication. hide() without a source still
removes the badge unconditionally. Progress payloads carry no name, so
the client attributes them to the most recent `building` event.
* feat(client): share the overlay across bundled copies and report by source (#2368)
* feat(client): share the overlay across bundled copies and report by source
The overlay DOM and problem state now live in a window singleton, so a
second bundled copy of the module (e.g. the webpack-dev-server client
once it adopts this overlay) renders into the same iframe instead of
stacking a duplicate. Fields missing from a state created by an older
copy are filled in place, and the Trusted Types policy moves into the
shared state too — creating two policies with the same name throws
under an enforced CSP.
* test(overlay): prevent re-rendering when clearing a source that reported nothing
* refactor(cspell): consolidate cspell configuration into .cspell.json and remove cspell.config.json
* fix(hot): name building events and repair the client reconnect lifecycle
The building payload now carries the name of the compilation that
invalidated (tapped per child compiler, since the MultiCompiler hook
does not say which one fired), so clients can pair it with the built or
sync that follows — without it the building indicator registered every
build under "" and could never be hidden for named compilations.
On the client, the inactivity watchdog is restarted inside init(), so it
survives a reconnect instead of dying with the first clearInterval, and
the reconnect timeout handle is now stored and cleared by close(), so
disconnect() during the reconnect window no longer resurrects an
orphaned, uncloseable connection.
All three defects were inherited from webpack-hot-middleware.
* fix(hot): harden the SSE handshake and defer the overlay's Escape listener
The handshake now ends a response whose headers were already sent
instead of crashing on writeHead, and the middleware routes handshake
errors to next() — an exception there previously became an unhandled
rejection that killed the process.
The overlay's Escape listener on the host document is now attached
lazily inside ensureOverlay (once per page, through the shared state),
matching how webpack-dev-server registers it inside createOverlay, so
importing the client in a non-DOM environment (SSR bundle, worker) no
longer throws at evaluation time.
* fix(hot): align statsOptions and heartbeat validation and pair duplicate-name bundles by occurrence
statsOptions now accepts only the object form everywhere: the schema
rejected string presets the types allowed, and booleans validated but
were silently ignored since toBundles only merges objects over the
middleware's base stats options. Schema, JSDoc, and generated types all
agree now, so invalid forms fail validation instead of at runtime.
heartbeat: 0 was schema-valid but silently replaced with the 10s
default by a falsy check — the schema now requires >= 1 and the code
uses ?? so the option and its validation tell the same story.
* fix(hot): require a leading slash in the hot path and serialize publishes once
A path without a leading slash validated but could never match a
request, since URL pathnames always start with one — the schema now
enforces it. publish() also serializes the payload once per event
instead of once per connected client.
* fix(hono): set headers in the SSE writeHead shim instead of appending
writeHead semantics replace a header an earlier middleware may have
left on the response — appending merged both values into one
comma-joined header (e.g. "max-age=100, no-cache, no-transform" for
Cache-Control, which intermediaries may treat as cacheable).
* docs: fix the hot option reference and add a webpack-hot-middleware migration guide
The changeset advertised a `log` option that fails validation (it is
`progress`), `hot.statsOptions` documented the boolean form the schema
no longer accepts, `hot.path` now notes the required leading slash, and
the launch-editor sentence that had landed inside the `paginate`
parenthetical is back with `openEditorEndpoint`, where it belongs.
Also adds a "Migrating from webpack-hot-middleware" section: server and
webpack-config before/after, an option mapping table, and the
programmatic API equivalents.
* docs: make the migration guide's entry snippet valid standalone JS
eslint parses the README's fenced code blocks, and the bare
`entry: [...]` object fragment failed with "Unexpected token :" — the
before/after entries are now wrapped in module.exports.
* test: adopt a hybrid unit + browser e2e model, shard CI, and snapshot the console
The hot client, overlay, and building indicator are now exercised end
to end in headless Chrome (puppeteer 22, the last CJS release jest can
require) against a real webpack watcher: updates applied without
reload, the full-reload fallback, reconnect with catch-up sync after a
server restart, disconnect() semantics, overlay lifecycle (Escape on
the host page, HTML-in-error-message escaping, warnings, pagination),
the building badge, multi-compiler ?name= filtering with per-bundle
overlay slots, and the custom publish/subscribe API.
Console behavior is snapshotted from the real browser console: the full
info-level update cycle, logging=none/warn/error gates, and the
per-bundle dedup where a sibling's clean rebuild does not re-log
another bundle's unchanged warning while its own rebuild does. Each
multi-compiler compilation gets its own context dir so editing one
entry cannot invalidate the sibling's watcher and shuffle event order.
The jsdom suites stay, slimmed to what a browser cannot exercise
deterministically: process-update internals, shared state between
bundled copies, option parsing, and protocol edges. The review of the
new tests also surfaced a real client bug, now fixed: an EventSource
error event queued behind disconnect() re-armed the reconnect timer on
the orphaned wrapper — close() is now final.
e2e runs serially through the new test:e2e script (four concurrent
Chrome instances plus watchers starved the rest of the suite), and CI
shards every test job 4x with jest --shard, uploading per-shard
coverage that a final job aggregates for codecov, mirroring
webpack-dev-server's setup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): snapshot the logging=log module detail and drop the covered unit tests
A logging=log e2e run now snapshots the collapsed "Updated modules:"
group and its per-module entries straight from the browser console
(puppeteer surfaces group frames as console events). The jsdom
equivalents — the process-update logging describe and the
building-message log test, whose output already appears verbatim in the
info-level e2e snapshot — are removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): move the reconnection tests to the browser and snapshot their console
The inactivity watchdog is now exercised against a genuinely silent SSE
connection (a server heartbeat far beyond the client timeout): the
second connect proves it fires on pure silence and the third that it
re-arms after a reconnect. Manual recovery gets its own test —
disconnect() followed by setOptionsAndConnect() opens a fresh
connection that still delivers updates. Both, plus the server-restart
catch-up test, snapshot their console sequence; browser network-error
noise is filtered out of the snapshots since its volume depends on
reconnect timing.
The fake-timer equivalents in the jsdom suite are removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): exercise the indicator's shared state with two real bundled copies
The multi-compiler helper already produces what the jsdom suite could
only simulate: two genuinely separate bundled copies of the indicator
module on one page. Each bundle now exposes its copy as a global and
the tests drive both from the browser — a second copy adopts the same
badge instead of stacking one, per-source counting spans copies, hiding
an unknown source is ignored, and a leaner state shape left by an older
package version gets its missing fields filled (planted via
evaluateOnNewDocument before the bundles load).
The jsdom indicator suite is removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): cover the client option surface in the browser and slim the jsdom suite
The subscribe/subscribeAll contract, the ?name= filter, per-source badge
counting, the shared SSE connection between bundled clients (asserted
explicitly in the multi-compiler console snapshot: one "connected" for
two clients), the dev-server-shaped overlay option, and the warnings
filter function — parsed from the entry query and applied against two
real warning-producing modules — now all run in Chrome. The warnings
overlay test also covers clearing on a recovered build, and the
pagination test snapshots the two-error console output that the deleted
jsdom snapshot used to document.
client.test.js keeps only what the browser cannot pin deterministically:
problem-type transitions, the multi-bundle problem union, option
forwarding to the overlay factory, dynamicPublicPath URL building, and
protocol edges.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): assert the multi-bundle problem union in the browser
Breaking both bundles shows the union of their problems in the shared
overlay — paginated one per page, in whichever order the compilers
finished. The two jsdom equivalents are removed: the union test, and
no-apply-on-errored-builds, which the multi-compiler console snapshot
already documents (no "Checking for updates" follows a bundle's error
report).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): collapse the rebuilding path in console snapshots
webpack's invalid hook sometimes reports the watched directory instead
of the changed file under the polling watcher, so the "bundle
rebuilding (…)" line alternated between the file and the fixture dir
and flaked the snapshots. The normalizer now collapses both forms to
"(<fixture> changed)".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): isolate the indicator shared-state tests from the hot client
The bundles' real hot clients call indicator.hide("a")/hide("b") when
their connect-time syncs arrive — the same source names these tests
register — so a late sync could wipe a source mid-test. progress=false
keeps the client away from the indicator entirely.
* test: drop a stray build artifact committed into fixtures
test/fixtures/js3 was compiled output (a misdirected copy of the
dev-server-false test's dist, whose real destination under test/outputs
is gitignored) swept up by a bulk git add.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: let test:only take file arguments without losing the e2e exclusion
--testPathIgnorePatterns is variadic, so a positional file after
`npm run test:only --` was consumed as another ignore pattern — silently
excluding the very file being targeted (the = form appends all the
same). The exclusion now lives in jest.config.js and lifts itself when a
test/e2e path is requested explicitly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): exercise the overlay's shared state with two real bundled copies
Same pattern as the indicator: each compilation exposes its own bundled
copy of the overlay module and the tests drive both from the browser —
a second copy adopts the shared iframe instead of stacking one, both
sources paginate together in the union, either copy can dismiss what
the other rendered, errors from one copy outrank another's warnings
until the erroring source recovers, clearing an unreported source does
not rebuild the card another copy is showing, and a leaner state shape
left by an older package version gets its missing fields filled.
The jsdom equivalents are removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): dismiss the overlay with real clicks
Backdrop click, close (×) button, and click-inside-stays-open now run
against real mouse events in the overlay iframe — the backdrop case
clicks the frame's top-left corner away from the centered card, and the
close-button pass rides the catch-up sync that re-shows the overlay
after a reload. The jsdom dismiss describe is removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: gitignore the js3 fixture output
The one-error-one-warning-one-* fixture configs build into
test/fixtures/js3 by design, so every logging run recreates it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): accumulate runtime errors in the browser and settle on webpack 5.109
Runtime-error accumulation (two errors paging "1 / 2" ↔ "2 / 2") and
unhandled promise rejections now run against real page errors — the
rejection fixture rejects from the page's own script so the event
carries the real reason. Their jsdom equivalents are removed.
The branch also settles on webpack 5.109: the lockfile catches up with
the already-bumped range, and the assertions and console snapshots
adopt 5.109's parse-error format (numbered code-frame gutter, caret
line, and the "File was parsed as module type" note) instead of
normalizing it away.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(client): replace webpack's logging runtime with a self-contained logger
webpack/lib/logging/runtime.js pulls tapable into the client bundle,
and tapable compiles its hooks with new Function — an EvalError under a
require-trusted-types-for 'script' CSP that killed the client's logging
and surfaced spurious runtime errors in the overlay. The replacement is
a level-gated console logger with byte-identical output (same prefix
merging, same level gates, groups from the "log" level up), verified
against the existing console snapshots. It also drops tapable and
webpack's logging machinery from the bundle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): enforce the Trusted Types CSP for real
The page is served with require-trusted-types-for 'script' and a
trusted-types allowlist holding only the configured policy name — under
real Chrome enforcement, which jsdom cannot do, the overlay only
renders if every HTML write went through that policy (including inside
the about:blank iframe, which inherits the page's CSP). The jsdom
trusted-types test is removed; the helper gained a pageHeaders option.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(client): keep HMR alive when Trusted Types blocks the logging runtime
Under a require-trusted-types-for 'script' CSP, tapable — bundled
through webpack's logging runtime — cannot compile its hooks: the first
log call throws an EvalError from new Function, killing whatever
listener happened to log (and with it the update flow). The logger
methods are now guarded, so webpack's logging runtime stays the engine
and enforcement just turns logging off.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: prove SSE broadcast with two real pages and a mid-connection publish
Two browser pages connected to the same server both apply a single
edit — broadcast proven end to end. The unit variant also stops passing
vacuously: it now publishes while both SSE clients are attached instead
of only observing the per-client connect-time sync.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): poll app text on an interval so hidden pages resolve
waitForFunction's default requestAnimationFrame polling never fires on
a hidden document, so the backgrounded first tab of the broadcast test
hung on a wait for text it had already rendered. Also settle the
connections before editing so both pages receive the broadcast rather
than a catch-up sync.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): drive instance.invalidate() and instance.close() against a browser
invalidate() with unchanged sources rebuilds and reaches the page as a
building event followed by a no-op sync — the client neither re-renders
nor reloads (a built here would 404 on the never-emitted manifest).
close() ends the SSE stream under a connected page: the client falls
into its reconnect loop against an endpoint that no longer speaks SSE
and the page keeps running untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): keyboard pagination, paginate=false, and the cross-copy runtime filter
Arrow keys page through problems with real keyboard events (the frame
holds focus after the pager click), paginate=false shows the full
problem list with no counter, and a runtime filter configured by a
later bundled copy is honored by the window listeners the first copy
attached — proven non-vacuously by first catching an error through
copy A, then watching copy B's rejecting filter keep the overlay away.
The three jsdom equivalents are removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: drop two overlay unit tests the e2e suite already covers
The accent-bar colors are asserted (both hues, including the
error-to-warning flip) by the shared-state e2e, and the default
pagination counter by the pagination e2e.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): cover pagination clamping and page retention, drop the jsdom describe
The pagination e2e now clamps at the last page under real clicks and
key presses, and a shared-state test drives a bundled copy through the
page-index semantics: a re-publish of the same problems keeps the page
the user navigated to, a different set starts back at page one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: drop two more overlay unit tests the e2e suite covers
Problem replacement is asserted by the page-retention e2e (a new set
swaps the rendered content), and the overlay API shape is exercised by
every shared-state test driving real bundled copies.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: assert the dismiss hint where it is acted on
The Escape e2e now checks the card advertises the dismissal it is about
to perform; the jsdom hint test is removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): assert linkification on real error messages
The build-error e2e verifies webpack's own "See https://…" becomes a
safe new-tab link (href, target, rel), and the runtime-error e2e keeps
the sentence-ending dot out of the href. The two jsdom linkify tests
are removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: drop the hot unit tests the browser suites already prove
The building payload's file and compiler name are pinned by the e2e
console snapshots, sync-vs-built by the invalidate and multi-compiler
runs, custom publishes by the subscribe round-trip, and the event
stream's delivery, broadcast, and close behavior by the two-page and
instance.close() tests. The invalidate e2e now snapshots its console —
a file-less building line and a sync silent enough not to log. What
stays in hot.test.js is the transport level: headers, heartbeat frames,
the headersSent guard, duplicate-name pairing, and progress throttling.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): snapshot the console wherever it is asserted
hot.statsOptions warnings-filtering runs in the browser — its snapshot
is a lone "connected", the warning never entering the payload, while
the overlay={"warnings":false} snapshot shows the warning reaching the
console and stopping there. Three hot unit tests fall away: the
late-connect sync and post-close handling are covered by the e2e and
framework suites, and statsOptions forwarding by the new run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): problem-type transitions, runtimeErrors=false, warning updates, and dynamicPublicPath
A warning overlay escalates to an error overlay when the build breaks,
an error overlay becomes a warning overlay on partial recovery (read
through the page realm so the reload fallback cannot detach it),
overlay={"runtimeErrors":false} leaves runtime errors uncaught, updates
carrying warnings apply without a reload, and the client connects and
updates through a dynamic public path served under /assets/ (the
helper gained a publicPath option). Six jsdom equivalents are removed —
client.test.js keeps only the slash-edge parsing and the protocol
guards nothing can reach end to end.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): read the SSE protocol raw off a real server
A plain http reader against the running middleware covers what the
fake-response units simulated: the keep-alive handshake headers, real
heartbeat frames on real timers, and the catch-up sync reaching only
the newly connecting client while already-attached ones stay quiet.
The three fake-timer/fake-response equivalents leave hot.test.js; what
remains there has no end-to-end path (pure parsing, HTTP/2 headers,
forced pairing edges, progress throttling).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): make the warning-dedup sequence independent of the catch-up race
The shared SSE connection writes its catch-up sync once — a sibling
bundle still evaluating when it arrives misses it, so a warning carried
by that sync reached the console on some runs and not others. The
widget now starts clean (an empty catch-up cannot vary the sequence)
and picks its warning up through a build the page observes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): adopt webpack-dev-server's 400s test cap for cold CI runners
A multi-step browser test spent its whole 120s budget on a cold
Windows runner without any single wait hanging — first build, rebuild,
and reload each run several times slower there. dev-server runs all
its tests under a flat --test-timeout=400000; the e2e suites now use
the same cap, while the per-wait 30s timeouts keep catching real hangs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): share the waits, fixtures, and teardown across the browser suites
One helper now owns the text/overlay waits (all on interval polling —
only one of the five copies had received the hidden-page fix), the app
fixtures (accepted, unaccepted, warning-carrying, boom), the overlay
and indicator element ids, and the browser/app teardown that every
describe had cloned. Two snapshots move a warning column, following
the shared fixture's indentation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): click the backdrop by raw mouse coordinates
ElementHandle.click's scrollIntoViewIfNeeded pre-check stalled CDP on a
slow Windows runner (Runtime.callFunctionOn timing out). The overlay
iframe is fixed at the viewport origin, so a raw mouse click at (5,5)
lands on the backdrop without any evaluate round-trip.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): open the clicked file reference through a real endpoint
The overlay's file chip carries webpack's real file:line:column
reference, and clicking it lands a GET on an express route the test
mounts through the helper's new setup hook — the full documented
open-in-editor round trip, not just a fetch spy. Without an endpoint
configured no chip is rendered. The jsdom describe is removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): feed the client real heartbeats and a malformed frame
With a 100ms server heartbeat, several real 💓 frames cross the wire
and reach neither subscribers (only the catch-up sync does) nor the
console — snapshotted as a lone "connected". A rogue SSE endpoint
mounted through the helper's setup hook feeds the real EventSource a
non-JSON frame: the client warns (snapshotted, V8's wording is pinned
by the puppeteer-locked Chrome) and the page keeps running. The two
jsdom equivalents leave; client.test.js is down to the slash-edge
parsing and the no-EventSource guard.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): finish the client migration — runtime public path and no-EventSource
The dynamic public path is now asserted on the URLs the browser
actually requests: a bundled setter swaps __webpack_public_path__ at
runtime (the free variable only exists inside the bundle) and manual
reconnects show intentional double slashes surviving and trailing
slashes not doubling. Deleting EventSource before the bundle loads
(evaluateOnNewDocument) snapshots the polyfill warning with the page
still rendering. The jsdom client suite is gone — nothing was left
that a real browser could not cover.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): pin only the first three connects of the watchdog cycle
The silent watchdog keeps reconnecting by design, so a fourth
"connected" can land in the same poll window as the awaited third and
flake the full-list snapshot.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): process the update paths against the real HMR runtime
publish() turns out to be the injection point the fake runtimes
simulated: a built announcing a hash the server never produced walks
the real check → manifest 404 → reload fallback; a sync carrying the
bundle's own hash (read through a bundled getter — __webpack_hash__
only exists inside the bundle) locks the compilation name so a
sibling's impossible hash is ignored, snapshotted as pure silence; and
without HotModuleReplacementPlugin the disabled-runtime error logs
exactly once across two builds. The accept-handler error path runs on
a genuinely throwing accept callback: reload by default, a warning and
the broken state kept with reload=false. Six fake-runtime units leave;
what stays needs forced runtime states no real flow reaches.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): click frames through DOM-domain coordinates
The close-button click stalled on a Windows runner with the same
Runtime.callFunctionOn timeout as the backdrop before it — main-world
evaluates go unresponsive after navigations there. clickInFrame resolves
the element through the isolated-world selector wait, takes its
clickable point from the DOM domain, and clicks with raw mouse input;
the in-card and close-button clicks use it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): the pre-lock catch-up race and the missing-update-chunk path
The sibling-sync race runs deterministically against the real runtime:
a slowed-down manifest route holds the sibling's check open while the
own sync locks the compilation name, and the failed check is discarded
in silence — snapshotted as a connect and a single "Checking". Trying
to force the runtime's failure status through a missing update chunk
surfaced the real behavior instead: check rejects with a
ChunkLoadError, no failure status is reached, and the client logs
"Update check failed" and stays put — now pinned as its own test. The
abort-status unit stays: no real flow reaches that state on demand.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: drop the last process-update unit
The failure-status branch it exercised is unreachable through the real
runtime: the client's ignoreUnaccepted/ignoreDeclined/ignoreErrored
options close every path webpack 5.109 has to the abort and fail
statuses. The branch itself stays as a cheap guard for upstream
changes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): the React-boundary heuristic and the runtime slot reset, for real
A fixture function literally named invokeGuardedCallbackDev throws, so
the genuine stack carries the marker React's dev build leaves on
boundary-handled errors — the overlay ignores it while a plain error
right after proves the listeners live. The runtime slot resets through
its real trigger: a clean rebuild's reporter clears the accumulated
errors, and the next one starts a fresh slot with no pager. The jsdom
runtime-errors describe is removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): assert the badge and file-path rendering on real errors
The ERROR/WARNING badges (text and background) and the highlighted file
path are checked where Chrome actually renders them — on webpack's own
parse error and critical-dependency warning. Three jsdom rendering
tests leave.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): retire the last jsdom suite
The final overlay rendering details run in Chrome: the code-frame
highlight on webpack's real parse error, the re-mount after something
wiped the iframe (re-triggered by an unchanged rebuild, exercising the
identical-set guard), clear() as a no-op with nothing shown, and the
query-configured custom card styles and ansi colors driving the badge
and border. With no jsdom consumer left, jest-environment-jsdom leaves
the dev dependencies.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ci: collect server-side coverage from the e2e phase
Migrating server behavior to the browser suites moved its execution out
of the coverage run — the heartbeat write, the SSE handshake, and every
hot.js path only e2e exercises showed as uncovered. The e2e step now
instruments src/ too (the middleware runs inside the jest process even
under a browser), writes to its own directory, and the artifact carries
both lcov files for codecov to merge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): let the watcher settle before frame-level assertions
macOS fired a spurious startup rebuild whose unchanged hash broadcast a
legitimate sync to every client — indistinguishable from a re-sent
catch-up at the frame level. The helper's settle() drains builds until
the watcher goes quiet, and the catch-up test runs after it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* perf(client): use a Set for renewed module ids in logUpdates (#2375)
Replaces Array#includes inside the filter over updated modules
(O(n·m) → O(n+m)) — noticeable on updates touching hundreds of modules.
* perf(hot): avoid idle work on the SSE stream (#2376)
- skip payload serialization in publish() when no client is connected,
and cut the ProgressPlugin callback early via eventStream.hasClients()
- start the heartbeat interval with the first client and stop it with
the last one, instead of ticking forever
- pair bundles against the previous build through a name-grouped Map,
replacing the per-bundle filter (O(n²) → O(n))
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* test: measure the hot client from the browser
The e2e suite already exercised client-src, but nothing counted it: only
jest-instrumented modules were measured, so the browser-only paths read 0%
and the number came from the jsdom tests alone.
The e2e bundle now instruments client-src as webpack builds it, and the
counters are read off each page before its browser closes. Two details
matter. Istanbul's default way of reaching the global is
new Function("return this"), which a page enforcing
require-trusted-types-for 'script' refuses to compile, so the scope is
named directly -- instrumenting must not change what the client can run
under. And a reload takes window.__coverage__ with it, which is exactly
what reloadPage and the unaccepted-update branches would have reported,
so the counters are parked in sessionStorage on pagehide and picked up
afterwards.
client-src goes from 94.58% to 96.71% of statements and 85.35% to 90.07%
of branches; theme.js, utils/get-hot.js and utils/reload.js were each
reported at 0% and are now fully covered. Two e2e cases cover what was
left reachable: the overlay query override, an empty pair in the client's
own query string, and the non-JSON overlay value that falls back to on.
* test: retire the jsdom client suites, run the browser ones on Linux only
The four jsdom suites duplicated what test/e2e now drives through a real
browser, and since the client is measured from the browser they were no
longer carrying coverage either. Removing them takes jest-environment-jsdom
with them; the remaining jest suites are node-only.
client-src is at 97.82% of statements from the browser suite alone. Four
cases cover what the deletions left reachable: a second connect for an
already-subscribed path, a host-supplied custom overlay, an update to a
declined module, and an error event carrying neither an error nor a
message. The environment guards a browser cannot produce -- a
document-less import, a script running before <body>, a frame without a
document -- are marked rather than faked, since contriving them is how
these suites would turn flaky.
The browser suite says the same thing on every OS and node version while
costing the slowest job in the matrix, so it runs on ubuntu/22.x alone.
All four shards still run there, so the suite is covered exactly once.
* test: address the review findings on the e2e suite
Two of my own tests were green for the wrong reason. The custom-overlay
one stubbed show/clear, but the client calls showProblems/clear, so only
clear ever ran and a length check passed on it; it now asserts
showProblems with the errors. The declined-update one asserted a log
count while the page was in fact reloading underneath it -- a decline
cannot be applied, so the client falls back to a full reload, which the
test now waits for and asserts. The bare-error-event test checked the DOM
only after triggering a real error, which would have reused a wrongly
created overlay.
The runtime-error tests raced the client: attachRuntimeErrorListeners
runs during the client's own setup, while the tests only waited for the
app to render, so an error thrown first landed nowhere and the overlay
never appeared. Two different tests failed on two consecutive runs from
this. They now wait for the listeners through the state the client
already exposes -- including after a reload, and excluding the test whose
configuration deliberately attaches none.
Also: the merge left an unreachable env.test block in babel.config.js
behind the early return, naming a plugin the same merge removed; a failed
coverage read could leave Chromium running; the SSE reader dropped frames
split across TCP chunks; snapshots pinned V8 and puppeteer wording that a
toolchain bump would change; a multi-compiler comment claimed a re-log
that the dedup cache makes impossible; and upload-coverage skipped
entirely when a shard failed.
* ci: run the browser suite in one job instead of across the matrix
The browser suite was running in all 48 matrix jobs, and the 4-way shard
axis this branch added existed to absorb that cost. It drives a real
Chromium, so every OS and node version reports the same thing.
It moves to a single ubuntu job. The shard axis goes with it, which puts
the test matrix back to what main has: 3 OS x 4 node versions, 12 jobs
running the node suite unsharded. Together with lint, the client job and
the coverage upload, a pull request goes from 48 test jobs to 12 plus
one.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: alexander-akait <sheo13666q@gmail.com>
Summary
What kind of change does this PR introduce?
Did you add tests for your changes?
Does this PR introduce a breaking change?
If relevant, what needs to be documented once your changes are merged or what have you already documented?
Use of AI