You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat: run webpack-dev-server as a compiler plugin - #4847
Run webpack-dev-server as a compiler plugin when it exposes apply(). The CLI starts watch compilation, prints build stats, and closes the compiler on signals or stdin EOF so the server stops through its shutdown hooks. Older dev servers keep their existing start()/stop() flow.
The plugin path disables the server's own signal handlers to avoid duplicate shutdown and preserves failure exit codes. A later successful rebuild clears a failure exit code. Duplicate ports in devServer configurations are detected before listening starts.
The middleware leaves printing stats to the CLI, so the dev server's devMiddleware.stats replace the configuration's stats, and the CLI sets them up the same way as configuration stats (a boolean or a preset string becomes an object, plus colors).
webpack-dev-server only logs a failure of the plugin's asynchronous listen() phase, e.g. a port that is already in use. The CLI wraps listen() of each dev server plugin it creates and, on a failure, sets the exit code to 2 and closes the compiler, the same as dev servers without plugin support. A TODO notes to remove the wrapper once webpack-dev-server reports listen failures to the compiler. Setup errors that reach the watch callback also exit with code 2.
Multi-compiler mode and --dev-server-per-compiler
webpack-dev-server 6 supports a multi-compiler both through apply(multiCompiler) and as a plugin in each configuration. Verified with webpack-dev-server 6.1 and two configurations with their own devServer:
Mode
Server of the 1st config
Server of the 2nd config
apply(multiCompiler) (default)
both bundles
both bundles
apply(childCompiler) (--dev-server-per-compiler)
1st bundle only
2nd bundle only
new DevServer() in each config's plugins
1st bundle only
2nd bundle only
By default, every dev server keeps serving all compilers, the same as dev servers without plugin support do. The new --dev-server-per-compiler option of webpack serve applies each dev server only to the compiler that defines it, and prints each compiler's stats using the devMiddleware.stats of its own dev server. Dev servers without plugin support (v5) ignore the option with a warning.
The workaround for dev servers without plugin support (the apply check, the v5 typings cast and the start()/stop() path) is marked with TODOs to remove in the next major release. webpack-dev-server is bumped to ^6.1.0 for development (validation in apply() and stopping before listen()).
What kind of change does this PR introduce?
Feature, with shutdown fixes and regression tests.
Did you add tests for your changes?
Yes. Tests cover compilation errors and warnings, middleware setup failures, a port already in use, exit status and shutdown through SIGINT/SIGTERM/stdin, resetting the failure exit code after a successful rebuild, duplicate signal handlers, serving rebuilt bundles, middleware stats, independent multi-compiler rebuilds with distinct watch options and shutdown hooks, webpack before 5.112, and which compilers each dev server serves with and without --dev-server-per-compiler. The duplicate-port snapshot is updated for validation before startup.
Does this PR introduce a breaking change?
No intended breaking change. Existing webpack-dev-server 5 integration remains on the imperative path. In plugin mode, the CLI owns signal handling even when setupExitSignals: true is configured, and shutdown preserves compilation failure status.
If relevant, what needs to be documented once your changes are merged or what have you already documented?
Added a minor changeset describing compiler plugin support and the --dev-server-per-compiler option, and regenerated SERVE-OPTIONS-v6.md. The serve documentation should explain CLI ownership of watch compilation, stats output, and shutdown with webpack-dev-server 6.
Use of AI
Codex assisted with the initial implementation, regression tests, code review, and this PR description. Claude Code rebased the PR onto main, verified the multi-compiler behaviour, added the --dev-server-per-compiler option, and fixed the listen-failure and exit-code handling and the flaky tests.
Summary by CodeRabbit
New Features
webpack serve now supports webpack-dev-server 6 as a compiler plugin, including watch builds and graceful shutdown.
In multi-compiler mode, each dev server serves all compilers by default. Use --dev-server-per-compiler to limit each server to its own compiler.
Multiple dev servers can select distinct automatic ports.
Improvements
Build statistics are displayed according to the configured middleware stats mode.
Failed rebuilds set a failing exit code, which clears after a successful rebuild.
Documentation
Added help and option documentation for --dev-server-per-compiler.
❌ Patch coverage is 98.28571% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.07%. Comparing base (351f1c9) to head (ad60c8c).
It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.
The serve command adds plugin-based webpack-dev-server integration and the --dev-server-per-compiler option. It supports per-compiler server assignment, validates explicit ports, and assigns distinct automatic ports to multiple servers. Compiler configuration, stats output, and shutdown handling are shared with watch execution. New tests cover serving, ports, error handling, middleware stats, and rebuild behavior.
Priority: Normal
Merge Risk: Moderate · previous review d230e
With several dev servers sharing a multi-compiler setup, only the last server may serve compiled assets. Fix this before merge or limit the supported configurations. The earlier exit-code and listen-failure concerns are addressed.
Pre-merge checks: 5
Check name
Status
Explanation
Description Check
Check skipped - CodeRabbit’s high-level summary is enabled.
Title check
The title clearly and concisely describes the primary change: running webpack-dev-server as a compiler plugin.
Docstring Coverage
Docstring coverage is inconclusive: files from earlier reviews still need to be rechecked within the check's analysis limits. Not blocking.
Linked Issues check
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Finishing Touches: 💡 1
📝 Generate docstrings
Commit to this branch
Create a new PR
🧪 Generate unit tests (beta)
Commit to this branch
Create a new PR
🛠️ Fix failing CI checks: 💡
Commit to this branch
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
In multi-compiler mode every dev server serves all compilers by default,
the same as dev servers without plugin support do. With the new option, each
dev server is applied as a plugin only to the compiler that defines it, and
each compiler's stats are printed using the stats options of its own dev
server. Dev servers without plugin support ignore the option with a warning.
Also require webpack-dev-server@6.1 for development and mark the workaround
for dev servers without plugin support for removal in the next major release.
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
…pack serve`
With plugin support, `webpack serve` adds dev servers to the loaded
configuration as plugins: to the configuration that defines `devServer`
with `--dev-server-per-compiler`, otherwise to the top-level `plugins` of
the multi configuration. Then it compiles like `webpack watch`, reusing
its stats output, so `--json` works too. The old flow for dev servers
without plugin support is kept unchanged in the `else` branch.
webpack before 5.112 doesn't apply `plugins` of multi configurations, so
they are applied to the multi compiler before watching starts. TODOs mark
both workarounds for removal in the next major release.
Dev servers in plugin mode select ports before any of them listens, so
the CLI still assigns distinct automatic ports and rejects duplicate
explicit ports, otherwise one of the servers fails with EADDRINUSE.
The automatic ports test now picks ports below the ephemeral range, which
outgoing connections on Windows could take, and stops on EADDRINUSE instead
of waiting for the test timeout.
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
Test - windows-latest - Node v24.x, Webpack latest, DevServer 5 failed on 04d3f13 because test/info/output.test.js timed out after 180s, with an orphaned powershell process left at job cleanup. This PR doesn't touch webpack info: main fails the same job the same way at 351f1c9 (test/info/basic.test.js times out, orphaned powershell), so it looks like an existing hang in envinfo on Windows runners. There's no fix for it yet. The next push to this PR re-runs CI.
Plugin mode dev servers select ports before any of them listens, so the
CLI picks distinct automatic ports itself. It now uses the dev server's own
`getFreePort()` (starting each search after the previously picked port)
instead of the `get-port` dependency, which is removed from `webpack-cli`.
Also simplify the plugin setup: one loop creates the dev servers and one
loop prepares the configurations (watch mode and middleware stats).
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
Fix the sticky failure exit code in plugin-mode serve.
#setupGracefulShutdown(compiler, true) exits with process.exitCode. The shared watch callback in #createWebpackCallback sets process.exitCode = 1 when a build has errors. The callback never clears it when a later rebuild succeeds.
Example trigger:
A build fails with a syntax error.
The developer fixes the file, and the rebuild succeeds.
The developer presses ^C.
The CLI exits with code 1 although the last build was clean. npm scripts then print an error. The serve command exited with 0 on a signal before this change.
Track the result of the latest build in this mode. Do not keep the earliest failure.
One option is a per-build flag in #createWebpackCallback. Reset the flag on each callback invocation. Set process.exitCode from the flag only when the preserveExitCode mode is active. Keep the existing behavior for the single-run build command.
Review info
Run configuration
Source mode: Auto
Repository: Unavailable
Branch: Unavailable
File: No configuration file from this repository applied
Commit: Unavailable
Layers: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: f992d925-a917-4284-80fe-5eb294efa4fc
Commits
Reviewing files that changed from the base of the PR and between 04d3f13 and 5954947.
Files ignored due to path filters: 1
package-lock.json is excluded by !**/package-lock.json
Files selected for processing: 1
packages/webpack-cli/src/webpack-cli.ts
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.
…ck serve`
A failed build sets the failure exit code, which `webpack serve` keeps on
shutdown. The code stayed set after the error was fixed, so stopping the dev
server after a successful rebuild still exited with 1. The stats callback now
clears the failure exit code it set when a later build succeeds.
Also test the fallback for webpack before 5.112, which doesn't apply
`plugins` of multi configurations.
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
The reason will be displayed to describe this comment to others. Learn more.
Caution
Some comments are outside the diff and can’t be posted inline due to GitHub limitations.
⚠️ Outside diff range comments (1)
Major · Propagate asynchronous server listen failures. · webpack-cli.ts:2359
packages/webpack-cli/src/webpack-cli.ts:2359 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Propagate asynchronous server listen failures.
If a selected port becomes unavailable before listen(), plugin mode logs the bind error but leaves watch compilation running without a server or a failure exit status. The watch callback cannot catch this error: webpack-dev-server starts listen() from its done hook and catches the rejection there. Provide a startup-error path that sets a nonzero status and closes the compiler. (raw.githubusercontent.com)
Review info
Run configuration
Source mode: Auto
Repository: Unavailable
Branch: Unavailable
File: No configuration file from this repository applied
Commit: Unavailable
Layers: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: 59609062-4b57-4929-bc07-064f8b7f8e07
Commits
Reviewing files that changed from the base of the PR and between 5954947 and f524913.
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.
As a plugin, webpack-dev-server starts listening after the first build
and only logs a failure, e.g. a port already in use, so the CLI kept
watching without a dev server and exited with 0. The CLI now sets the
exit code to 2 and closes the compiler, as it does for dev servers
without plugin support.
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
@coderabbitai both findings outside the diff are addressed:
Sticky failure exit code in plugin-mode serve: fixed in f524913. The stats callback clears the failure exit code it set once a later build succeeds. Covered by "should reset the failure exit code after a successful rebuild".
Asynchronous listen failures: fixed in a70dff9. The CLI wraps listen() of each dev server plugin it creates. On a failure, e.g. EADDRINUSE, it sets the exit code to 2 and closes the compiler, the same as dev servers without plugin support. The plugin still logs the error. Covered by "should log the error and exit when the port is already in use". The TODO is to drop the wrapper once webpack-dev-server reports listen failures to the compiler.
For best results, initiate chat on the files or code changes.
@alexander-akait Thanks for addressing both findings and adding regression tests. The reported fixes cover successful rebuild recovery and compiler shutdown with exit code 2 after a listen() failure.
I have not independently verified the commits or test results.
If you found this review helpful, would you consider giving us a shout-out on X?
The test fixed the entry once, right after the first failed build, which
the watcher could miss: it can start watching after that write, and on
macOS the temporary directory is behind a symlink. The test now uses the
real path and rewrites the fix until a rebuild succeeds.
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
The merge risk in the summary is based on f524913. Since then, a70dff9 exits with code 2 when a dev server fails to listen, and 6514be0 makes the rebuild recovery test stable on macOS.
Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.
The middleware stats replace the configuration's stats, and the CLI now
normalizes them with the same code it uses for the configuration (a
boolean or a preset string to an object, and colors), instead of its own
conversion.
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
Since the last review, two things changed. 817c7ae sets up the dev middleware stats with the same #setupStats() the CLI uses for configuration stats, replacing its own conversion. d230ec8 reverts the Windows webpack info change and its changeset.
Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.
The reason will be displayed to describe this comment to others. Learn more.
Follow-up Comments
Share the output filesystem when servers share a compiler.
2365-2365: Major · Functional Correctness · Heavy lift
Share the output filesystem when servers share a compiler.
When multiple dev servers use default multi-compiler mode, each middleware instance creates its own memfs and assigns it to the shared child compilers. The last assignment becomes the compiler output filesystem. Earlier servers retain their own middleware filesystem and can serve no compiled assets.
Pass one shared devMiddleware.outputFileSystem to all servers, or keep each server bound to its own compiler.
File: No configuration file from this repository applied
Commit: Unavailable
Layers: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: 2ac892bc-3571-4776-a2f0-ada662cdcbde
Commits
Reviewing files that changed from the base of the PR and between 6514be0 and d230ec8.
Files selected for processing: 1
packages/webpack-cli/src/webpack-cli.ts
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.
@coderabbitai on "Share the output filesystem when servers share a compiler": it is right that each middleware creates its own memfs and assigns it to the compilers, so the last assignment wins. But the middleware doesn't serve from its own context.outputFileSystem. For each request it reads from compilation.compiler.outputFileSystem (webpack-dev-middleware/dist/middleware.js, the getFilenameFromUrl path). That is the filesystem webpack actually emitted to, so every dev server serves every bundle.
test/serve/per-compiler/serve-per-compiler.test.js covers this. "should serve all compilers by every dev server by default" starts two dev servers in the default multi-compiler mode and gets 200 for every bundle from every server (app:app, app:admin, admin:app, admin:admin). The same test also runs with webpack before 5.112. So I'm leaving it as is.
For best results, initiate chat on the files or code changes.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
ast-grep outline test/serve/per-compiler/serve-per-compiler.test.js --items all
sed -n '1,200p' test/serve/per-compiler/serve-per-compiler.test.js
ast-grep outline test/serve/per-compiler/old-webpack.js --items all
sed -n '1,80p' test/serve/per-compiler/old-webpack.js
Length of output: 5694
@alexander-akait You’re right. My finding assumed that middleware serves files from its own context.outputFileSystem. With requests using compilation.compiler.outputFileSystem, separate middleware filesystems do not establish the serving failure I claimed. I withdraw “Share the output filesystem when servers share a compiler.” No change is needed for this finding.
I inspected test/serve/per-compiler/serve-per-compiler.test.js. Both default-mode tests assert HTTP 200 for all four server/bundle pairs. The compatibility test uses old-webpack.js to report version 5.111.0 and exercise the older-version CLI path; it does not load an older webpack implementation. I did not run the tests.
CI on d230ec8: "Test - windows-latest - Node v26.x, Webpack latest, DevServer latest" failed only because test/info/output.test.js › "gets info text by default" ran past the 180 s timeout. webpack info hangs at random on Windows runners while envinfo detects browsers, and this also happens on main. The Windows workaround was dropped from this PR on purpose (d230ec8), so no fix is ported. Fail-fast cancelled the other jobs, so I'll re-run the failed and cancelled jobs once the run completes.
* test: make the generator and dev server tests stable
The `create-webpack-app` tests install real packages, and a registry
hiccup made npm fail, which the tests only reported as a missing line in
stdout. npm now prefers its cache and retries requests more, without the
audit, fund and update-notifier requests, and a failed install fails the
test with npm's error.
The dev server tests took ports from the ephemeral range, where outgoing
connections or another Jest worker could take them before the server
listened. Each worker now takes ports from its own range below the
ephemeral ones.
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
* test: keep npm's default retries for the generator tests
More retries made a failing install outlast the test timeout, which then
hid npm's error.
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
* test: keep the port ranges of Jest workers within valid ports
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
* test: give up to 120 Jest workers their own port range
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
* fix: don't detect browsers in `webpack info` on Windows
On Windows, `envinfo` starts the browsers themselves to get their versions,
which can hang, e.g. the `info` tests time out on Windows CI runners.
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
* test: don't let npm prefer its cache in the generator tests
With `prefer-offline`, npm used cached metadata that lacked a version a
freshly fetched package depended on, and the install failed with
ETARGET (`@babel/generator@^8.0.10`) on Windows CI.
Also add a changeset for not detecting browsers in `webpack info` on
Windows.
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
* chore: move the `webpack info` changeset to #4847
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
* revert: detect browsers in `webpack info` on Windows again
The `info` tests time out on Windows only at random, so keep detecting
browsers there.
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
* test: give each Jest worker its own Yarn cache in the generator tests
The init, loader and plugin tests install with Yarn in parallel Jest
workers. Yarn 1 can't fill one cache from two installs at the same time,
and an install failed on macOS CI reading a half-written package.json
from the shared cache.
Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Run webpack-dev-server as a compiler plugin when it exposes
apply(). The CLI starts watch compilation, prints build stats, and closes the compiler on signals or stdin EOF so the server stops through its shutdown hooks. Older dev servers keep their existingstart()/stop()flow.The plugin path disables the server's own signal handlers to avoid duplicate shutdown and preserves failure exit codes. A later successful rebuild clears a failure exit code. Duplicate ports in
devServerconfigurations are detected before listening starts.The middleware leaves printing stats to the CLI, so the dev server's
devMiddleware.statsreplace the configuration'sstats, and the CLI sets them up the same way as configuration stats (a boolean or a preset string becomes an object, pluscolors).webpack-dev-server only logs a failure of the plugin's asynchronous
listen()phase, e.g. a port that is already in use. The CLI wrapslisten()of each dev server plugin it creates and, on a failure, sets the exit code to 2 and closes the compiler, the same as dev servers without plugin support. A TODO notes to remove the wrapper once webpack-dev-server reports listen failures to the compiler. Setup errors that reach the watch callback also exit with code 2.Multi-compiler mode and
--dev-server-per-compilerwebpack-dev-server 6 supports a multi-compiler both through
apply(multiCompiler)and as a plugin in each configuration. Verified with webpack-dev-server 6.1 and two configurations with their owndevServer:apply(multiCompiler)(default)apply(childCompiler)(--dev-server-per-compiler)new DevServer()in each config'spluginsBy default, every dev server keeps serving all compilers, the same as dev servers without plugin support do. The new
--dev-server-per-compileroption ofwebpack serveapplies each dev server only to the compiler that defines it, and prints each compiler's stats using thedevMiddleware.statsof its own dev server. Dev servers without plugin support (v5) ignore the option with a warning.The workaround for dev servers without plugin support (the
applycheck, the v5 typings cast and thestart()/stop()path) is marked with TODOs to remove in the next major release. webpack-dev-server is bumped to^6.1.0for development (validation inapply()and stopping beforelisten()).What kind of change does this PR introduce?
Feature, with shutdown fixes and regression tests.
Did you add tests for your changes?
Yes. Tests cover compilation errors and warnings, middleware setup failures, a port already in use, exit status and shutdown through SIGINT/SIGTERM/stdin, resetting the failure exit code after a successful rebuild, duplicate signal handlers, serving rebuilt bundles, middleware stats, independent multi-compiler rebuilds with distinct watch options and shutdown hooks, webpack before 5.112, and which compilers each dev server serves with and without
--dev-server-per-compiler. The duplicate-port snapshot is updated for validation before startup.Does this PR introduce a breaking change?
No intended breaking change. Existing webpack-dev-server 5 integration remains on the imperative path. In plugin mode, the CLI owns signal handling even when
setupExitSignals: trueis configured, and shutdown preserves compilation failure status.If relevant, what needs to be documented once your changes are merged or what have you already documented?
Added a minor changeset describing compiler plugin support and the
--dev-server-per-compileroption, and regeneratedSERVE-OPTIONS-v6.md. The serve documentation should explain CLI ownership of watch compilation, stats output, and shutdown with webpack-dev-server 6.Use of AI
Codex assisted with the initial implementation, regression tests, code review, and this PR description. Claude Code rebased the PR onto
main, verified the multi-compiler behaviour, added the--dev-server-per-compileroption, and fixed the listen-failure and exit-code handling and the flaky tests.Summary by CodeRabbit
webpack servenow supports webpack-dev-server 6 as a compiler plugin, including watch builds and graceful shutdown.--dev-server-per-compilerto limit each server to its own compiler.--dev-server-per-compiler.