Skip to content

Modules not handled by manualChunks are bundled into to the first manualChunk encountered (alphabetically) when the module is imported by multiple manualChunks #6086

@maiieul

Description

@maiieul

Rollup Version

4.50.0

Operating System (or Browser)

MacOS / Chrome

Node Version (if applicable)

No response

Link To Reproduction

https://stackblitz.com/edit/rollup-repro-ntrtgr3g?file=rollup.config.js

Expected Behaviour

Module ids that are not handled by manualChunks (returned as undefined or null) should not be bundled together with other manualChunks and instead be processed by Rollup as if there was no manualChunks.

Actual Behaviour

A module id that is not handled by manualChunks (returned as undefined or null) gets bundled together to the first manualChunk encountered (alphabetically), when the module in question is imported by multiple manualChunks.

In the repro, the entry1-task.js, entry2-task.js, and entry3-task.js share common utils imports, but all the utils code ends up in entry1-[hash].js

This leads to 2 issues:

  1. The first (alphabetically) manualChunk that imports a non manualChunk module that is also imported by other manualChunks will get bigger. This can affect multiple bundles in a non repro setup. This is represented by the entry1-[hash].js bundle in the repro.
  2. The manualChunks static import graph becomes inaccurate (which can change the dynamic imports graph in complex setups). In the repro, every time entry2-[hash].js and entry3-[hash].js are to be requested, the app will also request entry1-[hash].js, even though they are not requesting the original entry1-task.js or entry1-onClick.js exports, just the utils in common.

Note: in the repro, if you remove manualChunks you will notice that Rollup is smart enough to create two utils bundles based on the imports graph instead of 3, because entry3-task.js does not import from utils1.js. Here it's a simplified example, but this shows that the bundler is smarter at bundling non manualChunk modules when they're not merged with other manualChunks.

This issue is particularly bad in qwik, where we first split the developers code into the smallest possible segments and emit them, and then group the emitted segments that share a common entry point together (using manualChunks). As a result qwik has ~10x more bundles than most other frameworks and we can be surgical about what to preload and when. But for that we need to leverage the static and dynamic import graphs to know which bundles to preload ahead of time or be able to reprioritize them on user interaction. When some bundles are bigger than expected and the graph is incorrect, user interactions are delayed even more on bad network conditions. I also explain the issue here with a qwik repro: QwikDev/qwik#7882.

The problem comes from generateChunks -> getChunkAssignments -> getChunkDefinitionsFromManualChunks -> addStaticDependenciesToManualChunk -> if (!(dependency instanceof ExternalModule || modulesInManualChunks.has(dependency))) { modulesToHandle.add(dependency) }.

This can be fixed by removing the addStaticDependenciesToManualChunk logic. I'm not sure if that would be a breaking change though. I assume most projects use manualChunks for code-splitting purposes. Could it be we're the only ones to leverage manualChunks to group files together and that's why it doesn't work for us 😄?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions