Skip to content

feat(code-splitting): support group-local includeDependenciesRecursively#9587

Merged
graphite-app[bot] merged 1 commit into
mainfrom
arch-wren
Jun 2, 2026
Merged

feat(code-splitting): support group-local includeDependenciesRecursively#9587
graphite-app[bot] merged 1 commit into
mainfrom
arch-wren

Conversation

@hyf0

@hyf0 hyf0 commented May 27, 2026

Copy link
Copy Markdown
Member

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

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

  • Chunking behavior test: group with false excludes transitive deps, group without inherits global
  • Validation test: per-group false + preserveEntrySignatures: 'strict' → error
  • All existing advanced-chunks tests pass
  • Full build (just build-rolldown) passes

Copilot AI review requested due to automatic review settings May 27, 2026 13:23
@netlify

netlify Bot commented May 27, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs ready!

Name Link
🔨 Latest commit 2d507b6
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/6a16f04044a6ab00091e850c
😎 Deploy Preview https://deploy-preview-9587--rolldown-rs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 preserveEntrySignatures validation 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 underlying InvalidOptionType::IncludeDependenciesRecursivelyWithConflictPreserveEntrySignatures message text still hard-codes codeSplitting.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(),
            ),
          ));
        }

@netlify

netlify Bot commented May 27, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs canceled.

Name Link
🔨 Latest commit 5e05eba
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/6a1e47f86f5393000888a854

@codspeed-hq

codspeed-hq Bot commented May 27, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 4 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing arch-wren (cde6270) with main (73650f4)2

Open in CodSpeed

Footnotes

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

  2. No successful run was found on main (094f8c0) during the generation of this report, so 73650f4 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Comment thread crates/rolldown/src/utils/prepare_build_context.rs Outdated
Comment thread crates/rolldown/src/utils/prepare_build_context.rs Outdated
@pkg-pr-new

pkg-pr-new Bot commented May 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

@rolldown/browser

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/browser@9587

@rolldown/debug

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/debug@9587

rolldown

npm i https://pkg.pr.new/rolldown/rolldown@9587

@rolldown/binding-android-arm64

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-android-arm64@9587

@rolldown/binding-darwin-arm64

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-arm64@9587

@rolldown/binding-darwin-x64

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-x64@9587

@rolldown/binding-freebsd-x64

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-freebsd-x64@9587

@rolldown/binding-linux-arm-gnueabihf

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm-gnueabihf@9587

@rolldown/binding-linux-arm64-gnu

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-gnu@9587

@rolldown/binding-linux-arm64-musl

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-musl@9587

@rolldown/binding-linux-ppc64-gnu

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-ppc64-gnu@9587

@rolldown/binding-linux-s390x-gnu

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-s390x-gnu@9587

@rolldown/binding-linux-x64-gnu

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-gnu@9587

@rolldown/binding-linux-x64-musl

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-musl@9587

@rolldown/binding-openharmony-arm64

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-openharmony-arm64@9587

@rolldown/binding-wasm32-wasi

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-wasm32-wasi@9587

@rolldown/binding-win32-arm64-msvc

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-arm64-msvc@9587

@rolldown/binding-win32-x64-msvc

npm i https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-x64-msvc@9587

commit: b5b4eb2

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Comment thread crates/rolldown/src/utils/prepare_build_context.rs Outdated
Comment thread packages/rolldown/src/options/output-options.ts Outdated

hyf0 commented Jun 2, 2026

Copy link
Copy Markdown
Member Author

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
@graphite-app graphite-app Bot merged commit 5e05eba into main Jun 2, 2026
34 checks passed
@graphite-app graphite-app Bot deleted the arch-wren branch June 2, 2026 03:08
@rolldown-guard rolldown-guard Bot mentioned this pull request Jun 3, 2026
shulaoda added a commit that referenced this pull request Jun 3, 2026
> [!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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support group-local includeDependenciesRecursively for route fanout chunking

4 participants