Summary
After fixing a false-negative in Rspack's code-splitting cache, a visible, constant-length leaf.tsx edit in a large Rsbuild/React Router app consistently reuses the chunk graph but still executes the full SplitChunks pass. The client graph has roughly 408 route entries / 5,452 chunks; the edited module builds in 4-5 ms, while SplitChunks costs 0.81-1.40 s on each hot rebuild.
This is distinct from the existing leaf/chunk-graph report #14768 and from HMR process_assets #14767: those address earlier/later stages. There is currently no open SplitChunks-incremental issue or PR.
Attribution
Four consecutive hot edits in the post-cache-fix native trace:
edit 1 edit 2 edit 3 edit 4
code_splitting (client, reuse) 26ms 29ms 28ms 21ms
SplitChunks 1.15s 0.85s 0.81s 1.40s
root.css loader chain 1.13s 1.12s 0.93s 1.00s
leaf.tsx ~5ms ~5ms ~5ms ~5ms
Each cache decision sees seven mutations/five affected modules and unchanged topology. SplitChunks' two prepare_module_group_map spans account for only about 80-190 ms combined; most of the pass is repeated full-graph grouping/application work. The browser run completes true HMRs with no CSS requests or reloads. The CSS row is the complete loader chain and is a separate opportunity, not time attributed to SplitChunks.
Artifacts:
trace: hmr-codesplit-focused-20260713.jsonl
sha256: 2f2ceb002d18fc6d8dbb6084a3aae22eee1c836f94dd541de70a90ae5e51b7dc
HMR result: rspack-codesplit-candidate-focused-result.json
sha256: edbbf933c8270778849a35238505f3720199bee4a3e0050e9fc22df8a8ec874f
cache patch: rspack-codesplit-outgoing-cache-fix.patch
sha256: c0ccae88e9588754089a71d4aa7a8136f0592005f89f6234302694af847f01b1
native: rspack.linux-x64-gnu.node
sha256: 43aeb4bb914277fba080227d85de59fd45c73f329b393ef05be87d81e3ff2418
Why it repeats
MemoryCache::after_build_chunk_graph snapshots BuildChunkGraphArtifact before OptimizeChunksPass. That is the correct input for the next code-splitting decision, but a cache hit consequently starts SplitChunks from the unsplit graph every time. SplitChunksPlugin::inner_impl recalculates sizes/membership, prepares both default groups, moves modules, creates/reuses chunks, and reapplies relationships across the graph.
The repeated pass also unconditionally records Mutation::ChunkSplit { from, to } for every split (rspack_plugin_split_chunks/src/plugin/chunk.rs). Mutations::get_affected_chunks_with_chunk_graph subsequently marks both chunks affected, so recreating identical splits expands the incremental set for runtime requirements, hashing, and assets. The same trace spends another 0.68-0.95 s mostly idle in process_chunks_runtime_requirements. Reusing a verified post-optimize artifact could therefore save downstream work as well as the SplitChunks pass itself.
The app uses ordinary Rsbuild dev splitChunks: {}: Rspack's native default/defaultVendors async groups, usedExports: false, minSize: 10000, and no function-valued filters or names. Disabling sharing/splitting is not a viable workaround; a separate 416-entry browser topology matrix substantially inflated initial output and slowed rebuilds.
Proposed incremental boundary
Keep the existing pre-split snapshot and additionally retain an in-memory post-optimize chunks snapshot, captured immediately after the optimize pass and before optimizeChunkModules, runtime requirements, or asset emission. On a verified code-splitting cache hit, restore that snapshot and skip the pass only if all relevant SplitChunks inputs are unchanged; otherwise use the existing path and refresh it.
Do not reuse the previous final BuildChunkGraphArtifact: later stages connect runtime modules and populate chunk IDs/files/render state, which can introduce stale or duplicate runtime/emission state. Snapshot cloning overhead should be measured; an immutable/delta representation could avoid another large graph clone.
The conservative reuse predicate should require:
- unchanged entry/group/chunk topology and a successful code-splitting reuse decision;
- unchanged membership and per-source-type sizes for affected modules, including extracted CSS and size-threshold inputs;
- unchanged module type/layer/name-for-condition/chunk-condition behavior and used-export/runtime partition when enabled;
- bailout for function-valued
chunks, cache-group test/name/layer, or other non-incremental optimize-chunk plugins.
This can be automatic for safe built-in configurations and needs no user-facing knob.
Suggested regressions
Cover (1) repeated visible leaf edits in a multi-entry async-sharing fixture, asserting stable topology and correct hot updates; (2) JS and extracted-CSS size changes crossing split thresholds; (3) add/remove/retarget of an async dependency; (4) usedExports and function-valued selectors changing results; and (5) successive HMRs with no stale runtime modules/chunk files/manifests. Each invalidating case must fall back to the existing full optimize pass.
Summary
After fixing a false-negative in Rspack's code-splitting cache, a visible, constant-length
leaf.tsxedit in a large Rsbuild/React Router app consistently reuses the chunk graph but still executes the full SplitChunks pass. The client graph has roughly 408 route entries / 5,452 chunks; the edited module builds in 4-5 ms, while SplitChunks costs 0.81-1.40 s on each hot rebuild.This is distinct from the existing leaf/chunk-graph report #14768 and from HMR
process_assets#14767: those address earlier/later stages. There is currently no open SplitChunks-incremental issue or PR.Attribution
Four consecutive hot edits in the post-cache-fix native trace:
Each cache decision sees seven mutations/five affected modules and unchanged topology. SplitChunks' two
prepare_module_group_mapspans account for only about 80-190 ms combined; most of the pass is repeated full-graph grouping/application work. The browser run completes true HMRs with no CSS requests or reloads. The CSS row is the complete loader chain and is a separate opportunity, not time attributed to SplitChunks.Artifacts:
Why it repeats
MemoryCache::after_build_chunk_graphsnapshotsBuildChunkGraphArtifactbeforeOptimizeChunksPass. That is the correct input for the next code-splitting decision, but a cache hit consequently starts SplitChunks from the unsplit graph every time.SplitChunksPlugin::inner_implrecalculates sizes/membership, prepares both default groups, moves modules, creates/reuses chunks, and reapplies relationships across the graph.The repeated pass also unconditionally records
Mutation::ChunkSplit { from, to }for every split (rspack_plugin_split_chunks/src/plugin/chunk.rs).Mutations::get_affected_chunks_with_chunk_graphsubsequently marks both chunks affected, so recreating identical splits expands the incremental set for runtime requirements, hashing, and assets. The same trace spends another 0.68-0.95 s mostly idle inprocess_chunks_runtime_requirements. Reusing a verified post-optimize artifact could therefore save downstream work as well as the SplitChunks pass itself.The app uses ordinary Rsbuild dev
splitChunks: {}: Rspack's nativedefault/defaultVendorsasync groups,usedExports: false,minSize: 10000, and no function-valued filters or names. Disabling sharing/splitting is not a viable workaround; a separate 416-entry browser topology matrix substantially inflated initial output and slowed rebuilds.Proposed incremental boundary
Keep the existing pre-split snapshot and additionally retain an in-memory post-
optimize chunkssnapshot, captured immediately after the optimize pass and beforeoptimizeChunkModules, runtime requirements, or asset emission. On a verified code-splitting cache hit, restore that snapshot and skip the pass only if all relevant SplitChunks inputs are unchanged; otherwise use the existing path and refresh it.Do not reuse the previous final
BuildChunkGraphArtifact: later stages connect runtime modules and populate chunk IDs/files/render state, which can introduce stale or duplicate runtime/emission state. Snapshot cloning overhead should be measured; an immutable/delta representation could avoid another large graph clone.The conservative reuse predicate should require:
chunks, cache-grouptest/name/layer, or other non-incremental optimize-chunk plugins.This can be automatic for safe built-in configurations and needs no user-facing knob.
Suggested regressions
Cover (1) repeated visible leaf edits in a multi-entry async-sharing fixture, asserting stable topology and correct hot updates; (2) JS and extracted-CSS size changes crossing split thresholds; (3) add/remove/retarget of an async dependency; (4)
usedExportsand function-valued selectors changing results; and (5) successive HMRs with no stale runtime modules/chunk files/manifests. Each invalidating case must fall back to the existing full optimize pass.