Skip to content

fix: follow a replaced inputFileSystem when purging in watch - #21643

Open
bjohansebas wants to merge 3 commits into
mainfrom
fix/watch-purge-replaced-input-filesystem
Open

bjohansebas wants to merge 3 commits into
mainfrom
fix/watch-purge-replaced-input-filesystem

Conversation

@bjohansebas

@bjohansebas bjohansebas commented Aug 9, 2026 •

Copy link
Copy Markdown
Member

Summary

NodeWatchFileSystem captures the input filesystem it is constructed with and purges that one on every change, so assigning compiler.inputFileSystem afterwards leaves the watcher purging an orphaned cache and watch keeps rebuilding from stale sources indefinitely. MultiCompiler's own inputFileSystem setter hands one filesystem to every child, which is exactly this shape.

Nobody is broken today, because the projects that hit this already worked around it — and the workarounds are the argument for fixing it. Next.js shares one input filesystem across its dev-server MultiCompiler children, then unregisters NodeEnvironmentPlugin's beforeRun tap via hooks.beforeRun.intercept and purges the whole cache on every done (hot-reloader-webpack.ts). morjs' ReplaceInputFileSystemPlugin deep-imports webpack/lib/node/NodeWatchFileSystem to rebuild the watcher by hand (webpack.ts). Both become unnecessary, and Next.js could purge incrementally instead of discarding its cache each build.

This also unblocks sharing one CachedInputFileSystem across MultiCompiler children in core: on 3000 modules with 4 configs it takes stat 13616 -> 3404, readFile and readlink 12808 -> 3202, and the retained cache 20.3 MB -> 5.1 MB. Before this fix that sharing produced stale bundles.

What kind of change does this PR introduce?

fix

Did you add tests for your changes?

Yes — test/WatchInputFileSystem.test.js covers a replaced inputFileSystem on a single Compiler and on every child of a MultiCompiler, both of which fail without the fix, plus one pinning that a watcher the user installed is never re-pointed.

Does this PR introduce a breaking change?

No. NodeEnvironmentPlugin only ever writes to the watcher it installed itself, so a watchFileSystem supplied or wrapped by the user is unaffected.

If relevant, what needs to be documented once your changes are merged or what have you already documented?

n/a

Use of AI

Yes. Claude Code found the bug while measuring input-filesystem duplication across MultiCompiler children, reduced it to a minimal reproduction, wrote the fix and the tests, and researched the affected ecosystem usages cited above. Every claim here was reproduced locally or read from the linked source.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed watch mode so rebuilds continue working correctly after the compiler’s input filesystem is replaced.
    • Improved watch reliability for both single-compiler and multi-compiler setups.
    • Preserved the configured input filesystem for custom watch filesystems.
  • Tests

    • Added coverage for filesystem replacement during watch rebuilds, including multi-compiler scenarios and custom watch filesystems.

NodeWatchFileSystem captures the input filesystem it is constructed with
and purges that one on every change. Assigning `compiler.inputFileSystem`
afterwards — as MultiCompiler's setter does for every child, and as
virtual-filesystem plugins do — left the watcher purging an orphaned
cache, so watch kept rebuilding from stale sources indefinitely.

Re-point the watcher at the compiler's current filesystem on watchRun,
and only while the watcher is still the one this plugin installed.
@changeset-bot

changeset-bot Bot commented Aug 9, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2bdf51a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
webpack Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Aug 9, 2026 •

Copy link
Copy Markdown
Contributor

This PR is packaged and the instant preview is available (2bdf51a).

Install it locally:

  • npm
npm i -D webpack@https://pkg.pr.new/webpack@2bdf51a
  • yarn
yarn add -D webpack@https://pkg.pr.new/webpack@2bdf51a
  • pnpm
pnpm add -D webpack@https://pkg.pr.new/webpack@2bdf51a

@coderabbitai

coderabbitai Bot commented Aug 9, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

NodeEnvironmentPlugin.apply stores the created NodeWatchFileSystem and refreshes its input filesystem during watchRun. Tests cover replaced input filesystems for single compilers, MultiCompiler, and user-supplied watch filesystems. A changeset documents the fix.

Changes

Watch input filesystem lifecycle

Layer / File(s) Summary
Refresh the active watcher
lib/node/NodeEnvironmentPlugin.js
NodeEnvironmentPlugin.apply stores the NodeWatchFileSystem, assigns it to the compiler, and updates its input filesystem during watchRun.
Validate watch rebuilds
test/WatchInputFileSystem.test.js, .changeset/108-watch-replaced-input-filesystem.md
Tests verify rebuilds after replacing the input filesystem for single compilers and MultiCompiler. Another test verifies that a user-supplied watch filesystem keeps its original input filesystem. The changeset records the patch.
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title uses valid Conventional Commit syntax and accurately describes the watch-mode fix, but the branch prefix is not provided for verification. Provide the branch name or confirm that its prefix is fix.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 9, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.39%. Comparing base (788ffe0) to head (2bdf51a).
⚠️ Report is 1232 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #21643      +/-   ##
==========================================
+ Coverage   94.23%   94.39%   +0.16%     
==========================================
  Files         628      628              
  Lines       79952    80621     +669     
  Branches    23676    23812     +136     
==========================================
+ Hits        75344    76106     +762     
+ Misses       4608     4515      -93     
Flag Coverage Δ
css-parsing 25.87% <75.00%> (-0.08%) ⬇️
html5lib 30.25% <75.00%> (+2.88%) ⬆️
integration 88.62% <100.00%> (-0.22%) ⬇️
test262 49.09% <75.00%> (-0.10%) ⬇️
unit 51.61% <75.00%> (-0.54%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Aug 9, 2026 •

Copy link
Copy Markdown
Contributor

Generated code size

Comparing 2bdf51a against 51470f3. Merging this PR will change the code webpack generates.

Changed New Deleted Unchanged Raw change
Cases 457 20 0 1047 🟢 ↓ -25.75 KiB
Assets 636 39 0 5265 🟢 ↓ -25.75 KiB
Runtimes 2 5 0 1856 —
675 asset(s) changed size, biggest 20 by raw change
Asset Before After Change Gzip (9) Brotli (11) Zstd (19)
➕ html/url-hints-rules __html_[hash]_0.mjs — 9.10 KiB +9.10 KiB (new) new new new
➕ side-effects/side-effects-globs bundle0.js — 4.74 KiB +4.74 KiB (new) new new new
➕ context-modules/glob-identifiers bundle0.js — 3.25 KiB +3.25 KiB (new) new new new
➕ html/url-hints-rules page.mjs — 2.98 KiB +2.98 KiB (new) new new new
➕ cjs-tree-shaking/require-member-access-trimming bundle0.js — 2.45 KiB +2.45 KiB (new) new new new
➕ cjs-tree-shaking/unused-reexport-forms-drop bundle0.js — 2.30 KiB +2.30 KiB (new) new new new
➕ cjs-tree-shaking/unused-eager-reexport-keeps-effect bundle0.js — 2.29 KiB +2.29 KiB (new) new new new
➕ ignore/unused-getter-reexport bundle0.js — 2.22 KiB +2.22 KiB (new) new new new
➕ cjs-tree-shaking/unused-reexport-package-sideEffects-drop bundle0.js — 2.21 KiB +2.21 KiB (new) new new new
➕ cjs-tree-shaking/unused-reexport-concatenated-drop bundle0.js — 2.10 KiB +2.10 KiB (new) new new new
➕ cjs-tree-shaking/unused-getter-reexport-drop bundle0.js — 2.08 KiB +2.08 KiB (new) new new new
➕ html/url-hints-rules test.js — 1.97 KiB +1.97 KiB (new) new new new
➕ concatenate-modules/commonjs-exports-template bundle0.js — 1.86 KiB +1.86 KiB (new) new new new
➕ process-assets/asset-chunk-index main.js — 1.61 KiB +1.61 KiB (new) new new new
➕ html/webmanifest-parsing bundle0.mjs — 1.08 KiB +1.08 KiB (new) new new new
➕ externals/interop-identifier bundle0.js — 752 B +752 B (new) new new new
➕ clean/nested-directories bundle0.js — 623 B +623 B (new) new new new
➕ html/url-hints-rules page.html — 565 B +565 B (new) new new new
➕ no-parse/array-rule bundle0.js — 564 B +564 B (new) new new new
➕ no-parse/string-rule bundle0.js — 479 B +479 B (new) new new new
… 655 more asset(s), see the uploaded report
7 runtime(s) changed which runtime modules they carry
Runtime Modules Added Removed
➕ html/url-hints-rules __html_[hash]_0 0 → 6 asset prefetch, asset preload, hasOwnProperty shorthand, import chunk loading, publicPath, startup asset hints —
➕ context-modules/glob-identifiers main 0 → 4 create fake namespace object, define property getters, hasOwnProperty shorthand, make namespace object —
🔴 ↑ hash-length/output-filename contenthash in async-node with length/main 6 → 7 jsonp chunk loading, load script importScripts chunk loading
🔴 ↑ hash-length/output-filename contenthash in webpack/main 6 → 7 jsonp chunk loading, load script importScripts chunk loading
➕ cjs-tree-shaking/unused-reexport-concatenated-drop main 0 → 1 concatenation wrap —
➕ concatenate-modules/commonjs-exports-template main 0 → 1 concatenation wrap —
➕ externals/interop-identifier main 0 → 1 make namespace object —

Built test/configCases with the defaults a user gets: 1524 case(s), 5940 asset(s), 38 emitted nothing.

The guard skipping the update when `watchFileSystem` was replaced never
did anything: the tap only ever writes to the instance this plugin holds,
so a watcher the user installed is out of reach either way. Drop it, and
cover the contract against the rewrite that would break it — assigning
through `compiler.watchFileSystem` instead.

`fs.rmSync` needs Node.js 14.14 and the matrix goes down to 10.x, so the
fixture teardown uses unlink + rmdir.
@codspeed

codspeed Bot commented Aug 9, 2026 •

Copy link
Copy Markdown

Merging this PR will regress 1 benchmark

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 3 improved benchmarks
❌ 1 regressed benchmark
✅ 216 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ Memory benchmark "many-modules-esm", scenario '{"name":"mode-development","mode":"development"}' 821.1 KB 2,303.2 KB -64.35%
⚡ Memory benchmark "devtool-eval", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 949.8 KB 476.9 KB +99.18%
⚡ Memory benchmark "many-modules-commonjs", scenario '{"name":"mode-production","mode":"production"}' 10.7 MB 8.9 MB +21.27%
⚡ Memory benchmark "css-modules", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 917.6 KB 761.4 KB +20.51%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing fix/watch-purge-replaced-input-filesystem (2bdf51a) with main (5c5d009)

Open in CodSpeed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c1a40c26-1f10-4888-bf86-2b477b26a640

📥 Commits

Reviewing files that changed from the base of the PR and between 4f0e14d and c612b19.

📒 Files selected for processing (2)
  • lib/node/NodeEnvironmentPlugin.js
  • test/WatchInputFileSystem.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/node/NodeEnvironmentPlugin.js

Comment on lines +225 to +228
const watching = compiler.watch(watchOptions, (err) => {
if (finished) return;
finished = true;
if (err) return done(err);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- target test ---'
cat -n test/WatchInputFileSystem.test.js | sed -n '180,255p'

printf '%s\n' '--- watcher implementation references ---'
rg -n -C 4 'class NodeWatchFileSystem|watch\(|close\(' lib/node/NodeWatchFileSystem.js test/WatchInputFileSystem.test.js

Repository: webpack/webpack

Length of output: 7242


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- test setup and watch-error controls ---'
cat -n test/WatchInputFileSystem.test.js | sed -n '1,180p'
rg -n -C 5 'watchFileSystem|watch\([^;]*err|callback\(.*err|watch\(\{' lib test | head -n 240

printf '%s\n' '--- compiler watch implementation ---'
rg -n -C 8 'watch\(watchOptions|watch\(watchOptions|class Watching|watchFileSystem\.watch|callback\(err' lib/Compiler.js lib/Watching.js lib

Repository: webpack/webpack

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- Compiler.watch ---'
cat -n lib/Compiler.js | sed -n '536,558p'

printf '%s\n' '--- Watching lifecycle and watch error handling ---'
cat -n lib/Watching.js | sed -n '28,190p'
cat -n lib/Watching.js | sed -n '190,360p'
cat -n lib/Watching.js | sed -n '360,520p'

printf '%s\n' '--- NodeWatchFileSystem watch return and close contract ---'
cat -n lib/node/NodeWatchFileSystem.js | sed -n '30,170p'

Repository: webpack/webpack

Length of output: 23644


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- Watching.close implementation ---'
cat -n lib/Watching.js | sed -n '477,590p'

printf '%s\n' '--- tests that force watch or watchRun errors ---'
rg -n -C 8 'watchRun|watchFileSystem\s*=|new NodeWatchFileSystem|done\(err\).*close|close\(\(\).*done' test | head -n 260

printf '%s\n' '--- watch callback type and all NodeWatchFileSystem callback error paths ---'
rg -n -C 5 'WatchMethod|callback\(err|callback\(null' lib/util/fs.d.ts lib/util/fs.js lib/node/NodeWatchFileSystem.js

Repository: webpack/webpack

Length of output: 23604


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path

test = Path("test/WatchInputFileSystem.test.js").read_text()
watching = Path("lib/Watching.js").read_text()
node_watch = Path("lib/node/NodeWatchFileSystem.js").read_text()

checks = {
    "test currently completes the error branch directly": (
        "if (err) return done(err);" in test
    ),
    "Watching forwards watch errors to the handler": (
        "if (err) {" in watching
        and "return this.handler(err);" in watching
    ),
    "Watching.close closes the active watcher before its callback": (
        "if (this.watcher) {" in watching
        and "this.watcher.close();" in watching
        and "this._closeCallbacks = [];" in watching
    ),
    "NodeWatchFileSystem.close closes the underlying watcher": (
        "this.watcher.close();" in node_watch
        and "this.watcher = null;" in node_watch
    ),
}

for name, passed in checks.items():
    print(f"{'PASS' if passed else 'FAIL'}: {name}")

assert all(checks.values())
print("Conclusion: an error delivered to the test callback can be followed by Watching.close(), and close performs watcher cleanup before invoking its close callback.")
PY

Repository: webpack/webpack

Length of output: 551


Close the watcher before reporting err. When the callback receives an error, call watching.close(() => done(err)) so watcher cleanup completes before Jest finishes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True

@alexander-akait

Copy link
Copy Markdown
Member

Please fix lint

A MultiCompiler reports a cycle per child that rebuilt, so asserting on
the second call to the handler read one child's bundle before the other
had rebuilt — which is what the Bun runtime hit. Wait for every bundle
to carry the change instead, bounded by a deadline that names the
failure, and type the watching a concurrent call can leave undefined.
@github-actions github-actions Bot added the area: runtime Emitted runtime and chunk loading across targets (lib/runtime, lib/web, lib/node, lib/esm) label Aug 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging fix/watch-purge-replaced-input-filesystem into main will be
99.29%
Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
bin
   webpack.js98.82%100%100%98.82%103
examples
   build-common.js100%100%100%100%
   buildAll.js100%100%100%100%
   examples.js100%100%100%100%
   template-common.js98.21%100%100%98.21%72
examples/custom-javascript-parser
   test.filter.js100%100%100%100%
examples/custom-javascript-parser/internals
   acorn-parse.js100%100%100%100%
   meriyah-parse.js100%100%100%100%
   oxc-parse.js100%100%100%100%
examples/markdown
   webpack.config.mjs100%100%100%100%
examples/module-federation
   test.filter.js100%100%100%100%
examples/reexport-components
   test.filter.js100%100%100%100%
examples/typescript
   test.filter.js100%100%100%100%
examples/typescript-non-erasable
   test.filter.js50%100%100%50%5
examples/virtual-modules
   test.filter.js100%100%100%100%
examples/wasm-bindgen-esm
   test.filter.js100%100%100%100%
examples/wasm-complex
   test.filter.js100%100%100%100%
examples/wasm-emscripten
   test.filter.js100%100%100%100%
examples/wasm-simple
   test.filter.js100%100%100%100%
examples/wasm-simple-source-phase
   test.filter.js100%100%100%100%
lib
   APIPlugin.js100%100%100%100%
   AsyncDependenciesBlock.js100%100%100%100%
   AutomaticPrefetchPlugin.js100%100%100%100%
   BannerPlugin.js100%100%100%100%
   Cache.js98.21%100%100%98.21%101
   CacheFacade.js100%100%100%100%
   Chunk.js99.72%100%100%99.72%39
   ChunkGraph.js100%100%100%100%
   ChunkGroup.js100%100%100%100%
   ChunkTemplate.js100%100%100%100%
   CircularModulesPlugin.js98.81%100%100%98.81%136
   CleanPlugin.js99.12%100%100%99.12%214, 234
   CodeGenerationResults.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.42%100%100%98.42%1648, 1967, 1974, 1982, 2004, 2007, 2946, 3425–3426, 3458, 4162, 4192, 4245–4246, 4250, 4255, 4271–4272, 4286–4287, 4292–4293, 4773, 4799, 527, 532, 5607, 5639, 5656, 5672, 5688, 5703, 5728–5729, 5731, 6061, 6066, 6072, 6075, 6082, 6094, 6096, 6100, 6116, 6131, 6163, 6217, 6241, 6356, 778–779
   Compiler.js99.56%100%100%99.56%1159–1160, 1168
   ConcatenationScope.js99.12%100%100%99.12%300
   ConditionalInitFragment.js100%100%100%100%
   ConstPlugin.js100%100%100%100%
   ContextExclusionPlugin.js100%100%100%100%
   ContextModule.js99.88%100%100%99.88%1461
   ContextModuleFactory.js97.20%100%100%97.20%266, 435, 456, 461, 501, 512, 514, 518, 527–528
   ContextReplacementPlugin.js100%100%100%100%
   DefinePlugin.js99.08%100%100%99.08%1080, 176–177, 193, 212, 286
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.51%100%100%98.51%480, 527
   DependencyTemplate.js100%100%100%100%
   DependencyTemplates.js100%100%100%100%
   DotenvPlugin.js98.41%100%100%98.41%378, 391–392
   DynamicEntryPlugin.js100%100%100%100%
   EntryOptionPlugin.js100%100%100%100%
   EntryPlugin.js100%100%100%100%
   Entrypoint.js100%100%100%100%
   EnvironmentPlugin.js97.14%100%100%97.14%49
   ErrorHelpers.js100%100%100%100%
   EvalDevToolModulePlugin.js100%100%100%100%
   EvalSourceMapDevToolPlugin.js100%100%100%100%
   ExportsInfo.js100%100%100%100%
   ExportsInfoApiPlugin.js100%100%100%100%
   ExternalModule.js98.67%100%100%98.67%1216, 1219, 515–519, 521, 667
   ExternalModuleFactoryPlugin.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FileSystemInfo.js99.16%100%100%99.16%1267, 1269–1274, 1281, 1284, 182, 2502–2503, 2506, 2517, 2528, 2539, 280, 3976, 3991, 4015
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.36%100%100%98.36%504, 513, 516, 520, 532
   FlagDependencyUsagePlugin.js100%100%100%100%
   FlagEntryExportAsUsedPlugin.js100%100%100%100%
   Generator.js100%100%100%100%
   HotModuleReplacementPlugin.js100%100%100%100%
   HotUpdateChunk.js100%100%100%100%
   IgnorePlugin.js100%100%100%100%
   IgnoreWarningsPlugin.js100%100%100%100%
   InitFragment.js100%100%100%100%
   JavascriptMetaInfoPlugin.js100%100%100%100%
   LazyBarrel.js100%100%100%100%
   LibraryTemplatePlugin.js100%100%100%100%
   LoaderOptionsPlugin.js100%100%100%100%
   LoaderTargetPlugin.js100%100%100%100%
   MainTemplate.js100%100%100%100%
   ManifestPlugin.js100%100%100%100%
   Module.js98.51%100%100%98.51%1303, 1308, 1368, 1382, 1444, 1453
   ModuleFactory.js100%100%100%100%
   ModuleFilenameHelpers.js98.90%100%100%98.90%111, 113
   ModuleGraph.js99.78%100%100%99.78%1170
   ModuleGraphConnection.js100%100%100%100%
   ModuleInfoHeaderPlugin.js100%100%100%100%
   ModuleNotFoundError.js100%100%100%100%
   ModuleProfile.js100%100%100%100%
   ModuleSourceTypeConstants.js100%100%100%100%
   ModuleTemplate.js100%100%100%100%
   ModuleTypeConstants.js100%100%100%100%
   MultiCompiler.js99.72%100%100%99.72%716
   MultiStats.js100%100%100%100%
   MultiWatching.js100%100%100%100%
   NoEmitOnErrorsPlugin.js100%100%100%100%
   NodeStuffPlugin.js100%100%100%100%
   NormalModule.js97.98%100%100%97.98%1020, 1023, 1040, 1057, 1305, 1339, 1355, 1803, 2100, 2105–2115, 34
   NormalModuleFactory.js98.72%100%100%98.72%1123, 1391, 1402, 1412, 1463–1465, 1472, 526, 538
   NormalModuleReplacementPlugin.js100%100%100%100%
   NullFactory.js100%100%100%100%
   OptimizationStages.js100%100%100%100%
   OptionsApply.js100%100%100%100%
   Parser.js100%100%100%100%
   PlatformPlugin.js100%100%100%100%
   PrefetchPlugin.js100%100%100%100%
   ProgressPlugin.js99.80%100%100%99.80%691
   ProvidePlugin.js100%100%100%100%
   RawModule.js100%100%100%100%
   RecordIdsPlugin.js100%100%100%100%
   RequestShortener.js100%100%100%100%
   ResolverFactory.js100%100%100%100%
   RuntimeGlobals.js100%100%100%100%
   RuntimeModule.js100%100%100%100%
   RuntimePlugin.js100%100%100%100%
   RuntimeTemplate.js100%100%100%100%
   SelfModuleFactory.js100%100%100%100%
   SingleEntryPlugin.js100%100%100%100%
   SourceMapDevToolModuleOptionsPlugin.js100%100%100%100%
   SourceMapDevToolPlugin.js98.63%100%100%98.63%220, 224, 226, 420, 431, 890
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js99.47%100%100%99.47%352–353
   UseStrictPlugin.js100%100%100%100%
   WarnCaseSensitiveModulesPlugin.js100%100%100%100%
   WarnDeprecatedOptionPlugin.js100%100%100%100%
   WarnNoModeSetPlugin.js100%100%100%100%
   WatchIgnorePlugin.js100%100%100%100%
   Watching.js100%100%100%100%
   WebpackError.js100%100%100%100%
   WebpackIsIncludedPlugin.js100%100%100%100%
   WebpackOptionsApply.js100%100%100%100%
   WebpackOptionsDefaulter.js100%100%100%100%
   buildChunkGraph.js99.87%100%100%99.87%371
   cli.js98.63%100%100%98.63%10, 119, 549, 581, 631, 905
   index.js99.73%100%100%99.73%184
   validateSchema.js94.67%100%100%94.67%100, 87, 89, 98
   webpack.js97.10%100%100%97.10%10, 263, 285, 287
lib/asset
   AssetBytesGenerator.js100%100%100%100%
   AssetBytesParser.js100%100%100%100%
   AssetGenerator.js100%100%100%100%
   AssetModule.js100%100%100%100%
   AssetModulesPlugin.js97.95%100%100%97.95%295, 319, 322, 42, 452, 47
   AssetParser.js100%100%100%100%
   AssetSourceGenerator.js100%100%100%100%
   AssetSourceParser.js100%100%100%100%
   RawDataUrlModule.js100%100%100%100%
   WebManifestGenerator.js100%100%100%100%
   WebManifestParser.js100%100%100%100%
lib/async-modules
   AsyncModuleHelpers.js100%100%100%100%
   AwaitDependenciesInitFragment.js100%100%100%100%
   InferAsyncModulesPlugin.js100%100%100%100%
   isGeneratorLowered.js100%100%100%100%
lib/bun
   BunTargetPlugin.js100%100%100%100%
lib/cache
   AddBuildDependenciesPlugin.js100%100%100%100%
   AddManagedPathsPlugin.js100%100%100%100%
   IdleFileCachePlugin.js97.92%100%100%97.92%75, 87, 95
   MemoryCachePlugin.js92%100%100%92%34, 43
   MemoryWithGcCachePlugin.js93.42%100%100%93.42%108, 122–123, 132, 90
   PackFileCacheStrategy.js96.52%100%100%96.52%1310, 1410, 1414, 1476, 1712, 1796, 1819, 1851, 675, 694, 704–706, 708, 724–725, 730, 733, 735, 740, 745, 770, 776, 810, 816, 822, 827, 838, 847, 852–853, 855, 872, 878–879, 881
   ResolverCachePlugin.js100%100%100%100%
   

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/WatchInputFileSystem.test.js`:
- Around line 155-158: Adjust the timeout handling in the watch test around the
deadline timer and its surrounding test so the Jest test-specific timeout
exceeds the full initial-build, watcher-close, and 15-second rebuild-wait
duration, or reduce the deadline to fit within the configured 30-second limit.
Keep the existing failure behavior in the deadline callback unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cd10bd1c-f0d6-41a0-871a-2bb01188ea20

📥 Commits

Reviewing files that changed from the base of the PR and between c612b19 and 2bdf51a.

📒 Files selected for processing (1)
  • test/WatchInputFileSystem.test.js
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • webpack/schema-utils (auto-detected)
  • webpack/tapable (auto-detected)

Comment on lines +155 to +158
if (err2) return finish(err2);
deadline = setTimeout(() => {
finish(new Error("the rebuild never picked the change up"));
}, 15000);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline test/WatchInputFileSystem.test.js --items all --type function

rg -n -C 6 \
  'jest\.setTimeout|testTimeout|15000|setTimeout' \
  test/WatchInputFileSystem.test.js package.json .

Repository: webpack/webpack

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- test file timeout-related lines ---'
rg -n -C 8 'jest\.setTimeout|testTimeout|15000|setTimeout|after(All|Each)|watcher\.close|close\(' test/WatchInputFileSystem.test.js

printf '%s\n' '--- repository Jest configuration ---'
rg -n -C 5 'testTimeout|setTimeout|jest\.setTimeout|slowTestThreshold|testMatch|testRegex' \
  package.json \
  jest.config.* \
  config \
  test 2>/dev/null | head -n 300

printf '%s\n' '--- relevant test block ---'
sed -n '110,190p' test/WatchInputFileSystem.test.js

Repository: webpack/webpack

Length of output: 21677


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- complete test setup and affected tests ---'
sed -n '1,270p' test/WatchInputFileSystem.test.js

printf '%s\n' '--- watcher close implementation and related tests ---'
rg -n -C 8 'class .*Watcher|close\(callback|close\(.*callback|watcher.*close|Watching.*close' lib test | head -n 400

printf '%s\n' '--- Jest timeout semantics in repository configuration ---'
sed -n '1,45p' jest.config.js
rg -n -C 4 'WatchInputFileSystem|testTimeout|jest\.setTimeout' test/WatchInputFileSystem.test.js test/setupTestFramework.js jest.config.js package.json

Repository: webpack/webpack

Length of output: 29681


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '477,565p' lib/Watching.js
sed -n '60,90p' lib/MultiWatching.js
sed -n '1465,1505p' lib/Compiler.js

Repository: webpack/webpack

Length of output: 3857


Give this test an explicit timeout margin.

jest.config.js sets a 30-second timeout, but the 15-second deadline starts after the initial build and watcher setup. Watching.close() can also wait for an active build before calling done. Set a test-specific timeout above the full operation time, or shorten the deadline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/WatchInputFileSystem.test.js` around lines 155 - 158, Adjust the timeout
handling in the watch test around the deadline timer and its surrounding test so
the Jest test-specific timeout exceeds the full initial-build, watcher-close,
and 15-second rebuild-wait duration, or reduce the deadline to fit within the
configured 30-second limit. Keep the existing failure behavior in the deadline
callback unchanged.

Source: MCP tools

@alexander-akait alexander-akait left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a breaking change

@Abhirup0 Abhirup0 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue being addressed is real—rebuilding against an orphaned CachedInputFileSystem after reassigning compiler.inputFileSystem causes watch rebuilds to serve stale files in multi-compiler and custom dev-server setups.

However, as @alexander-akait noted, modifying this in NodeEnvironmentPlugin across all setups is a breaking behavior change:

  1. Lifecycle Inconsistency with beforeRun: NodeEnvironmentPlugin explicitly guards beforeRun and shutdown with if (compiler.inputFileSystem === inputFileSystem). This was designed so custom filesystems aren't purged by Webpack's default node plugin. Reassigning watchFileSystem.inputFileSystem in watchRun breaks that assumption and forces all custom filesystems into NodeWatchFileSystem's purge(item, { exact }) calls.
  2. Wrapper Decoupling: Updating the closed-over watchFileSystem instance inside the watchRun hook ignores whether compiler.watchFileSystem has been replaced or wrapped by other plugins.

To move this forward, we should clarify the intended ownership: if Webpack core is to manage cache purging for user-supplied inputFileSystems in watch mode, should this also apply to beforeRun, or should MultiCompiler.prototype.inputFileSystem / the caller be responsible for synchronizing the watcher reference?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: runtime Emitted runtime and chunk loading across targets (lib/runtime, lib/web, lib/node, lib/esm)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants