Skip to content

[🐞] over-preloading due to inaccurate bundle-graph static import graph #7882

@maiieul

Description

@maiieul

Which component is affected?

Qwik Runtime

Describe the bug

Running a client build for a qwik app in 1.16 (or 2.0.0-beta-7) results in an incorrect q-manifest which results in an inaccurate bundle-graph. This is because Rollup's generateChunks merges static node_modules into manualChunks bundles aliases.

Reproduction

https://github.com/maiieul/qwik-overpreloading-inaccurate-bundle-graph-static-imports-graph

Steps to reproduce

Run build.client look at the "alert.tsx_Description_component_MExiH8uFvn4.js" component in the q-manifest. It includes the orgins for a lot of node_modules (e.g tailwind-merge, css-tree, etc.). Those are imported by @qwik-ui/utils which the Alert component is importing. The problem is that when rollup sees this, it merges the @qwik-ui/utils node_modules into the "alert.tsx_Description_component_MExiH8uFvn4.js" bundle, even though the node_modules in question are also used in other bundles, resulting in those other bundles importing "alert.tsx_Description_component_MExiH8uFvn4.js" and its static+dynamic dependencies.

Tip: you can run the debugger build.client from within vscode to see this in action.

System Info

🙈

Potential solutions

We can solve this in two ways:

1) fix Rollup

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

We can fix it by removing the addStaticDependenciesToManualChunk logic.

2) Improve our manualChunks logic

The current manualChunks logic will take related QRL segments outputs from the optimizer, and merge them together:

    const { pathId } = parseId(id);
    const segment = segments.get(normalizePath(pathId));
    if (segment) {
      const { hash } = segment;
      const chunkName = (opts.entryStrategy as SmartEntryStrategy).manual?.[hash] || segment.entry;
      if (chunkName) {
        return chunkName;
      }
    }

This not only works for the app's QRL segments, but also for node_modules QRL segments (e.g. import { Accordion } from "@qwik-ui/headless"; will contain many segments.

This means that for the remaining non QRL ids and node_modules ids, we can safely return them into separate bundles and group node_modules by name, taking into account their size.

Solution 2 gives us more control, but we might end up bundling together bundles that were explicitly set to be separate by a library, or create multiple bundles for code that will always be used together.

Edit: updated solution 1.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions