Skip to content

fix: prevent cycles for indirectly awaited dynamic imports - #6516

Merged
lukastaegert merged 4 commits into
rollup:masterfrom
ryanchou1994:fix/preserve-entry-side-effect-split
Oct 10, 2026
Merged

lukastaegert merged 4 commits into
rollup:masterfrom
ryanchou1994:fix/preserve-entry-side-effect-split

Conversation

@ryanchou1994

Copy link
Copy Markdown
Contributor

This PR contains:

  • bugfix
  • feature
  • refactor
  • documentation
  • other

Are tests included?

  • yes (bugfixes and features will not be merged without tests)
  • no

Breaking Changes?

  • yes (breaking changes will not be merged unless absolutely necessary)
  • no

List any relevant issue numbers:

Description

With preserveEntrySignatures: false, awaited dynamic imports keep their dependent-entry coloring so shared modules are not merged into an entry that is still evaluating. The existing analysis only recognized a direct await import(). When top-level await calls through a static dependency that performs the dynamic import, the shared module could instead be merged into the entry, making the dynamic chunk import back from that evaluating entry and creating a cycle.

Track dynamic entries reached by each static dependency graph and, when that graph contains top-level await, preserve their awaited coloring. This is intentionally conservative: an unawaited dynamic import reachable from the same graph can retain an extra shared chunk, but it avoids an incorrect cycle when the await relationship is indirect and cannot be recovered from the direct importer metadata.

The new chunking fixture covers both ES and SystemJS output. The focused fixture, the full chunking suite, the full core suite, targeted ESLint, and formatting checks pass on macOS and Linux.

generated with AI

Copilot AI balanced review requested due to automatic review settings September 15, 2026 04:25
@vercel

vercel Bot commented Sep 15, 2026

Copy link
Copy Markdown

@ryanchou1994 is attempting to deploy a commit to the rollup-js Team on Vercel.

A member of the Team first needs to authorize it.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

Copilot review overview

🟡 Changes recommended

The critical awaited-dependency mapping defect must be fixed before approval.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)

Comment thread src/utils/chunkAssignment.ts
@ryanchou1994

Copy link
Copy Markdown
Contributor Author

Hi @lukastaegert, following up on this. I replied to the Copilot comment above with the fixture results (the indirectly awaited case is covered by the added tests, and the remaining dead-lock variant is outside this PR's scope). Happy to adjust if you see it differently. Also, if you get a chance, could you approve the CI run so it can run on this branch?

@vercel

vercel Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
rollup Ready Ready Preview Oct 10, 2026 8:52am UTC

Request Review

@codecov

codecov Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.15%. Comparing base (9568144) to head (fa78b84).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #6516   +/-   ##
=======================================
  Coverage   98.15%   98.15%           
=======================================
  Files         379      379           
  Lines       13805    13826   +21     
  Branches     2925     2927    +2     
=======================================
+ Hits        13550    13571   +21     
  Misses        212      212           
  Partials       43       43           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Performance report

  • BUILD: 7630ms, 817 MB
    • initialize: 0ms, 23.8 MB
    • generate module graph: 2795ms, 629 MB
      • generate ast: 1412ms, 618 MB
    • sort and bind modules: 419ms, 691 MB
    • mark included statements: 4345ms (+92ms, +2.2%), 817 MB
      • treeshaking pass 1: 2549ms, 833 MB (+2%)
      • treeshaking pass 2: 489ms, 804 MB
      • treeshaking pass 3: 415ms, 803 MB
      • treeshaking pass 4: 400ms, 811 MB (+2%)
      • treeshaking pass 5: 400ms, 817 MB
  • GENERATE: 659ms, 868 MB
    • initialize render: 0ms, 817 MB
    • generate chunks: 44ms, 814 MB (+2%)
      • optimize chunks: 0ms, 823 MB (+2%)
    • render chunks: 598ms, 842 MB
    • transform chunks: 15ms, 868 MB
    • generate bundle: 0ms, 868 MB

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Thank you for your contribution! ❤️

You can try out this pull request locally by installing Rollup via

npm install ryanchou1994/rollup#fix/preserve-entry-side-effect-split

Notice: Ensure you have installed the latest nightly Rust toolchain. If you haven't installed it yet, please see https://www.rust-lang.org/tools/install to learn how to download Rustup and install Rust.

or load it into the REPL: https://rollup-3ighjiwpu-rollup-js.vercel.app/repl/?pr=6516

Warning

The REPL link above is outdated: the available artefacts represent an earlier commit and do not include the latest changes of this pull request.

@lukastaegert lukastaegert left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Marking every dynamic import reached from an entry with a top-level await
as awaited was not conservative: the awaited already-loaded atoms were
intersected over all awaiting importers, so an additional awaiting importer
that had not loaded a shared chunk itself could drop that chunk from the
protected set. The chunk was then merged into the entry and the dynamic
entry imported the entry back, creating a new cycle.

Awaited already-loaded atoms are now the union over all awaiting
importers, and entries that await a dynamic entry indirectly are
registered as awaiting importers of that entry.
@ryanchou1994

Copy link
Copy Markdown
Contributor Author

Thanks for the REPL, you are right. Marking every dynamic import reached from an entry with a top-level await as awaited was only half of it: the awaited already-loaded atoms were intersected over all awaiting importers, so once y (which reaches b.js with a top-level await) was also treated as awaiting d, the intersection dropped shared from the protected set and it got folded into main. That is the opposite of conservative.

Pushed 5bd6869 on top of your merge:

  • getAwaitedAlreadyLoadedAtomsByEntry now takes the union over awaiting importers instead of the intersection. A single still-evaluating importer is enough to make merging an atom into that importer's chunk a cycle, so over-marking can now only keep an extra chunk, never create a cycle.
  • addAwaitingEntries registers the entry-level awaiting relation in dynamicallyDependentEntriesByAwaitedDynamicEntry. Before, that map only came from modules that contain a top-level await themselves, which is why the original fixture only worked through the -1n default of an empty set.
  • Your example is added as preserve-entry-signatures/false-with-awaited-dynamic-import-chain. It fails on the previous head and now produces the same chunks as master (shared stays its own chunk).

The full suite passes locally and no existing expected output changed, so the union does not alter chunking for the current fixtures. The known gap where the import() lives inside another dynamic chunk (main awaits import('./a.js'), a returns import('./dynamic.js')) is unchanged and out of scope here.

generated with AI

@lukastaegert lukastaegert left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, looks good now!

@lukastaegert
lukastaegert added this pull request to the merge queue Oct 10, 2026
Merged via the queue into rollup:master with commit d45f8e8 Oct 10, 2026
49 checks passed
@github-actions

Copy link
Copy Markdown

This PR has been released as part of rollup@4.64.5. You can test it via npm install rollup.

This branch was successfully deployed

1 active deployment
Preview — fa78b84e Deployed Oct 10, 2026 by vercel[bot]
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.

preserveEntrySignatures adds exports to possible side-effects entry file

3 participants