Skip to content

fix(dev): disable lazy barrel in dev mode#10060

Merged
graphite-app[bot] merged 1 commit into
mainfrom
07-01-fix_disable_lazy_barrel_in_dev_mode
Jul 1, 2026
Merged

fix(dev): disable lazy barrel in dev mode#10060
graphite-app[bot] merged 1 commit into
mainfrom
07-01-fix_disable_lazy_barrel_in_dev_mode

Conversation

@shulaoda

@shulaoda shulaoda commented Jul 1, 2026

Copy link
Copy Markdown
Member

What is this PR solving?

Dev mode (experimental.devMode) requires treeshaking to be disabled. We already force treeshake off when experimental.dev_mode.is_some() in prepare_build_context.

The lazy_barrel optimization is a form of treeshaking for barrel (re-export) files: it lazily tracks and prunes re-exports based on what is actually used. It is enabled by default (is_lazy_barrel_enabled() returns true when the option is None). This means that in dev mode, treeshaking is off but lazy barrel is still on, which is inconsistent and can lead to incorrect behavior.

This PR forces lazy_barrel off whenever dev mode is enabled. While doing so, the two separate experimental.dev_mode.is_some() checks (one forcing incremental_build on, the other forcing treeshake off) are merged into a single block, so all dev-mode overrides live in one place.

Changes

crates/rolldown/src/utils/prepare_build_context.rs:

let mut raw_treeshake = raw_options.treeshake;
let mut experimental = raw_options.experimental.unwrap_or_default();
if experimental.dev_mode.is_some() {
  experimental.incremental_build = Some(true);
  // Dev mode requires treeshaking to be disabled, and lazy barrel relies on
  // treeshaking, so it must be disabled as well.
  raw_treeshake = TreeshakeOptions::Boolean(false);
  experimental.lazy_barrel = Some(false);
}

Because is_lazy_barrel_enabled() reads self.lazy_barrel.unwrap_or(true), setting it to Some(false) forces the feature off, overriding the default-on behavior. All downstream call sites (module_task, module_loader, flat_options) go through is_lazy_barrel_enabled(), so this single override turns the feature off everywhere for dev mode.

Why do it here?

This block already normalizes dev-mode-specific overrides, and it runs before flat_options and the module loader read the flag, so the override takes effect for the whole build. Keeping the treeshake, incremental_build, and lazy_barrel decisions in one obvious block makes the shared reason explicit. raw_treeshake is declared just above the block (it only depends on raw_options.treeshake), so pulling it up to merge the two checks is safe.

@netlify

netlify Bot commented Jul 1, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs canceled.

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

@h-a-n-a h-a-n-a assigned sapphi-red and unassigned h-a-n-a Jul 1, 2026
@codspeed-hq

codspeed-hq Bot commented Jul 1, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 7 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing 07-01-fix_disable_lazy_barrel_in_dev_mode (d9ef0d3) with main (cf91eb3)

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.

h-a-n-a commented Jul 1, 2026

Copy link
Copy Markdown
Member

Merge activity

## What is this PR solving?

Dev mode (`experimental.devMode`) requires treeshaking to be disabled. We already force `treeshake` off when `experimental.dev_mode.is_some()` in `prepare_build_context`.

The `lazy_barrel` optimization is a form of treeshaking for barrel (re-export) files: it lazily tracks and prunes re-exports based on what is actually used. It is enabled by default (`is_lazy_barrel_enabled()` returns `true` when the option is `None`). This means that in dev mode, treeshaking is off but lazy barrel is still on, which is inconsistent and can lead to incorrect behavior.

This PR forces `lazy_barrel` off whenever dev mode is enabled. While doing so, the two separate `experimental.dev_mode.is_some()` checks (one forcing `incremental_build` on, the other forcing `treeshake` off) are merged into a single block, so all dev-mode overrides live in one place.

## Changes

`crates/rolldown/src/utils/prepare_build_context.rs`:

```rust
let mut raw_treeshake = raw_options.treeshake;
let mut experimental = raw_options.experimental.unwrap_or_default();
if experimental.dev_mode.is_some() {
  experimental.incremental_build = Some(true);
  // Dev mode requires treeshaking to be disabled, and lazy barrel relies on
  // treeshaking, so it must be disabled as well.
  raw_treeshake = TreeshakeOptions::Boolean(false);
  experimental.lazy_barrel = Some(false);
}
```

Because `is_lazy_barrel_enabled()` reads `self.lazy_barrel.unwrap_or(true)`, setting it to `Some(false)` forces the feature off, overriding the default-on behavior. All downstream call sites (`module_task`, `module_loader`, `flat_options`) go through `is_lazy_barrel_enabled()`, so this single override turns the feature off everywhere for dev mode.

## Why do it here?

This block already normalizes dev-mode-specific overrides, and it runs before `flat_options` and the module loader read the flag, so the override takes effect for the whole build. Keeping the `treeshake`, `incremental_build`, and `lazy_barrel` decisions in one obvious block makes the shared reason explicit. `raw_treeshake` is declared just above the block (it only depends on `raw_options.treeshake`), so pulling it up to merge the two checks is safe.
@graphite-app graphite-app Bot force-pushed the 07-01-fix_disable_lazy_barrel_in_dev_mode branch from d9ef0d3 to a0b9748 Compare July 1, 2026 06:42
@graphite-app graphite-app Bot merged commit a0b9748 into main Jul 1, 2026
34 checks passed
@graphite-app graphite-app Bot deleted the 07-01-fix_disable_lazy_barrel_in_dev_mode branch July 1, 2026 06:47
@h-a-n-a

h-a-n-a commented Jul 1, 2026

Copy link
Copy Markdown
Member

Context:

This is caused by inconsistent tree shaking in FBM. In the future, we can investigate whether the ROI for enabling barrel optimization is high enough for us to reenable it again. If so, then we can start thinking of whether it's implemented based on one-pipeline module (making HMR and normal build in the same pipeline) or implemented based on the current standalone HMR architecture.

@rolldown-guard rolldown-guard Bot mentioned this pull request Jul 1, 2026
shulaoda added a commit that referenced this pull request Jul 1, 2026
## [1.1.4] - 2026-07-01

### 🚀 Features

- disable `experimental.lazyBarrel` by default (#10071) by @shulaoda

### 🐛 Bug Fixes

- dev: disable lazy barrel in dev mode (#10060) by @shulaoda
- generate: keep full JSON interface under preserveModules namespa… (#10056) by @IWANABETHATGUY
- check finalize_other_specifiers in its own Debug attribute (#10032) by @shulaoda
- serialize the KeepAssign unused minify option as "keep_assign" (#10031) by @shulaoda
- keep fragments after the newline fragment in MagicString::last_line (#10023) by @shulaoda
- generate: undeclared JSON named exports under preserveModules (#10020) (#10027) by @IWANABETHATGUY
- deconflict: rename CJS-wrapped locals that shadow chunk-root bindings (#9921) by @IWANABETHATGUY
- rolldown: keep entry facade when a shared chunk holds another entry's module (#9997) by @hyf0
- treeshake: also bail JSON default split when the object escapes (#9996) by @IWANABETHATGUY
- don't classify await in a strict-mode function as top-level await (#9987) by @shulaoda
- avoid spurious leading newline in addon hooks (banner/footer/intro/outro) (#9989) by @shulaoda
- handle JSON default mutation bailouts (#9972) by @TheAlexLichter
- plugin: make lazy hook metadata enumerable (#9991) by @TheAlexLichter
- dev: make init errors in lazy-compiled modules catchable (#9981) by @h-a-n-a
- treeshake: keep computed-key side effects on namespace member access (#9986) by @shulaoda
- binding: validate replace plugin delimiters length instead of panicking (#9984) by @shulaoda
- reconstruct nested rest patterns in into_expression (#9980) by @IWANABETHATGUY
- reconstruct rest patterns as spread in into_expression (#9976) by @shulaoda
- preserve export keyword on multi-declarator exports under keepNames (#9974) by @shulaoda
- deterministically keep the shortest name for deduplicated assets (#9948) by @x1024
- treeshake: apply @__NO_SIDE_EFFECTS__ to cross-chunk namespace calls (#9960) by @IWANABETHATGUY

### 🚜 Refactor

- drop redundant program scope enter/leave in finalizer (#10049) by @shulaoda
- deconflict: extract collect_chunk_scope_captured_names (#10006) by @IWANABETHATGUY
- unify pre-scan multi-declarator split into one decision site (#9982) by @IWANABETHATGUY
- common: return bool from SymbolRef::is_not_reassigned (#9962) by @IWANABETHATGUY

### 📚 Documentation

- rolldown: remove outdated comment for removing parenthesized expression (#10062) by @Dunqing
- use GitHub-flavored alert for Etiquette note in contribution guide (#10012) by @IWANABETHATGUY
- replace: explain the delimiters left and right boundaries (#9985) by @shulaoda
- ast-mutation: remove stale Address Use section after pre-scan refactor (#9983) by @IWANABETHATGUY
- remove fathom (#9968) by @mdong1909
- contribution-guide: code-format main branch references (#9966) by @IWANABETHATGUY
- contribution-guide: fix stale REPL note and tidy wording (#9957) by @hyf0
- contribution-guide: clarify when to discuss before opening a PR (#9955) by @hyf0

### ⚡ Performance

- disable preserve_parens across all parse paths (#10057) by @Dunqing
- common: inline declared_symbols with SmallVec (#9920) by @IWANABETHATGUY
- common: pack TaggedSymbolRef into 8 bytes (#9919) by @IWANABETHATGUY
- sourcemap: skip newline scan on the no-sourcemap join fast path (#9936) by @Boshen

### 🧪 Testing

- dev: error in lazy module should be catchable (#9975) by @sapphi-red
- dev: reject unknown lazy compile modules (#9969) by @sapphi-red

### ⚙️ Miscellaneous Tasks

- deps: update actions/cache action to v6 (#10001) by @renovate[bot]
- trigger vite ecosystem-ci from PR comments (#10058) by @shulaoda
- deps: update napi to v3.10.0 (#10063) by @renovate[bot]
- remove unused From impl for RolldownLabelSpan (#10055) by @shulaoda
- remove dead Diagnostic::with_kind method (#10054) by @shulaoda
- remove unused StatementExt methods (#10053) by @shulaoda
- remove unused ExpressionExt methods (#10052) by @shulaoda
- remove commented-out re_export_all_names field (#10051) by @shulaoda
- deps: update pnpm to v11.9.0 (#10047) by @renovate[bot]
- remove the unused BindingGenerateHmrPatchReturn napi type (#10034) by @shulaoda
- remove the dead inline_entry_chunk_wrapping scaffolding (#10037) by @shulaoda
- deps: bump oxc_resolver to 11.22.0 (#10045) by @Boshen
- remove never-constructed MatchImportKind::_Ignore variant (#10041) by @shulaoda
- remove the unused ScheduledBuild napi struct (#10033) by @shulaoda
- remove dead compute_hmr_update_single method (#10040) by @shulaoda
- drop the redundant visited.insert in manual code splitting (#10038) by @shulaoda
- remove the dead output_assets vector in render_chunk_to_assets (#10036) by @shulaoda
- remove the unused From<String>/Display impls for BindingLogLevel (#10035) by @shulaoda
- deps: upgrade oxc to 0.138.0 and migrate to per-type AST construction (#10018) by @shulaoda
- deps: update rust crates (#9911) by @renovate[bot]
- deps: update test262 submodule for tests (#10016) by @rolldown-guard[bot]
- deps: update github actions (#9999) by @renovate[bot]
- deps: update npm packages (#10000) by @renovate[bot]

### ◀️ Revert

- "fix(plugin): make lazy hook metadata enumerable (#9991)" (#10005) by @shulaoda

### ❤️ New Contributors

* @x1024 made their first contribution in [#9948](#9948)

Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants