Skip to content

perf(watcher): precompile ignored globs into one subtree-matching regex#14329

Open
stormslowly wants to merge 5 commits into
mainfrom
perf/watcher-ignored-precompiled-regex
Open

perf(watcher): precompile ignored globs into one subtree-matching regex#14329
stormslowly wants to merge 5 commits into
mainfrom
perf/watcher-ignored-precompiled-regex

Conversation

@stormslowly

@stormslowly stormslowly commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Why

On the macOS/Windows recursive-root watch the OS delivers an event for every path under the project — mostly deep node_modules / build-output that nobody registered. An event inside an ignored directory isn't a registered file, so find_associated_event bubbles it up to a registered parent and emits a [dir](Change) that looks like a real source change — a spurious rebuild that flakes watch-mode tests. The per-event filter must be cheap.

ignored was also only checked at registration, so these events reached Trigger::on_event unfiltered.

What

Do what watchpack does: translate each ignored glob to a regex ending in (?:$|/) so a directory match also covers its subtree, fold all patterns into one precompiled regex::Regex, and classify each event with a single is_matchO(1) per event, no ancestor walk. Applied at the event ingress and at registration (which absolutizes first so it filters the same absolute paths events report). Compiled once per watcher, reused across the rebuild loop.

Perf

benches/ignored_filter.rs: ~20–46× faster per event, ~30× on a 500-event burst. Matching normalizes \/, so it is separator-agnostic on Windows (portable test included).

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Rsdoctor Bundle Diff Analysis

Found 5 projects in monorepo, 5 projects with changes.

📊 Quick Summary
Project Total Size Gzip Size Change Gzip Change
popular-libs 1.7 MB 551.3 KB - -
react-10k 5.6 MB 1.3 MB - -
react-5k 2.7 MB 669.1 KB - -
ui-components 4.8 MB 1.4 MB - -
react-1k 822.8 KB 218.3 KB - -
📋 Detailed Reports (Click to expand)

📁 popular-libs

Path: ../build-tools-performance/cases/popular-libs/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 1.7 MB - -
🗜️ Gzip Size 551.3 KB - -
📄 JavaScript 1.7 MB - -
🎨 CSS 0 B - -
🌐 HTML 289.0 B - -
📁 Other Assets 0 B - -

📁 react-10k

Path: ../build-tools-performance/cases/react-10k/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 5.6 MB - -
🗜️ Gzip Size 1.3 MB - -
📄 JavaScript 5.6 MB - -
🎨 CSS 21.0 B - -
🌐 HTML 328.0 B - -
📁 Other Assets 0 B - -

📁 react-5k

Path: ../build-tools-performance/cases/react-5k/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 2.7 MB - -
🗜️ Gzip Size 669.1 KB - -
📄 JavaScript 2.7 MB - -
🎨 CSS 21.0 B - -
🌐 HTML 328.0 B - -
📁 Other Assets 0 B - -

📁 ui-components

Path: ../build-tools-performance/cases/ui-components/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 4.8 MB - -
🗜️ Gzip Size 1.4 MB - -
📄 JavaScript 4.7 MB - -
🎨 CSS 111.7 KB - -
🌐 HTML 328.0 B - -
📁 Other Assets 0 B - -

📁 react-1k

Path: ../build-tools-performance/cases/react-1k/dist/rsdoctor-data.json

⚠️ No baseline data found - Unable to perform comparison analysis

Metric Current Baseline Change
📊 Total Size 822.8 KB - -
🗜️ Gzip Size 218.3 KB - -
📄 JavaScript 822.4 KB - -
🎨 CSS 0 B - -
🌐 HTML 328.0 B - -
📁 Other Assets 0 B - -

Generated by Rsdoctor GitHub Action

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

📦 Binary Size-limit

Comparing 6edaf63 to refactor: swc exp for javascript parser plugin (#14256) by CPunisher

❌ Size increased by 4.00KB from 62.60MB to 62.60MB (⬆️0.01%)

@codspeed-hq

codspeed-hq Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will improve performance by 11.18%

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚠️ 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

⚡ 1 improved benchmark
❌ 1 regressed benchmark
✅ 50 untouched benchmarks
⏩ 40 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation js@collect imported identifiers 222.8 µs 229.1 µs -2.74%
WallTime bundle@threejs-10x-development 308.7 ms 242.9 ms +27.09%

Tip

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


Comparing perf/watcher-ignored-precompiled-regex (6edaf63) with main (1fd4fca)

Open in CodSpeed

Footnotes

  1. 40 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

On the macOS/Windows recursive-root watch the OS delivers an event for every
path under the project — mostly deep node_modules / build-output that nobody
registered — so the per-event ignored filter must be cheap.

Translate each ignored glob to a regex ending in `(?:$|/)` (watchpack's trick)
so a directory match also covers its subtree, fold all patterns into one
precompiled `regex::Regex`, and classify each event with a single `is_match` —
O(1) per event instead of O(depth x patterns) ancestor walking. Applied at the
event ingress (`Trigger::on_event`) and at registration, which absolutizes
first so it filters the same absolute paths events report.

Benched in `benches/ignored_filter.rs`: ~20-46x faster per event, ~30x on a
500-event burst.
@stormslowly stormslowly force-pushed the perf/watcher-ignored-precompiled-regex branch from 7646162 to 1706471 Compare June 9, 2026 04:41
@stormslowly stormslowly marked this pull request as ready for review June 9, 2026 04:46
Copilot AI review requested due to automatic review settings June 9, 2026 04:46

Copilot AI 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.

Pull request overview

This PR improves rspack_watcher’s ignore filtering for recursive-root watch mode by precompiling ignored glob patterns into a single subtree-matching regex and applying it both at path registration and at event ingress, preventing spurious rebuild triggers from ignored directories (e.g. deep node_modules / build output).

Changes:

  • Add IgnoredMatcher that rewrites ignored globs to subtree-matching regex alternatives and precompiles them into one regex::Regex.
  • Filter ignored paths at event ingress (Trigger::on_event) and during registration (PathUpdater::update) using the same absolute path basis.
  • Add a Criterion benchmark (benches/ignored_filter.rs) and wire it up in Cargo.toml.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
crates/rspack_watcher/src/trigger.rs Drops ignored-subtree events before association bubbling to prevent spurious rebuilds.
crates/rspack_watcher/src/paths.rs Uses IgnoredMatcher during registration; adds PathManager::is_ignored_path.
crates/rspack_watcher/src/lib.rs Re-exports IgnoredMatcher as doc-hidden for benchmarking.
crates/rspack_watcher/src/ignored.rs Implements glob→subtree-regex translation and the new precompiled matcher + tests.
crates/rspack_watcher/Cargo.toml Adds regex dep, Criterion dev-dep, and registers the benchmark.
crates/rspack_watcher/benches/ignored_filter.rs New benchmark comparing ancestor-walk glob matching vs precompiled regex.
Cargo.lock Updates lockfile for new dependencies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/rspack_watcher/src/ignored.rs Outdated
Comment thread crates/rspack_watcher/src/ignored.rs
…hen no patterns

Address review on #14329:
- `IgnoredMatcher::new` no longer swallows a regex compile error via `.ok()`.
  Glob escaping guarantees valid syntax so the only realistic failure is the
  regex size limit on a pathological config; degrade to "no glob filtering"
  (events still flow, no missed changes) but log it instead of disabling
  ignores silently.
- `is_ignored` short-circuits before normalizing the path when there are no
  patterns, removing per-event overhead in the common no-ignores config.
Removes the bench, its criterion dev-dependency and `[[bench]]` entry, and the
`doc(hidden)` `IgnoredMatcher` re-export that existed only so the bench could
reach it. Not needed for now; the production `regex` dependency stays.
@stormslowly stormslowly requested a review from hardfist June 9, 2026 08:06
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