Skip to content

perf: Disable incremental tracking when build module graph incremental is off#13272

Draft
hardfist wants to merge 1 commit intomainfrom
codex/filecounter
Draft

perf: Disable incremental tracking when build module graph incremental is off#13272
hardfist wants to merge 1 commit intomainfrom
codex/filecounter

Conversation

@hardfist
Copy link
Contributor

Summary

  • stop persisting and tracking module graph caches when the incremental build module graph pass is disabled
  • reset file/context/missing dependency bookkeeping and file counter state in disabled incremental scenarios (including watching changes)
  • add regression coverage for incremental-disabled caching in cacheCases/common

Testing

  • Not run (not requested)

Copilot AI review requested due to automatic review settings March 10, 2026 02:42
@hardfist hardfist changed the title Disable incremental tracking when build module graph incremental is off perf: Disable incremental tracking when build module graph incremental is off Mar 10, 2026
@github-actions github-actions bot added release: performance release: performance related release(mr only) team The issue/pr is created by the member of Rspack. labels Mar 10, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 10, 2026

📝 Benchmark detail: Open

Name Base (2026-03-10 1826195) Current Change
10000_big_production-mode_disable-minimize + exec 14.1 s ± 233 ms 14.2 s ± 283 ms +0.42 %
10000_development-mode + exec 1.03 s ± 29 ms 1.03 s ± 60 ms -0.64 %
10000_development-mode_hmr + stats 173 ms ± 2 ms 172 ms ± 4.9 ms -0.40 %
10000_development-mode_noop-loader + exec 2.06 s ± 77 ms 2.08 s ± 136 ms +0.94 %
10000_production-mode + exec 1.12 s ± 35 ms 1.09 s ± 37 ms -2.96 %
10000_production-mode_persistent-cold + exec 1.3 s ± 24 ms 1.28 s ± 27 ms -1.36 %
10000_production-mode_persistent-hot + exec 1 s ± 28 ms 985 ms ± 46 ms -1.81 %
10000_production-mode_source-map + exec 1.3 s ± 32 ms 1.27 s ± 23 ms -2.36 %
arco-pro_development-mode + exec 1.35 s ± 51 ms 1.34 s ± 55 ms -0.77 %
arco-pro_development-mode_hmr + stats 37 ms ± 3.5 ms 36 ms ± 0.47 ms -1.81 %
arco-pro_production-mode + exec 2.47 s ± 71 ms 2.47 s ± 56 ms -0.02 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 2.53 s ± 110 ms 2.49 s ± 61 ms -1.66 %
arco-pro_production-mode_persistent-cold + exec 2.53 s ± 45 ms 2.52 s ± 57 ms -0.74 %
arco-pro_production-mode_persistent-hot + exec 1.4 s ± 20 ms 1.39 s ± 13 ms -0.40 %
arco-pro_production-mode_source-map + exec 2.91 s ± 42 ms 2.87 s ± 45 ms -1.40 %
arco-pro_production-mode_traverse-chunk-modules + exec 2.47 s ± 46 ms 2.48 s ± 46 ms +0.51 %
bundled-threejs_development-mode + exec 193 ms ± 3.4 ms 193 ms ± 4.3 ms +0.42 %
bundled-threejs_production-mode + exec 231 ms ± 7 ms 228 ms ± 6.7 ms -1.21 %
large-dyn-imports_development-mode + exec 1.29 s ± 20 ms 1.26 s ± 27 ms -1.98 %
large-dyn-imports_production-mode + exec 1.38 s ± 25 ms 1.35 s ± 27 ms -1.98 %
threejs_development-mode_10x + exec 856 ms ± 33 ms 839 ms ± 15 ms -1.94 %
threejs_development-mode_10x_hmr + stats 118 ms ± 4.8 ms 115 ms ± 1 ms -3.17 %
threejs_production-mode_10x + exec 3.06 s ± 29 ms 3.05 s ± 32 ms -0.62 %
threejs_production-mode_10x_persistent-cold + exec 3.19 s ± 47 ms 3.19 s ± 40 ms -0.14 %
threejs_production-mode_10x_persistent-hot + exec 2.61 s ± 39 ms 2.58 s ± 29 ms -0.97 %
threejs_production-mode_10x_source-map + exec 3.79 s ± 50 ms 3.76 s ± 68 ms -0.64 %
10000_big_production-mode_disable-minimize + rss memory 2019 MiB ± 78.6 MiB 2037 MiB ± 49.2 MiB +0.87 %
10000_development-mode + rss memory 668 MiB ± 8.08 MiB 649 MiB ± 28.1 MiB -2.92 %
10000_development-mode_hmr + rss memory 888 MiB ± 26.8 MiB 860 MiB ± 46.4 MiB -3.23 %
10000_development-mode_noop-loader + rss memory 963 MiB ± 11.7 MiB 949 MiB ± 12.8 MiB -1.47 %
10000_production-mode + rss memory 538 MiB ± 42.2 MiB 540 MiB ± 16 MiB +0.43 %
10000_production-mode_persistent-cold + rss memory 746 MiB ± 9.88 MiB 740 MiB ± 13.5 MiB -0.76 %
10000_production-mode_persistent-hot + rss memory 760 MiB ± 6.52 MiB 751 MiB ± 10.3 MiB -1.25 %
10000_production-mode_source-map + rss memory 574 MiB ± 12.4 MiB 558 MiB ± 13 MiB -2.89 %
arco-pro_development-mode + rss memory 480 MiB ± 7.22 MiB 476 MiB ± 5.34 MiB -0.76 %
arco-pro_development-mode_hmr + rss memory 492 MiB ± 9.78 MiB 497 MiB ± 9.98 MiB +1.04 %
arco-pro_production-mode + rss memory 673 MiB ± 10.4 MiB 676 MiB ± 10.9 MiB +0.42 %
arco-pro_production-mode_generate-package-json-webpack-plugin + rss memory 680 MiB ± 56.3 MiB 687 MiB ± 18.7 MiB +1.04 %
arco-pro_production-mode_persistent-cold + rss memory 748 MiB ± 6.04 MiB 743 MiB ± 10.3 MiB -0.59 %
arco-pro_production-mode_persistent-hot + rss memory 564 MiB ± 46.4 MiB 564 MiB ± 34.4 MiB +0.03 %
arco-pro_production-mode_source-map + rss memory 739 MiB ± 13.8 MiB 757 MiB ± 24.1 MiB +2.46 %
arco-pro_production-mode_traverse-chunk-modules + rss memory 679 MiB ± 13 MiB 665 MiB ± 62.8 MiB -2.05 %
bundled-threejs_development-mode + rss memory 157 MiB ± 1.32 MiB 161 MiB ± 7.28 MiB +2.42 %
bundled-threejs_production-mode + rss memory 180 MiB ± 4.61 MiB 177 MiB ± 7.94 MiB -1.55 %
large-dyn-imports_development-mode + rss memory 666 MiB ± 5.36 MiB 656 MiB ± 6.32 MiB -1.46 %
large-dyn-imports_production-mode + rss memory 475 MiB ± 6.52 MiB 469 MiB ± 12.8 MiB -1.22 %
threejs_development-mode_10x + rss memory 540 MiB ± 11.5 MiB 533 MiB ± 11 MiB -1.13 %
threejs_development-mode_10x_hmr + rss memory 682 MiB ± 31.6 MiB 678 MiB ± 27.9 MiB -0.53 %
threejs_production-mode_10x + rss memory 729 MiB ± 12.4 MiB 725 MiB ± 24.5 MiB -0.56 %
threejs_production-mode_10x_persistent-cold + rss memory 861 MiB ± 12.9 MiB 847 MiB ± 19.4 MiB -1.65 %
threejs_production-mode_10x_persistent-hot + rss memory 709 MiB ± 17.6 MiB 702 MiB ± 4.99 MiB -0.95 %
threejs_production-mode_10x_source-map + rss memory 869 MiB ± 38.1 MiB 863 MiB ± 24.1 MiB -0.69 %

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 16288cb08c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +419 to +420
const incrementalEnabled =
this.compiler.options.incremental !== false;

Choose a reason for hiding this comment

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

P2 Badge Check buildModuleGraph pass before trusting dep deltas

This gate treats any non-false incremental config as "incremental dependency tracking is available", but users can set incremental: { buildModuleGraph: false, ... }. In that mode this patch rebuilds the module graph from scratch each run, so the internal __internal__added*/__internal__removed* sets are not reliable deltas; using them here means removed dependencies are never unwatched and stale paths keep accumulating in watch mode. Please branch on whether the build-module-graph pass is enabled (or fall back to #diffDependencies) instead of only checking incremental !== false.

Useful? React with 👍 / 👎.

Copy link
Contributor

Copilot AI left a comment

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 ensures incremental module-graph tracking/caching is disabled (and bookkeeping reset) when the incremental build module graph pass is turned off, including watch/HMR scenarios, and adds a regression test to prevent cache reuse in that mode.

Changes:

  • Stop persisting module graph artifacts/snapshots when BUILD_MODULE_GRAPH incremental is disabled.
  • Reset/disable incremental dependency tracking and related counters when incremental is disabled.
  • Add a cacheCase regression test covering “incremental-disabled + persistent cache”.

Reviewed changes

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

Show a summary per file
File Description
tests/rspack-test/cacheCases/common/update-file-incremental-disabled/rspack.config.js Adds a persistent-cache config with incremental: false + plugin assertions for the regression case
tests/rspack-test/cacheCases/common/update-file-incremental-disabled/loader.js Loader records processed files to validate rebuild behavior
tests/rspack-test/cacheCases/common/update-file-incremental-disabled/index.js Test flow covering HMR + multiple restarts with incremental disabled
tests/rspack-test/cacheCases/common/update-file-incremental-disabled/file.js Multi-step fixture to drive rebuild expectations across runs
packages/rspack/src/Watching.ts Computes dependency diffs manually when incremental tracking is disabled
crates/rspack_core/src/utils/incremental_info.rs Adds an enabled switch and disable() to stop tracking and reset state
crates/rspack_core/src/utils/file_counter/mod.rs Exposes a way to disable incremental info tracking for file counters
crates/rspack_core/src/stats/mod.rs Adjusts “module built” logic when incremental tracking is disabled
crates/rspack_core/src/compilation/mod.rs Changes dependency iterators to avoid incremental-only bookkeeping when disabled
crates/rspack_core/src/compilation/build_module_graph/module_executor/mod.rs Disables incremental tracking / resets artifacts when the build-module-graph pass is disabled
crates/rspack_core/src/compilation/build_module_graph/mod.rs Rebuilds entry dependencies into a fresh artifact when the pass is disabled
crates/rspack_core/src/cache/persistent/snapshot/mod.rs Adds Snapshot::replace() helper to overwrite a scope
crates/rspack_core/src/cache/persistent/mod.rs Skips restoring/saving module-graph cache when the incremental pass is disabled
crates/rspack_core/src/artifacts/build_module_graph_artifact.rs Adds disable_incremental_tracking() to turn off incremental bookkeeping in artifacts

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

.build_module_graph_artifact
.context_dependencies
.added_files()
.chain(&self.file_dependencies),
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

In the incremental-enabled branches for context_dependencies(), missing_dependencies(), and build_dependencies(), added_files() is chained with &self.file_dependencies rather than the corresponding dependency set (&self.context_dependencies, &self.missing_dependencies, &self.build_dependencies). This produces incorrect “added” iterators (mixing in file deps) and likely breaks downstream snapshot/build-deps updates. Chain each “added” iterator with its matching self.*_dependencies collection (consistent with the all_files and incremental-disabled branches).

Suggested change
.chain(&self.file_dependencies),
.chain(&self.context_dependencies),

Copilot uses AI. Check for mistakes.
Comment on lines +574 to +581
let added_files = if self.incremental.enabled() {
Either::Left(
self
.build_module_graph_artifact
.missing_dependencies
.added_files()
.chain(&self.file_dependencies),
)
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

In the incremental-enabled branches for context_dependencies(), missing_dependencies(), and build_dependencies(), added_files() is chained with &self.file_dependencies rather than the corresponding dependency set (&self.context_dependencies, &self.missing_dependencies, &self.build_dependencies). This produces incorrect “added” iterators (mixing in file deps) and likely breaks downstream snapshot/build-deps updates. Chain each “added” iterator with its matching self.*_dependencies collection (consistent with the all_files and incremental-disabled branches).

Copilot uses AI. Check for mistakes.
Comment on lines +627 to +634
let added_files = if self.incremental.enabled() {
Either::Left(
self
.build_module_graph_artifact
.build_dependencies
.added_files()
.chain(&self.file_dependencies),
)
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

In the incremental-enabled branches for context_dependencies(), missing_dependencies(), and build_dependencies(), added_files() is chained with &self.file_dependencies rather than the corresponding dependency set (&self.context_dependencies, &self.missing_dependencies, &self.build_dependencies). This produces incorrect “added” iterators (mixing in file deps) and likely breaks downstream snapshot/build-deps updates. Chain each “added” iterator with its matching self.*_dependencies collection (consistent with the all_files and incremental-disabled branches).

Copilot uses AI. Check for mistakes.
process.nextTick(() => {
if (!this.#closed) {
const incrementalEnabled =
this.compiler.options.incremental !== false;
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

This gating uses compiler.options.incremental !== false, but the rest of this PR’s behavior is keyed off whether the BUILD_MODULE_GRAPH incremental pass is enabled. If the pass is disabled while incremental remains enabled, this code will still rely on compilation.__internal__added/removed* (which may be empty/meaningless in that mode), potentially causing the watcher to miss dependency changes. Consider plumbing a dedicated flag that reflects passes_enabled(BUILD_MODULE_GRAPH) into the JS compilation object and using that here (or otherwise switching to the diff-based path when the pass is disabled).

Suggested change
this.compiler.options.incremental !== false;
this.compiler.options.incremental !== false &&
compilation.__internal__addedFileDependencies != null &&
compilation.__internal__removedFileDependencies != null &&
compilation.__internal__addedContextDependencies != null &&
compilation.__internal__removedContextDependencies != null &&
compilation.__internal__addedMissingDependencies != null &&
compilation.__internal__removedMissingDependencies != null;

Copilot uses AI. Check for mistakes.
Comment on lines +441 to +461

if (incrementalEnabled) {
fileDependencies.added = new Set(
compilation.__internal__addedFileDependencies,
);
fileDependencies.removed = new Set(
compilation.__internal__removedFileDependencies,
);
contextDependencies.added = new Set(
compilation.__internal__addedContextDependencies,
);
contextDependencies.removed = new Set(
compilation.__internal__removedContextDependencies,
);
missingDependencies.added = new Set(
compilation.__internal__addedMissingDependencies,
);
missingDependencies.removed = new Set(
compilation.__internal__removedMissingDependencies,
);
} else {
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

This gating uses compiler.options.incremental !== false, but the rest of this PR’s behavior is keyed off whether the BUILD_MODULE_GRAPH incremental pass is enabled. If the pass is disabled while incremental remains enabled, this code will still rely on compilation.__internal__added/removed* (which may be empty/meaningless in that mode), potentially causing the watcher to miss dependency changes. Consider plumbing a dedicated flag that reflects passes_enabled(BUILD_MODULE_GRAPH) into the JS compilation object and using that here (or otherwise switching to the diff-based path when the pass is disabled).

Copilot uses AI. Check for mistakes.
Comment on lines +221 to +272
let build_module_graph_incremental_enabled = compilation
.incremental
.passes_enabled(IncrementalPasses::BUILD_MODULE_GRAPH);

if build_module_graph_incremental_enabled {
// save snapshot
// TODO add a all_dependencies to collect dependencies
let (_, file_added, file_updated, file_removed) = compilation.file_dependencies();
let (_, context_added, context_updated, context_removed) =
compilation.context_dependencies();
let (_, missing_added, missing_updated, missing_removed) =
compilation.missing_dependencies();
let (_, build_added, build_updated, _) = compilation.build_dependencies();
self
.build_deps
.add(build_added.chain(build_updated).cloned())
.await,
);
.snapshot
.remove(SnapshotScope::FILE, file_removed.cloned());
self
.snapshot
.remove(SnapshotScope::CONTEXT, context_removed.cloned());
self
.snapshot
.remove(SnapshotScope::MISSING, missing_removed.cloned());
self
.snapshot
.add(SnapshotScope::FILE, file_added.chain(file_updated).cloned())
.await;
self
.snapshot
.add(
SnapshotScope::CONTEXT,
context_added.chain(context_updated).cloned(),
)
.await;
self
.snapshot
.add(
SnapshotScope::MISSING,
missing_added.chain(missing_updated).cloned(),
)
.await;
self.warnings.extend(
self
.build_deps
.add(build_added.chain(build_updated).cloned())
.await,
);
} else {
let (build_all, _, _, _) = compilation.build_dependencies();
self
.warnings
.extend(self.build_deps.add(build_all.cloned()).await);
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

In the build_module_graph_incremental_enabled == false branch, the cache still updates build_deps but does not clear/replace the snapshot scopes (FILE/CONTEXT/MISSING). That can leave stale snapshot data persisted on disk even though this mode intends to stop persisting/tracking module-graph-related caches, and it may also cause confusing state if the pass is later re-enabled. Consider explicitly clearing/replacing those snapshot scopes in the else branch (the newly added Snapshot::replace() looks well-suited here), or otherwise ensuring stale snapshot entries aren’t retained when the pass is disabled.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Contributor

Rsdoctor Bundle Diff Analysis

Found 5 projects in monorepo, 0 projects with changes.

📊 Quick Summary
Project Total Size Change
react-10k 5.7 MB 0
react-1k 826.2 KB 0
react-5k 2.7 MB 0
rome 984.2 KB 0
ui-components 2.3 MB 0

Generated by Rsdoctor GitHub Action

@hardfist hardfist marked this pull request as draft March 10, 2026 02:53
@github-actions
Copy link
Contributor

📦 Binary Size-limit

Comparing 16288cb to fix(create-rspack): update @rspack/dev-server to 2.0.0-beta.4 (#13268) by neverland

❌ Size increased by 16.00KB from 48.98MB to 48.99MB (⬆️0.03%)

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 10, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing codex/filecounter (16288cb) with main (0c4c570)

Open in CodSpeed

Footnotes

  1. 3 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.

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

Labels

release: performance release: performance related release(mr only) team The issue/pr is created by the member of Rspack.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants