Skip to content

fix(bundler): prevent duplicate export statements with code splitting - #26477

Closed
robobun wants to merge 1 commit into
mainfrom
claude/fix-duplicate-exports-splitting
Closed

robobun wants to merge 1 commit into
mainfrom
claude/fix-duplicate-exports-splitting

Conversation

@robobun

@robobun robobun commented Jan 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes duplicate export { symbol } statements when code splitting is enabled and a file is both an entry point and imported by another entry point

Root Cause

When code splitting is enabled and a module is both:

  1. An entry point (has its own exports)
  2. Imported by another entry point (needs cross-chunk exports)

Two separate code paths generate export statements:

  1. generateEntryPointTailJS generates exports for entry point's named exports
  2. computeCrossChunkDependencies generates cross_chunk_suffix_stmts for symbols that other chunks need to import

Both paths were adding export clauses to the output, resulting in:

export { logStuff };

export { logStuff };  // duplicate!

Fix

Skip generating cross_chunk_suffix_stmts for entry point chunks since generateEntryPointTailJS already handles their exports. The exports_to_other_chunks map is still populated for the import side to work correctly.

Test plan

  • Added regression test in test/regression/issue/10631.test.ts
  • Verified test fails with USE_SYSTEM_BUN=1 and passes with bun bd test
  • Existing bundler splitting tests pass (bun bd test test/bundler/bundler_splitting.test.ts)

Fixes #10631

🤖 Generated with Claude Code

When code splitting is enabled and a file is both an entry point and
imported by another entry point, the bundler was generating duplicate
export statements. This happened because:

1. generateEntryPointTailJS generates exports for entry point's named exports
2. computeCrossChunkDependencies generates cross_chunk_suffix_stmts for
   symbols that other chunks need to import

Both paths were adding export clauses to the output, resulting in invalid
JavaScript with duplicate `export { symbol }` statements.

The fix skips generating cross_chunk_suffix_stmts for entry point chunks
since generateEntryPointTailJS already handles their exports. The
exports_to_other_chunks map is still populated for the import side.

Fixes #10631

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@robobun

robobun commented Jan 27, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 12:17 AM PT - Jan 27th, 2026

❌ Your commit 1e816992 has 2 failures in Build #35921 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 26477

That installs a local version of the PR into your bun-26477 executable, so you can run:

bun-26477 --bun

@coderabbitai

coderabbitai Bot commented Jan 27, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR fixes duplicate exports generated by bun build when code-splitting is enabled and entry points are imported or exported. The fix adds a conditional guard in the cross-chunk dependency computation to prevent entry point chunks from emitting duplicate export statements. Regression tests validate the fix across multiple code-splitting scenarios.

Changes

Cohort / File(s) Summary
Core bundler fix
src/bundler/linker_context/computeCrossChunkDependencies.zig
Modified cross-chunk export statement generation to skip emitting exports for entry point chunks (added condition: clause_items.len > 0 and not an entry point), while still tracking exports for the import side. Comment clarifies that entry points' exports are generated separately by generateEntryPointTailJS.
Regression tests
test/regression/issue/10631.test.ts
Added comprehensive test suite with three scenarios: (1) minimal pair with code-splitting verifying no duplicate exports, (2) multiple entry points importing shared modules, (3) entry point that both exports and imports. Tests verify export counts, content integrity, and runtime execution correctness.

Possibly related PRs

Suggested reviewers

  • Jarred-Sumner
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main fix: preventing duplicate export statements when code splitting is enabled with entry points.
Description check ✅ Passed The description comprehensively covers what the PR does, root cause, fix, and test plan aligned with the template sections.
Linked Issues check ✅ Passed The PR directly addresses issue #10631 by preventing duplicate export statements in code-split bundles when modules are both entry points and imported by other entry points.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the duplicate export issue: the bundler logic change and the regression test directly support the stated objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@test/regression/issue/10631.test.ts`:
- Around line 25-28: The test currently awaits stdout, stderr and exitCode
together but asserts exitCode before checking stdout, making failures less
clear; update the assertions in the build blocks that use Promise.all([...])
(variables stdout, stderr, exitCode) so you assert stdout (and stderr if
expected) before asserting exitCode — i.e., add or move the expect(stdout)
assertion to come prior to expect(exitCode). Apply this change to the
occurrences around the Promise.all usage (the blocks at lines ~25-28, ~76-79,
and ~138-141).

Comment on lines +25 to +28
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stderr).toBe("");
expect(exitCode).toBe(0);

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.

🧹 Nitpick | 🔵 Trivial

Assert build stdout before exitCode for clearer failures.

This follows the test guideline and makes failures more actionable.

♻️ Proposed change (apply to each build block)
-  const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
-
-  expect(stderr).toBe("");
-  expect(exitCode).toBe(0);
+  const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
+
+  expect(stdout).toBe("");
+  expect(stderr).toBe("");
+  expect(exitCode).toBe(0);

As per coding guidelines, ...

Also applies to: 76-79, 138-141

🤖 Prompt for AI Agents
In `@test/regression/issue/10631.test.ts` around lines 25 - 28, The test currently
awaits stdout, stderr and exitCode together but asserts exitCode before checking
stdout, making failures less clear; update the assertions in the build blocks
that use Promise.all([...]) (variables stdout, stderr, exitCode) so you assert
stdout (and stderr if expected) before asserting exitCode — i.e., add or move
the expect(stdout) assertion to come prior to expect(exitCode). Apply this
change to the occurrences around the Promise.all usage (the blocks at lines
~25-28, ~76-79, and ~138-141).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bun build creates files with duplicate exports

2 participants