Skip to content

Commit 7353453

Browse files
authored
feat: add rspack-split-chunks skill (#37)
1 parent 2da8862 commit 7353453

File tree

3 files changed

+515
-0
lines changed

3 files changed

+515
-0
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ A collection of Agent Skills for [Rstack](https://rspack.rs/guide/start/ecosyste
1515

1616
- [Usage](#usage)
1717
- [Rspack Skills](#rspack-skills)
18+
- [rspack-split-chunks](#rspack-split-chunks)
1819
- [Rsbuild Skills](#rsbuild-skills)
1920
- [Rslib Skills](#rslib-skills)
2021
- [Rspress Skills](#rspress-skills)
@@ -70,6 +71,14 @@ Comprehensive guide and toolkit for diagnosing Rspack build issues. Quickly iden
7071

7172
Use when the user encounters build failures, slow builds, or wants to optimize Rspack performance.
7273

74+
### rspack-split-chunks
75+
76+
```bash
77+
npx skills add rstackjs/agent-skills --skill rspack-split-chunks
78+
```
79+
80+
Diagnose and optimize Rspack `optimization.splitChunks` configuration. Use when tuning production chunking, reducing duplicated modules, improving cache behavior, or debugging over-fetch caused by `name` and `cacheGroups`.
81+
7382
## Rsbuild Skills
7483

7584
### rsbuild-best-practices
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
---
2+
name: rspack-split-chunks
3+
description: >-
4+
Diagnose and optimize Rspack `optimization.splitChunks` configuration. Use
5+
this when a user wants better production chunking, safer `chunks: "all"`
6+
defaults, fewer duplicated modules, better long-term caching, `cacheGroups`
7+
design help, `maxSize` tuning, or debugging over-fetch caused by `name` and
8+
forced chunk merging.
9+
---
10+
11+
# Rspack SplitChunks Optimization
12+
13+
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.
14+
15+
## Default stance
16+
17+
- Distinguish repo defaults from recommended production baselines.
18+
- Rspack's built-in default is `chunks: "async"`, but for most production web apps the best starting point is:
19+
20+
```js
21+
optimization: {
22+
splitChunks: {
23+
chunks: "all",
24+
},
25+
}
26+
```
27+
28+
- Keep the default cache groups unless there is a concrete reason to replace them.
29+
- Treat `name` as a graph-shaping option, not a cosmetic naming option.
30+
- Do not use `splitChunks` to 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.
31+
32+
Read [`references/repo-behavior.md`](references/repo-behavior.md) when you need the source-backed rationale.
33+
34+
## What To Optimize For
35+
36+
First identify which problem the user actually has:
37+
38+
- duplicated modules across entry or async boundaries
39+
- a route fetching a large shared chunk with mostly unused modules
40+
- too many tiny chunks
41+
- a vendor/common chunk that changes too often and hurts caching
42+
- an oversized async or initial chunk that should be subdivided
43+
- confusion about whether `splitChunks` affects runtime execution order
44+
45+
Do not optimize all of these at once. Pick the primary goal and keep the rest as constraints.
46+
47+
## Workflow
48+
49+
### 1. Start from the safest production baseline
50+
51+
Unless the user already has a measured problem that requires custom grouping, prefer:
52+
53+
```js
54+
optimization: {
55+
splitChunks: {
56+
chunks: "all",
57+
},
58+
}
59+
```
60+
61+
Why:
62+
63+
- it lets splitChunks dedupe modules across both initial and async chunks
64+
- it still only loads chunks reachable from the current entry/runtime
65+
- it usually avoids loading unnecessary modules better than hand-written global vendor buckets
66+
67+
If the existing config disables `default` or `defaultVendors`, assume that is suspicious until proven necessary.
68+
69+
### 2. Audit the config for high-risk knobs
70+
71+
Check these first:
72+
73+
- fixed `name`
74+
- `cacheGroups.*.name`
75+
- `enforce: true`
76+
- disabled `default` / `defaultVendors`
77+
- broad `test: /node_modules/` rules combined with a single global `name`
78+
- `usedExports: false`
79+
- very small `minSize`
80+
- `maxSize` combined with manual global names
81+
82+
### 3. Interpret `name` correctly
83+
84+
Use this rule:
85+
86+
- No `name`: splitChunks can keep different chunk combinations separate.
87+
- Same `name`: matching modules are merged into the same named split chunk candidate.
88+
89+
That means a fixed `name: "vendors"` or `name: "common"` is often the real reason a page starts fetching modules from unrelated dependency chains.
90+
91+
Prefer these alternatives before adding `name`:
92+
93+
- keep `name` unset
94+
- use `idHint` if the goal is filename identity, not grouping identity
95+
- narrow the `test` so the cache group is smaller
96+
- split one broad cache group into several focused cache groups
97+
- rely on `maxSize` to subdivide a big chunk instead of forcing a global name
98+
99+
Use a fixed `name` only when the user explicitly wants one shared asset across multiple entries/routes and accepts the extra coupling.
100+
101+
### 4. Preserve the built-in cache groups by default
102+
103+
Rspack's built-in production-oriented behavior depends heavily on these two groups:
104+
105+
- `default`: extracts modules shared by at least 2 chunks and reuses existing chunks
106+
- `defaultVendors`: extracts `node_modules` modules and reuses existing chunks
107+
108+
These defaults are usually the best balance between dedupe and "only fetch what this page needs".
109+
110+
If you customize `cacheGroups`, do not casually replace these with one manually named vendor bucket.
111+
112+
### 5. Use `chunks: "all"` without fear of breaking execution order
113+
114+
When a module group is split out, Rspack connects the new chunk back to the original chunk groups. That preserves JavaScript loading semantics.
115+
116+
So:
117+
118+
- `splitChunks` changes chunk topology
119+
- the runtime still guarantees dependency loading/execution order
120+
- if execution order appears broken, look for other causes first
121+
- this statement is about JavaScript, not CSS order
122+
123+
### 6. Use `maxSize` as a refinement tool
124+
125+
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".
126+
127+
Important behavior:
128+
129+
- `maxSize` runs after a chunk already exists
130+
- the split is deterministic
131+
- modules are grouped by path-derived keys and split near low-similarity boundaries
132+
- similar file paths tend to stay together
133+
134+
This is usually safer than forcing one giant named vendor chunk, because it keeps chunk graph semantics while subdividing hot spots.
135+
136+
### 7. Use `usedExports` deliberately
137+
138+
If the user has multiple runtimes/entries and wants leaner shared chunks per runtime, prefer keeping `usedExports` enabled.
139+
140+
If they set `usedExports: false`, expect broader sharing and potentially larger common chunks.
141+
142+
This is still not tree shaking. It only changes how splitChunks groups modules across runtimes.
143+
144+
### 8. Treat `enforce: true` as an escape hatch
145+
146+
`enforce: true` bypasses several normal guardrails. Use it only when the user intentionally wants a split regardless of `minSize`, `minChunks`, and request limits.
147+
148+
If a config looks aggressive and hard to explain, check `enforce` before changing anything else.
149+
150+
## Recommendations By Goal
151+
152+
### Better default production chunking
153+
154+
Recommend:
155+
156+
```js
157+
optimization: {
158+
splitChunks: {
159+
chunks: "all",
160+
},
161+
}
162+
```
163+
164+
Avoid:
165+
166+
- disabling `default`
167+
- disabling `defaultVendors`
168+
- adding `name` before measuring a real problem
169+
170+
### Avoid fetching non-essential modules
171+
172+
Recommend:
173+
174+
- remove fixed `name`
175+
- keep cache groups narrow
176+
- keep `chunks: "all"` if dedupe across initial chunks is still desired
177+
- inspect which routes now depend on a shared chunk after each change
178+
179+
Avoid:
180+
181+
```js
182+
cacheGroups: {
183+
vendors: {
184+
test: /[\\/]node_modules[\\/]/,
185+
chunks: "all",
186+
name: "vendors",
187+
enforce: true
188+
}
189+
}
190+
```
191+
192+
That pattern often creates one over-shared chunk that many pages must fetch.
193+
194+
### Improve caching without over-merging
195+
196+
Recommend:
197+
198+
- keep `name` unset
199+
- use `idHint`
200+
- keep `chunkIds: "deterministic"` or other stable id strategies elsewhere in the config
201+
- split broad groups into smaller focused groups only when the package boundaries are stable and important
202+
203+
Use a fixed `name` only if the user explicitly prefers cache reuse over route isolation.
204+
205+
### Split a large shared chunk
206+
207+
Recommend:
208+
209+
```js
210+
optimization: {
211+
splitChunks: {
212+
chunks: "all",
213+
maxSize: 200000,
214+
},
215+
}
216+
```
217+
218+
Then tune:
219+
220+
- `maxAsyncSize` when async chunks are the pain point
221+
- `maxInitialSize` when first-load pressure matters more
222+
- `hidePathInfo` if generated part names should not leak path structure
223+
224+
### Keep an intentionally shared chunk
225+
226+
Recommend a named chunk only when the user says something like:
227+
228+
- "all pages should share one React vendor asset"
229+
- "I want one framework chunk for cache reuse across routes"
230+
231+
Even then, call out the tradeoff explicitly:
232+
233+
- better cache hit rate
234+
- more coupling between routes
235+
- a page may fetch modules it does not execute immediately
236+
237+
## Review Checklist
238+
239+
When reviewing a user's config, explicitly answer:
240+
241+
1. Is the goal dedupe, cache stability, request count, or route isolation?
242+
2. Is `chunks: "all"` a better baseline than the current config?
243+
3. Did `name` accidentally turn multiple candidates into one forced shared chunk?
244+
4. Were `default` or `defaultVendors` disabled without a strong reason?
245+
5. Would `idHint` satisfy the naming goal without changing grouping?
246+
6. Is `maxSize` a better fit than a broad manual vendor/common bucket?
247+
7. Does the result still keep each page fetching only reachable chunks?
248+
249+
## Minimal stats setup
250+
251+
When the task includes diagnosis, ask for or generate stats that expose chunk relations:
252+
253+
```js
254+
stats: {
255+
chunks: true,
256+
chunkRelations: true,
257+
chunkOrigins: true,
258+
entrypoints: true,
259+
modules: false
260+
}
261+
```
262+
263+
Then compare:
264+
265+
- which entrypoints reference which shared chunks
266+
- whether a change added a new dependency edge from an entry to a broad shared chunk
267+
- whether a large shared chunk exists only because of a fixed `name`
268+
269+
## FAQ
270+
271+
### Why do I still see duplicate modules?
272+
273+
Common reasons:
274+
275+
- the shared candidate is too small, so extracting it would not satisfy `minSize`
276+
- the candidate does not satisfy `minSizeReduction`
277+
- it does not satisfy `minChunks`
278+
- request-budget limits reject the split
279+
- `chunks` / `test` / `cacheGroups` do not actually select the same chunk combination
280+
281+
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.
282+
283+
### Does splitChunks affect JS execution order?
284+
285+
No.
286+
287+
- `splitChunks` only changes chunk boundaries and dependency edges
288+
- JS loading and execution order are runtime concerns
289+
- if a JS ordering bug appears, investigate runtime/bootstrap, side effects, or app code first
290+
291+
### Does splitChunks affect tree shaking?
292+
293+
No.
294+
295+
- tree shaking is controlled by module-graph analysis such as `sideEffects`, `usedExports`, and dead-code elimination
296+
- `splitChunks` runs later and only reorganizes already-selected modules into chunks
297+
- `splitChunks.usedExports` is only a grouping hint for runtime-specific chunk combinations; it is not tree shaking itself
298+
299+
### Can splitChunks affect CSS order?
300+
301+
Yes, potentially.
302+
303+
- this caveat applies to CSS order, not JS execution order
304+
- extracted CSS flows such as `mini-css-extract-plugin` or `experiments.css` can observe changed final CSS order after splitChunks rewrites chunk groups
305+
- if CSS order is critical, be careful when splitting order-sensitive styles into separate chunks
306+
307+
See [web-infra-dev discussion #12](https://github.com/orgs/web-infra-dev/discussions/12).
308+
309+
## Quick conclusions to reuse
310+
311+
- "Keep `chunks: \"all\"`, keep the default cache groups, and remove `name` unless you intentionally want forced sharing."
312+
- "`name` is not just a filename hint in Rspack splitChunks; it changes grouping behavior."
313+
- "`splitChunks` does not control JS execution order or tree shaking; it only changes chunk topology."
314+
- "`splitChunks` can affect CSS order in extracted-CSS scenarios, so treat CSS as a separate caveat."
315+
- "`maxSize` is the safer tool when the problem is one chunk being too large."

0 commit comments

Comments
 (0)