Skip to content

fix: deduplicate re-exported external named imports within chunks#10225

Draft
IWANABETHATGUY wants to merge 2 commits into
mainfrom
fix/3427-dedupe-external-reexport-imports
Draft

fix: deduplicate re-exported external named imports within chunks#10225
IWANABETHATGUY wants to merge 2 commits into
mainfrom
fix/3427-dedupe-external-reexport-imports

Conversation

@IWANABETHATGUY

@IWANABETHATGUY IWANABETHATGUY commented Jul 11, 2026

Copy link
Copy Markdown
Member

Description

close #3427

Supersedes #8218.

Root cause

#3405 stopped merging external named imports whose local binding is re-exported (export { a } from 'ext'), because eager merging happens before chunks exist and can move a symbol's canonical ref where a chunk can't render it: in ESM output a chunk renders external imports only from its own modules' named_imports (direct_imports_from_external_modules), and compute_chunk_imports intentionally skips external-owned symbols. That fixed the Nuxt crash, but it left every re-export of the same (external module, name) pair as its own symbol:

// tests/rolldown/function/external/export_external, before
import { a, a as a$1, a as a1, b, b as b$1, b as b1 } from "external";

Fix

Defer the merge until chunk assignment is known, then merge strictly per chunk (next to the existing namespace merger in merge_external_import_symbols):

  • bind_imports_and_exports now collects re-exported external named imports into deferred_external_binding_merges, keyed by (external module, imported name), instead of skipping them.
  • At chunk level, each group merges within its chunk. The canonical is the first used symbol by (exec_order, symbol index), preferring a member whose name equals the imported name so the output reads import { a } rather than import { a as a$1 }.
  • The facade created by the eager merger (for plain, non-re-exported imports) is folded into that canonical only when every module owning those plain imports lives in the same chunk. A plain import is only referenced from its own module, so in that case all of the facade's references stay inside the chunk; otherwise the facade stays a root and each chunk keeps rendering its own import.

The per-chunk restriction preserves #3405's constraint by construction: a merged group's canonical always stays a symbol declared inside that chunk, so cross-chunk references keep flowing through the regular cross-chunk import/export machinery. (This is also where #8218 went wrong: it linked re-export symbols from every chunk into the external-owned facade, which reintroduces the #3405 scenario for indirect importers.)

// tests/rolldown/function/external/export_external, after
import { a, b } from "external";

Tests

Two node-executed fixtures assert runtime identity of the merged bindings:

  • external/reexport_external_binding_dedup: single chunk; plain import + re-export + pass-through re-export (named and default) collapse to one specifier each.
  • external/splitting_reexport_external_dedup: the fix(splitting): indirect external symbol #3405 splitting shape; the shared chunk collapses its duplicate re-exports into one import/one cross-chunk export, while the entry chunks' facade correctly does not fold across chunks.

Rollup compat suite: 1214 passed / 0 failed.

Copy link
Copy Markdown
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more


How to use the Graphite Merge Queue

Add the label graphite: merge-when-ready to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@IWANABETHATGUY
IWANABETHATGUY force-pushed the refactor/extract-external-import-symbol-merging branch from b9fc5a6 to fe6becc Compare July 12, 2026 11:05
@graphite-app
graphite-app Bot changed the base branch from refactor/extract-external-import-symbol-merging to graphite-base/10225 July 12, 2026 11:35
@graphite-app
graphite-app Bot force-pushed the graphite-base/10225 branch from fe6becc to c5adb85 Compare July 12, 2026 11:40
@graphite-app
graphite-app Bot force-pushed the fix/3427-dedupe-external-reexport-imports branch from 959bc01 to c85b8fe Compare July 12, 2026 11:40
@graphite-app
graphite-app Bot changed the base branch from graphite-base/10225 to main July 12, 2026 11:41
@graphite-app
graphite-app Bot force-pushed the fix/3427-dedupe-external-reexport-imports branch from c85b8fe to 61eebc2 Compare July 12, 2026 11:41
@netlify

netlify Bot commented Jul 12, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs canceled.

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

@IWANABETHATGUY
IWANABETHATGUY force-pushed the fix/3427-dedupe-external-reexport-imports branch from 61eebc2 to e8b4f02 Compare July 12, 2026 12:36
@IWANABETHATGUY

Copy link
Copy Markdown
Member Author

@codex[agent]

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@IWANABETHATGUY
IWANABETHATGUY force-pushed the fix/3427-dedupe-external-reexport-imports branch from 91b1235 to 0cb04b3 Compare July 14, 2026 07:32
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.

Polish external symbol merging.

1 participant