
Rspack Split Chunks
- 113 installs
- 86 repo stars
- Updated August 4, 2026
- rstackjs/agent-skills
Helps with ai & agent building tasks during AI-assisted development.
About
rspack-split-chunks is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- rspack-split-chunks
- AI & Agent Building
- AI-coding skill
Rspack Split Chunks by the numbers
- 113 all-time installs (skills.sh)
- +8 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #3,984 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/rstackjs/agent-skills --skill rspack-split-chunksAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 113 |
|---|---|
| repo stars | ★ 86 |
| Last updated | August 4, 2026 |
| Repository | rstackjs/agent-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Rspack SplitChunks Optimization
Use this skill when the task is to recommend, review, or debug optimization.splitChunks. If you are using ESM library, it's not the same algorithm of this skill.
Default stance
- Distinguish repo defaults from recommended production baselines.
- Rspack's built-in default is
chunks: "async", but for most production web apps the best starting point is:
optimization: {
splitChunks: {
chunks: "all",
},
}- Keep the default cache groups unless there is a concrete reason to replace them.
- Treat
nameas a graph-shaping option, not a cosmetic naming option. - Do not use
splitChunksto reason about JavaScript execution order or tree shaking. For JS, chunk loading/execution order is preserved by the runtime dependency graph, and tree shaking is decided elsewhere.
Read `references/repo-behavior.md` when you need the source-backed rationale.
What To Optimize For
First identify which problem the user actually has:
- duplicated modules across entry or async boundaries
- a route fetching a large shared chunk with mostly unused modules
- too many tiny chunks
- a vendor/common chunk that changes too often and hurts caching
- an oversized async or initial chunk that should be subdivided
- confusion about whether
splitChunksaffects runtime execution order
Do not optimize all of these at once. Pick the primary goal and keep the rest as constraints.
Workflow
1. Start from the safest production baseline
Unless the user already has a measured problem that requires custom grouping, prefer:
optimization: {
splitChunks: {
chunks: "all",
},
}Why:
- it lets splitChunks dedupe modules across both initial and async chunks
- it still only loads chunks reachable from the current entry/runtime
- it usually avoids loading unnecessary modules better than hand-written global vendor buckets
If the existing config disables default or defaultVendors, assume that is suspicious until proven necessary.
2. Audit the config for high-risk knobs
Check these first:
- fixed
name cacheGroups.*.nameenforce: true- disabled
default/defaultVendors - broad
test: /node_modules/rules combined with a single globalname usedExports: false- very small
minSize maxSizecombined with manual global names
3. Interpret name correctly
Use this rule:
- No
name: splitChunks can keep different chunk combinations separate. - Same
name: matching modules are merged into the same named split chunk candidate.
That means a fixed name: "vendors" or name: "common" is often the real reason a page starts fetching modules from unrelated dependency chains.
Prefer these alternatives before adding name:
- keep
nameunset - use
idHintif the goal is filename identity, not grouping identity - narrow the
testso the cache group is smaller - split one broad cache group into several focused cache groups
- rely on
maxSizeto subdivide a big chunk instead of forcing a global name
Use a fixed name only when the user explicitly wants one shared asset across multiple entries/routes and accepts the extra coupling.
4. Preserve the built-in cache groups by default
Rspack's built-in production-oriented behavior depends heavily on these two groups:
default: extracts modules shared by at least 2 chunks and reuses existing chunksdefaultVendors: extractsnode_modulesmodules and reuses existing chunks
These defaults are usually the best balance between dedupe and "only fetch what this page needs".
If you customize cacheGroups, do not casually replace these with one manually named vendor bucket.
5. Use chunks: "all" without fear of breaking execution order
When a module group is split out, Rspack connects the new chunk back to the original chunk groups. That preserves JavaScript loading semantics.
So:
splitChunkschanges chunk topology- the runtime still guarantees dependency loading/execution order
- if execution order appears broken, look for other causes first
- this statement is about JavaScript, not CSS order
6. Use maxSize as a refinement tool
Use maxSize, maxAsyncSize, or maxInitialSize when the problem is "this shared chunk is too large", not when the problem is "I need a stable vendor chunk name".
Important behavior:
maxSizeruns after a chunk already exists- the split is deterministic
- modules are grouped by path-derived keys and split near low-similarity boundaries
- similar file paths tend to stay together
This is usually safer than forcing one giant named vendor chunk, because it keeps chunk graph semantics while subdividing hot spots.
7. Use usedExports deliberately
If the user has multiple runtimes/entries and wants leaner shared chunks per runtime, prefer keeping usedExports enabled.
If they set usedExports: false, expect broader sharing and potentially larger common chunks.
This is still not tree shaking. It only changes how splitChunks groups modules across runtimes.
8. Treat enforce: true as an escape hatch
enforce: true bypasses several normal guardrails. Use it only when the user intentionally wants a split regardless of minSize, minChunks, and request limits.
If a config looks aggressive and hard to explain, check enforce before changing anything else.
Recommendations By Goal
Better default production chunking
Recommend:
optimization: {
splitChunks: {
chunks: "all",
},
}Avoid:
- disabling
default - disabling
defaultVendors - adding
namebefore measuring a real problem
Avoid fetching non-essential modules
Recommend:
- remove fixed
name - keep cache groups narrow
- keep
chunks: "all"if dedupe across initial chunks is still desired - inspect which routes now depend on a shared chunk after each change
Avoid:
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
chunks: "all",
name: "vendors",
enforce: true
}
}That pattern often creates one over-shared chunk that many pages must fetch.
Improve caching without over-merging
Recommend:
- keep
nameunset - use
idHint - keep
chunkIds: "deterministic"or other stable id strategies elsewhere in the config - split broad groups into smaller focused groups only when the package boundaries are stable and important
Use a fixed name only if the user explicitly prefers cache reuse over route isolation.
Split a large shared chunk
Recommend:
optimization: {
splitChunks: {
chunks: "all",
maxSize: 200000,
},
}Then tune:
maxAsyncSizewhen async chunks are the pain pointmaxInitialSizewhen first-load pressure matters morehidePathInfoif generated part names should not leak path structure
Keep an intentionally shared chunk
Recommend a named chunk only when the user says something like:
- "all pages should share one React vendor asset"
- "I want one framework chunk for cache reuse across routes"
Even then, call out the tradeoff explicitly:
- better cache hit rate
- more coupling between routes
- a page may fetch modules it does not execute immediately
Review Checklist
When reviewing a user's config, explicitly answer:
1. Is the goal dedupe, cache stability, request count, or route isolation? 2. Is chunks: "all" a better baseline than the current config? 3. Did name accidentally turn multiple candidates into one forced shared chunk? 4. Were default or defaultVendors disabled without a strong reason? 5. Would idHint satisfy the naming goal without changing grouping? 6. Is maxSize a better fit than a broad manual vendor/common bucket? 7. Does the result still keep each page fetching only reachable chunks?
Minimal stats setup
When the task includes diagnosis, ask for or generate stats that expose chunk relations:
stats: {
chunks: true,
chunkRelations: true,
chunkOrigins: true,
entrypoints: true,
modules: false
}Then compare:
- which entrypoints reference which shared chunks
- whether a change added a new dependency edge from an entry to a broad shared chunk
- whether a large shared chunk exists only because of a fixed
name
FAQ
Why do I still see duplicate modules?
Common reasons:
- the shared candidate is too small, so extracting it would not satisfy
minSize - the candidate does not satisfy
minSizeReduction - it does not satisfy
minChunks - request-budget limits reject the split
chunks/test/cacheGroupsdo not actually select the same chunk combination
If the duplicate module is tiny, do not assume this is a bug. Rspack may intentionally keep it in place because splitting it out would create a worse chunk.
Does splitChunks affect JS execution order?
No.
splitChunksonly changes chunk boundaries and dependency edges- JS loading and execution order are runtime concerns
- if a JS ordering bug appears, investigate runtime/bootstrap, side effects, or app code first
Does splitChunks affect tree shaking?
No.
- tree shaking is controlled by module-graph analysis such as
sideEffects,usedExports, and dead-code elimination splitChunksruns later and only reorganizes already-selected modules into chunkssplitChunks.usedExportsis only a grouping hint for runtime-specific chunk combinations; it is not tree shaking itself
Can splitChunks affect CSS order?
Yes, potentially.
- this caveat applies to CSS order, not JS execution order
- extracted CSS flows such as
mini-css-extract-pluginorexperiments.csscan observe changed final CSS order after splitChunks rewrites chunk groups - if CSS order is critical, be careful when splitting order-sensitive styles into separate chunks
See web-infra-dev discussion #12.
Quick conclusions to reuse
- "Keep
chunks: \"all\", keep the default cache groups, and removenameunless you intentionally want forced sharing." - "
nameis not just a filename hint in Rspack splitChunks; it changes grouping behavior." - "
splitChunksdoes not control JS execution order or tree shaking; it only changes chunk topology." - "
splitChunkscan affect CSS order in extracted-CSS scenarios, so treat CSS as a separate caveat." - "
maxSizeis the safer tool when the problem is one chunk being too large."
SplitChunks Repo Behavior
This file is the source-backed reference for SKILL.md.
1. Repo defaults vs recommended production baseline
Rspack's built-in defaults are defined in packages/rspack/src/config/defaults.ts.
Key defaults:
chunks: "async"at defaults.ts#L1046minChunks: 1at defaults.ts#L1048minSize: 20000in production,10000otherwise at defaults.ts#L1049maxAsyncRequestsandmaxInitialRequests:30in production,Infinityotherwise at defaults.ts#L1052 and defaults.ts#L1055automaticNameDelimiter: "-"at defaults.ts#L1058cacheGroups.defaultwithminChunks: 2andreuseExistingChunk: trueat defaults.ts#L1061cacheGroups.defaultVendorsmatchingnode_moduleswithreuseExistingChunk: trueat defaults.ts#L1067
Recommended production baseline in the skill is different from the built-in default:
- built-in default: safer generic fallback
- recommended app baseline:
chunks: "all"for better dedupe across both initial and async chunks
2. name changes grouping, not just filenames
The core behavior is in crates/rspack_plugin_split_chunks/src/plugin/module_group.rs.
When a module matches a cache group, merge_matched_item_into_module_group_map computes the module-group key.
If name exists:
- the key is
cache_group.key + chunk_nameat module_group.rs#L577
If name does not exist:
- the key uses the selected chunk combination hash at module_group.rs#L582
Effect:
- same
namecollapses otherwise separate chunk combinations into oneModuleGroup - that can force broader sharing than the user expects
This is why a broad manual name: "vendors" can create over-shared chunks.
3. Named chunks are reused by name
Chunk creation/reuse is handled in crates/rspack_plugin_split_chunks/src/plugin/chunk.rs.
If module_group.chunk_name exists:
- Rspack first looks for an existing named chunk at chunk.rs#L125
- if found, it reuses that chunk
- otherwise it creates a named chunk at chunk.rs#L134
If there is no name, only then does reuseExistingChunk try to reuse a chunk with the same module set at chunk.rs#L160.
Implication:
- fixed
nameis a stronger form of coupling thanreuseExistingChunk reuseExistingChunkis opportunisticnameis explicit merging
4. splitChunks does not decide JS execution order
After extracting a chunk, Rspack wires the new chunk back to each original chunk in split_from_original_chunks at chunk.rs#L235.
That means splitChunks changes the chunk graph, and the runtime follows that graph when loading dependencies.
Inference from the implementation:
- splitChunks is about graph topology and fetch boundaries
- execution order remains runtime-driven, not user-declared via splitChunks
- the plugin itself runs in optimize-chunks stage at plugin/mod.rs#L305, so it is not the mechanism that performs tree shaking
5. idHint is safer than name for identity hints
After a chunk is chosen, the plugin adds idHint to chunk id-name hints at plugin/mod.rs#L215.
idHint participates in naming/id presentation, but it is not used to build the ModuleGroup key.
This makes idHint a safer tool when the user wants chunk identity hints without changing grouping behavior.
6. enforce: true removes several guardrails
Normalization from JS options to Rust plugin options happens in crates/rspack_binding_api/src/raw_options/raw_split_chunks/mod.rs.
For cache groups with enforce: true:
- overall
minSizeis not merged in at raw_split_chunks/mod.rs#L157 - overall
minSizeReductionis not merged in at raw_split_chunks/mod.rs#L163 - overall
maxAsyncSize/maxInitialSizeconstraints are treated differently at raw_split_chunks/mod.rs#L171 and raw_split_chunks/mod.rs#L179 minChunksfalls back to1when enforced at raw_split_chunks/mod.rs#L188
This matches the high-level guidance: enforce is powerful and easy to misuse.
7. maxSize is a second-stage deterministic split
The maxSize algorithm lives in crates/rspack_plugin_split_chunks/src/plugin/max_size.rs.
Important implementation details:
- it runs after chunk extraction, inside
ensure_max_size_fit, at max_size.rs#L469 - it computes module keys from relative paths plus a short hash at max_size.rs#L232
- it sorts modules by that key before grouping at max_size.rs#L278
- it splits groups near the weakest similarity boundary at max_size.rs#L381
- it can hash away path info when
hidePathInfois enabled at max_size.rs#L625
The similarity function is simple character-distance scoring at max_size.rs#L456, so similar path prefixes tend to stay together.
Practical meaning:
maxSizeis for subdividing a chunk, not for creating a global shared chunk policy- path locality matters
- the result is deterministic and generally stable enough for production use
8. usedExports affects chunk combinations
The combinator prepares chunk combinations in two modes inside plugin/module_group.rs:
- by plain chunk sets at module_group.rs#L162
- by runtime-specific used exports at module_group.rs#L197
When usedExports is enabled, the grouping can stay more runtime-specific.
When it is disabled, more modules may be shared together because the grouping ignores those usage distinctions.
This is still not tree shaking. It only changes grouping granularity.
9. Why chunks: "all" is often the better application baseline
This is a recommendation, not the code default.
Combined reading of the defaults and the plugin behavior suggests:
- the built-in cache groups already cover duplicate-module extraction and vendor extraction
chunks: "all"lets those rules consider both initial and async chunk boundaries- because the runtime still follows chunk dependencies, this usually preserves "fetch only what the current page needs"
- the main thing that breaks that property is manual forced merging, especially fixed
name
So for application builds, the skill recommends:
- start with
chunks: "all" - keep the default cache groups
- avoid
nameunless the user consciously wants one shared asset
10. Why duplicate modules can still exist
Duplicate modules are not automatically a bug.
The plugin explicitly removes or rejects split candidates when they do not satisfy size constraints:
remove_min_size_violating_modulesdrops candidates belowminSizeat min_size.rs#L44check_min_size_reductionrejects candidates whose total reduction is too small at min_size.rs#L95ensure_min_size_fitremoves invalid module groups at min_size.rs#L121ensure_max_request_fitcan also reject a candidate when request budgets would be exceeded at max_request.rs#L12
Practical meaning:
- a very small shared module may remain duplicated because extracting it would fail
minSize - even with
minSize: 0,minSizeReductionor request limits may still block the split - if the user insists on extracting it, they need to lower the relevant threshold or intentionally override guardrails
11. Tree shaking is orthogonal to splitChunks
From the plugin stage and responsibilities:
- splitChunks runs during chunk optimization at plugin/mod.rs#L305
- it groups and regroups modules into chunks; it does not decide whether a module/export is dead
splitChunks.usedExportsonly changes runtime-specific grouping, as shown in module_group.rs#L197
So the clean mental model is:
- tree shaking decides what is kept
- splitChunks decides how kept modules are partitioned into chunks
12. CSS order is a separate caveat
For JavaScript, the guidance above stands: splitChunks does not change execution order semantics.
For CSS, there is a separate caveat documented in web-infra-dev discussion #12:
- the discussion shows that extracted CSS order can become unstable after splitChunks rewrites chunk groups
- this is specifically discussed for
mini-css-extract-pluginandexperiments.css - the same discussion contrasts this with
style-loader, where CSS insertion follows JS execution order more directly
Useful lines from that discussion:
- CSS order can become inconsistent with import order after splitChunks at discussion #12 lines 224-256
style-loaderkeeps insertion order aligned with import/execution order at discussion #12 lines 284-286- extracted CSS flows are the problematic case at discussion #12 lines 287-292
- splitChunks is described there as further splitting of chunks, separate from JS code splitting semantics, at discussion #12 lines 297-305