feat(code-splitting): support group-local includeDependenciesRecursively#9587
Conversation
✅ Deploy Preview for rolldown-rs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
Adds per-codeSplitting.groups[] control over includeDependenciesRecursively, allowing individual groups to opt out of recursive dependency capture while preserving the existing global/default behavior (true) for other groups. This improves manual code-splitting ergonomics for “route fanout” style apps where route chunks should not pull in sibling route closures.
Changes:
- Extend Rust option types + NAPI binding + TS public types/validator to support
groups[].includeDependenciesRecursively?: boolean. - Update chunk assignment logic to resolve the effective value as: group override → global option →
true. - Extend
preserveEntrySignaturesvalidation to treat any effective non-recursive capture (global or per-group) as conflicting, and add fixtures covering both behavior and validation.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/rolldown/tests/fixtures/misc/error/per-group-include-deps-conflict/main.js | Minimal entry for the new validation error fixture. |
| packages/rolldown/tests/fixtures/misc/error/per-group-include-deps-conflict/_config.ts | Verifies preserveEntrySignatures: 'strict' conflicts with group-local includeDependenciesRecursively: false. |
| packages/rolldown/tests/fixtures/function/advanced-chunks/per-group-include-deps/shared/util.js | Shared module used to assert non-recursive capture behavior. |
| packages/rolldown/tests/fixtures/function/advanced-chunks/per-group-include-deps/routes/alpha/index.js | Route module importing shared util to validate dependency exclusion when non-recursive. |
| packages/rolldown/tests/fixtures/function/advanced-chunks/per-group-include-deps/main.js | Entry importing both route and shared util to produce the expected chunk graph. |
| packages/rolldown/tests/fixtures/function/advanced-chunks/per-group-include-deps/_config.ts | Asserts route chunk does not include transitive deps when group override is false. |
| packages/rolldown/src/utils/validator.ts | Extends runtime option validation schema to accept groups[].includeDependenciesRecursively. |
| packages/rolldown/src/options/output-options.ts | Adds TS type + JSDoc for CodeSplittingGroup.includeDependenciesRecursively. |
| packages/rolldown/src/binding.d.cts | Updates generated binding typings for BindingMatchGroup. |
| crates/rolldown/src/utils/prepare_build_context.rs | Updates validation to consider group-local non-recursive capture via a helper. |
| crates/rolldown/src/stages/generate_stage/manual_code_splitting.rs | Implements group-local fallback logic for recursive dependency capture. |
| crates/rolldown_testing/_config.schema.json | Extends JSON schema to allow includeDependenciesRecursively on match groups. |
| crates/rolldown_common/src/inner_bundler_options/types/manual_code_splitting_options.rs | Adds include_dependencies_recursively: Option<bool> to MatchGroup (Rust type layer). |
| crates/rolldown_binding/src/utils/normalize_binding_options.rs | Maps the new binding field into Rust MatchGroup. |
| crates/rolldown_binding/src/options/binding_output_options/binding_manual_code_splitting_options.rs | Adds the new field to BindingMatchGroup (NAPI object). |
Comments suppressed due to low confidence (1)
crates/rolldown/src/utils/prepare_build_context.rs:134
- The diagnostics emitted here can now be triggered by a group-level
includeDependenciesRecursively: false, but the underlyingInvalidOptionType::IncludeDependenciesRecursivelyWithConflictPreserveEntrySignaturesmessage text still hard-codescodeSplitting.includeDependenciesRecursively = false(global). This makes the error/warning misleading for users who only set the per-group override. Consider updating the diagnostic message (and the implicit preserveEntrySignatures warning) to describe the effective non-recursive capture (global or any group), or introduce a dedicated InvalidOptionType for the per-group case.
// Check if `codeSplitting.include_dependencies_recursively` conflict with `preserveEntrySignatures`
if has_non_recursive_dependency_capture(manual_code_splitting) {
if let Some(preserve_signatures) = &raw_options.preserve_entry_signatures {
if matches!(
preserve_signatures,
PreserveEntrySignatures::Strict | PreserveEntrySignatures::ExportsOnly
) {
errors.push(BuildDiagnostic::invalid_option(
InvalidOptionType::IncludeDependenciesRecursivelyWithConflictPreserveEntrySignatures(
preserve_signatures.to_string(),
),
));
}
✅ Deploy Preview for rolldown-rs canceled.
|
Merging this PR will not alter performance
Comparing Footnotes
|
@rolldown/browser
@rolldown/debug
rolldown
@rolldown/binding-android-arm64
@rolldown/binding-darwin-arm64
@rolldown/binding-darwin-x64
@rolldown/binding-freebsd-x64
@rolldown/binding-linux-arm-gnueabihf
@rolldown/binding-linux-arm64-gnu
@rolldown/binding-linux-arm64-musl
@rolldown/binding-linux-ppc64-gnu
@rolldown/binding-linux-s390x-gnu
@rolldown/binding-linux-x64-gnu
@rolldown/binding-linux-x64-musl
@rolldown/binding-openharmony-arm64
@rolldown/binding-wasm32-wasi
@rolldown/binding-win32-arm64-msvc
@rolldown/binding-win32-x64-msvc
commit: |
Merge activity
|
…vely` (#9587) ## Summary - Allow `includeDependenciesRecursively` on individual `codeSplitting.groups`, overriding the global value - When omitted on a group, inherits `codeSplitting.includeDependenciesRecursively` (default `true`) - Extends `preserveEntrySignatures` validation to check both global and per-group values Closes #9467 Built on the exploration in #9488 — thank you @aminpaks for the thorough investigation and initial implementation! 🙏 ## Changes | Layer | File | Change | |-------|------|--------| | Rust types | `manual_code_splitting_options.rs` | Add `include_dependencies_recursively: Option<bool>` to `MatchGroup` | | NAPI binding | `binding_manual_code_splitting_options.rs` | Add field to `BindingMatchGroup` | | Binding conversion | `normalize_binding_options.rs` | Map new field | | Chunk logic | `manual_code_splitting.rs` | Per-group fallback: group → global → `true` | | Validation | `prepare_build_context.rs` | `has_non_recursive_dependency_capture()` helper, 2 callsites | | TS types | `output-options.ts` | Add to `CodeSplittingGroup` with JSDoc | | TS validator | `validator.ts` | Add to group schema | ## Example ```js output: { codeSplitting: { includeDependenciesRecursively: true, groups: [ { name: 'route-alpha', test: /routes\/alpha\//, includeDependenciesRecursively: false, // route-only, no sibling deps }, { name: 'shared', test: /shared\//, // inherits global (true) }, ], }, } ``` ## Test plan - [x] Chunking behavior test: group with `false` excludes transitive deps, group without inherits global - [x] Validation test: per-group `false` + `preserveEntrySignatures: 'strict'` → error - [x] All existing `advanced-chunks` tests pass - [x] Full build (`just build-rolldown`) passes
cde6270 to
5e05eba
Compare
> [!IMPORTANT] > **This is a minor release.** Two changes alter default behavior compared to `1.0.3`. Please read this section before upgrading. Everything else is additive (new features, fixes, deps). ##⚠️ Notable behavior changes ### 1. `experimental.lazyBarrel` is now enabled by default (#9632) **What changed.** `experimental.lazyBarrel` now defaults to `true`. When a barrel module is recognized as side-effect-free, Rolldown skips compiling the re-exported modules that are never actually used. **Impact.** For codebases with large barrel files (component libraries such as Ant Design, `@mui/icons-material`, etc.) this is a meaningful build-time speedup, and for the vast majority of projects the emitted output is unchanged. In rare cases where a barrel is *incorrectly* treated as side-effect-free, the optimization could drop a module that was being relied on for its side effects. **How to opt out (backward compatible).** ```js // rolldown.config.js export default { experimental: { lazyBarrel: false }, } ``` > Note: this opt-out flag is planned to be removed in a future release. If you have a case where you must turn it off, please open an issue so we can fix the underlying detection instead. --- ### 2. `tsconfig` project-reference resolution now aligns with TypeScript Upgrading `oxc_resolver` (`11.19.1` → `11.20.0` in #9549, then `→ 11.21.0` in #9634) changes how a *solution-style* `tsconfig.json` (one that only lists `references` and delegates the real settings to `tsconfig.app.json` / `tsconfig.node.json`, as Vite scaffolds) is resolved, bringing it **in line with how TypeScript (`tsc`) itself behaves**: - **Reference match priority** (oxc-resolver [#1151](oxc-project/oxc-resolver#1151)): when the root has `references`, a referenced project that includes the file now **takes precedence over the root**, instead of the root matching it first (this is what TypeScript already does). So that project's `compilerOptions.paths` now apply. - **`allowJs`** (oxc-resolver [#1198](oxc-project/oxc-resolver#1198)): whether a `.js`/`.jsx`/`.mjs`/`.cjs` file is included is now decided by **each referenced project's own** `allowJs`, not the root's (again matching TypeScript). So `tsconfig.app.json` with `allowJs: true` + `paths` now resolves aliases for `.js` files even when the root doesn't set `allowJs`. For most projects this is a fix (the standard Vite `paths` aliases now resolve, closes #8468), but it **is** a behavior change if you relied on the previous behavior, where the root's `paths` / `allowJs` took precedence. **If you relied on the old "root wins" behavior.** There is no exact toggle back, because the old behavior was the bug being fixed. The recommended path is to align your config with TypeScript: declare the `paths` / `allowJs` on the referenced project that actually owns the files. If you must keep the old precedence while still using `references`: a referenced project's match wins, and **the first matching `references` entry takes priority** (the root is only a fallback when no reference claims the file). So extract the old root settings into their own config and list it **first**: ```jsonc // tsconfig.json (solution root) { "files": [], "references": [ { "path": "./tsconfig.base.json" }, // old root paths/allowJs — listed first, so it wins { "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" } ] } ``` `tsconfig.base.json` should carry the `paths` you previously declared on the root, plus `allowJs: true` if it needs to claim `.js` files (the extension is checked against each config's own `allowJs`). With no `include`, it defaults to `**/*` under its directory and claims every file first. Alternatively, bypass reference resolution entirely by pointing the top-level `tsconfig` option at a single config: `export default { tsconfig: './tsconfig.app.json' }`. --- ## [1.1.0] - 2026-06-03 ### 🚀 Features - enable `experimental.lazyBarrel` by default (#9632) by @shulaoda - `import.meta.glob` support `caseSensitive` option (#9594) by @btea - add `SOURCEMAP_BROKEN` warning for renderChunk hook (#9601) by @sapphi-red - add `SOURCEMAP_BROKEN` warning for transform hook (#9600) by @sapphi-red - add `@__NO_SIDE_EFFECTS__` hint for invalid `@__PURE__` before function declarations (#9505) by @Copilot - code-splitting: support group-local `includeDependenciesRecursively` (#9587) by @hyf0 ### 🐛 Bug Fixes - report TSCONFIG_ERROR instead of UNHANDLEABLE_ERROR for a missing tsconfig file (#9633) by @shulaoda - browser: add missing exports and ensure consistency with `rolldown` package (#9629) by @sapphi-red - should build test-dev-server when test-node (#9610) by @situ2001 - chunk-optimizer: refuse asymmetric merge for cyclic dynamic entries (#9320) (#9322) by @aminpaks - dev: handle the remaining errors in dev (#9570) by @h-a-n-a - handle slash-normalized ids with preserveModulesRoot (#9595) by @IWANABETHATGUY - json: preserve .default access on JSON default imports (#9568) by @IWANABETHATGUY - testing: remove unintended trigger_full_build from test harness (#9573) by @hyf0 ### 🚜 Refactor - js-regex: use regress native replace/replace_all (#9607) by @IWANABETHATGUY - remove never-constructed `ImportStatus` variants (#9606) by @Boshen ### 📚 Documentation - clarify that `RolldownBuild::close` method should be called in most cases (#9619) by @sapphi-red ### ⚡ Performance - avoid unnecessary intermediate sourcemaps (#9599) by @sapphi-red ### 🧪 Testing - add unit test for collapsing module sourcemap (#9626) by @sapphi-red - cover vite-alias regex capture-group expansion (#9602) (#9608) by @IWANABETHATGUY ### ⚙️ Miscellaneous Tasks - deps: update oxc_resolver to 11.21.0 (#9634) by @shulaoda - update invalid option diagnostic link to point to Rolldown docs (#9631) by @sapphi-red - deps: update vite+ to v0.1.24 (#9628) by @renovate[bot] - deps: update oxc resolver to v11.20.0 (#9549) by @renovate[bot] - deps: update dependency vite-plus to v0.1.24 (#9470) by @renovate[bot] - deps: update npm packages (#9614) by @renovate[bot] - deps: upgrade oxc to 0.134.0 (#9625) by @shulaoda - deps: update crate-ci/typos action to v1.47.0 (#9620) by @renovate[bot] - deps: update rollup submodule for tests to v4.61.0 (#9623) by @rolldown-guard[bot] - deps: update github actions (#9613) by @renovate[bot] - deps: update pnpm to v11.4.0 (#9616) by @renovate[bot] - deps: update rust crates (#9615) by @renovate[bot] - deps: update test262 submodule for tests (#9624) by @rolldown-guard[bot] - deps: update dependency @napi-rs/cli to v3.7.0 (#9588) by @renovate[bot] - deps: update dependency rust to v1.96.0 (#9596) by @renovate[bot] - re-enable WASI testing with proper infrastructure (#9397) by @Boshen ### ❤️ New Contributors * @aminpaks made their first contribution in [#9322](#9322) Co-authored-by: shulaoda <[email protected]>
Summary
includeDependenciesRecursivelyon individualcodeSplitting.groups, overriding the global valuecodeSplitting.includeDependenciesRecursively(defaulttrue)preserveEntrySignaturesvalidation to check both global and per-group valuesCloses #9467
Built on the exploration in #9488 — thank you @aminpaks for the thorough investigation and initial implementation! 🙏
Changes
manual_code_splitting_options.rsinclude_dependencies_recursively: Option<bool>toMatchGroupbinding_manual_code_splitting_options.rsBindingMatchGroupnormalize_binding_options.rsmanual_code_splitting.rstrueprepare_build_context.rshas_non_recursive_dependency_capture()helper, 2 callsitesoutput-options.tsCodeSplittingGroupwith JSDocvalidator.tsExample
Test plan
falseexcludes transitive deps, group without inherits globalfalse+preserveEntrySignatures: 'strict'→ erroradvanced-chunkstests passjust build-rolldown) passes