Skip to content

fix: watch future glob matches without mutating options - #5731

Open
OskarEichler wants to merge 7 commits into
webpack:mainfrom
OskarEichler:codex/glob-watch-lifecycle
Open

fix: watch future glob matches without mutating options#5731
OskarEichler wants to merge 7 commits into
webpack:mainfrom
OskarEichler:codex/glob-watch-lifecycle

Conversation

@OskarEichler

@OskarEichler OskarEichler commented Aug 28, 2026

Copy link
Copy Markdown

Fixes

  • Watch each glob's static parent and filter file registrations, so future matching files/directories remain discoverable.
  • Match ignored globs against future paths, preserving function, regex and matcher-object ignores.
  • Keep explicit paths in mixed arrays and apply negative patterns as exclusions.
  • Clone watch options instead of mutating ignored; frozen options are supported.
  • Remove synchronous filesystem glob expansion from watch setup and deduplicate watched roots.

Compatibility

No API/engine removal. Adds picomatch ^4.0.4 as a direct dependency (already present transitively in the lockfile). Relative patterns remain cwd-relative; events retain chokidar's cwd semantics. Newly created matches now trigger live reload when subsequently edited. Directory watching is necessary to discover future matches; nonmatching files are excluded.

Verification

All 32 unchanged upstream watch-files tests pass. Ten real filesystem scenarios pass: absolute/cwd paths, negative patterns, function/regex/object ignores, mixed literal/glob paths, missing roots, escaped bracket paths and frozen options. Original source reproduces missed new files and option mutation. Build and full lint/types/formatting/spelling pass. Windows filesystem execution remains unverified.

Audit scope

This is a focused, independently based change from a broader source review at f804962. The combined frozen-source run executed 1,002 tests: 951 passed, 43 failed, one cancelled, seven skipped. Failures were traced to the separately proposed overlay DOM snapshots, reconnect-disabled test expectation and IPv6 host-test assumptions; it is not represented as a green full suite. Relevant focused results are listed above. No checked-in tests/specs/snapshots were added or modified. Hosted CI and the full OS/Node matrix remain pending.

Summary by CodeRabbit

  • New Features

    • File watching now automatically includes files created after the development server starts when they match configured glob patterns.
    • Ignore patterns continue to apply to newly created files and paths.
    • Watch configurations remain unchanged while monitoring dynamic file matches.
  • Bug Fixes

    • Improved handling of negated patterns and dynamically added files in file watch configurations.

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: f845ef6e-72ee-4522-8334-7c8a112b6246

📥 Commits

Reviewing files that changed from the base of the PR and between e4f44a2 and fda6370.

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

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.


Walkthrough

Server.watchFiles now classifies watch paths into dynamic patterns, ignored patterns, literal paths, and watch roots. It uses tinyglobby.globSync to refresh matched and ignored path sets. Chokidar refreshes these sets after files or directories are added. The change handler emits static-changed only for accepted paths. A patch changeset documents this behavior.

Merge Risk: 🔵 Low · up to fda63

The watch behavior may still mishandle absolute root-level globs and negated dynamic ignore patterns, which could cause matching files to be missed or incorrectly included during live reload. The change is mergeable with explicit owner awareness and follow-up on these bounded path-filtering cases.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: fixing future glob matching while avoiding mutation of watch options.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
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.
Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1 files.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

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

Comment thread package.json Outdated
"launch-editor": "^2.14.1",
"open": "^11.0.0",
"p-retry": "^8.0.0",
"picomatch": "^4.0.4",

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.

We should avoid extra packages here

@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.

We don't need multiple glob related packages

@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fda6370

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

This PR includes changesets to release 1 package
Name Type
webpack-dev-server 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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: fb40762e-ab2b-40e4-9a26-d895395863ea

📥 Commits

Reviewing files that changed from the base of the PR and between 1af4288 and 4e6f8a3.

📒 Files selected for processing (1)
  • lib/Server.js

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread lib/Server.js Outdated
Comment thread lib/Server.js Outdated
Comment on lines +3453 to +3457
const base = prefix.endsWith("/")
? prefix.slice(0, -1)
: path.posix.dirname(prefix);

const expand = (/** @type {string} */ item) => {
const posix = toPosix(item);
return isDynamicPattern(posix)
? globSync(posix, { cwd, absolute: true }).map(toNative)
: item;
return path.resolve(cwd, (base || ".").replace(/\\(.)/g, "$1"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve the filesystem root in getWatchRoot.

For an absolute glob such as /*, prefix is /. This branch removes the only separator, sets base to ., and returns cwd instead of /. Chokidar then watches the wrong root, so the absolute glob misses its intended matches. Preserve platform root prefixes before resolving the watch root.

🧰 Tools
🪛 ESLint

[error] 3457-3457: Prefer String#replaceAll() over String#replace().

(unicorn/prefer-string-replace-all)

Comment thread lib/Server.js Outdated
Comment thread lib/Server.js Outdated
? []
: [originalIgnored]) {
if (typeof item === "string" && isDynamicPattern(toPosix(item))) {
ignored.push((file) => matchesGlob(file, toPosix(item)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- Server.js relevant implementation ---'
sed -n '3380,3535p' lib/Server.js
printf '%s\n' '--- package/runtime declarations ---'
if [ -f package.json ]; then
  sed -n '1,180p' package.json
fi
printf '%s\n' '--- relevant symbols and path API usage ---'
rg -n -C 3 'watchFiles|watchOptions|matchesGlob|getWatchRoot|ignored|toPosix|anymatch|picomatch' lib package.json test tests 2>/dev/null | head -n 500

Repository: webpack/webpack-dev-server

Length of output: 40011


🌐 Web query:

Node.js v22.15.0 path.matchesGlob negation leading ! contract and Chokidar 5 ignored anymatch picomatch string matcher negation

💡 Result:

Node.js path.matchesGlob(path, pattern) does not natively support glob negation using a leading ! character [1][2][3]. As of Node.js v22.15.0, path.matchesGlob is an experimental API (marked stable in later versions) [2][4] designed to test whether a given string path matches a provided glob pattern string [1][4]. Its implementation expects a standard glob pattern and does not interpret leading ! as a negation operator [1][4]. Regarding Chokidar 5 and negation in ignored patterns: 1. Chokidar relies on anymatch to process its ignored option [5][6]. 2. Historically, while anymatch has supported negation patterns (e.g., ['/node_modules/', '!/node_modules/important/']), using them in Chokidar can be problematic because Chokidar performs an initial check against the parent path [7][6]. If a parent directory is matched by an "ignore" pattern, Chokidar often stops traversal immediately and does not evaluate subsequent "negation" (inclusion) patterns for children of that directory [7][6]. 3. For reliable results when needing to ignore most of a folder but keep specific files, the recommended approach is to provide a function to the ignored option rather than using glob strings with negation [8][9]. This allows for granular, programmatic control: javascript ignored: (path, stats) => { // Return true to ignore, false to watch if (path.includes('node_modules') && !path.endsWith('important-file.js')) { return true; } return false; } 4. Recent versions of Chokidar have emphasized using functions or regexes in the ignored field to avoid the ambiguity and limitations of glob-based negation string matching [8][9].

Citations:


Preserve negation semantics for dynamic watchOptions.ignored strings.

When a dynamic ignored string starts with !, the branch at lib/Server.js:3498 passes that pattern unchanged to path.matchesGlob. path.matchesGlob treats the leading ! literally, so the matcher can return false for files that Chokidar’s anymatch/picomatch semantics would ignore. Strip and apply the negation before creating the matcher.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants