Skip to content

[Bug]: strictExecutionOrder emits external bindings without their import #10013

Description

@chuganzy

Problem

I found this while bundling Storybook. The reproduction below is reduced, but the original failure was a generated Storybook bundle where a lazy __esmMin wrapper still referenced external bindings after the corresponding external import had been removed.

https://github.com/storybookjs/storybook/blob/d0ef37ec3f69e9ceff6328e9772cc506dd58f995/code/builders/builder-vite/src/utils/vite-features.ts#L27

When strictExecutionOrder is enabled, Rolldown can generate a shared chunk that still references bindings from an external module, but the corresponding external import statement is missing from the output.

The generated code can look like this:

var init_leaf = __esmMin(() => {
  value = first(second);
});

In that chunk, first and second came from an external import:

import { first, second } from "dep";

but the import declaration is removed, so the emitted chunk contains free identifiers and fails at runtime.

Expected behavior

If generated code still references bindings from an external import, the ImportDeclaration that defines those bindings should remain in the same output chunk.

Why this seems tied to strictExecutionOrder

With strictExecutionOrder, ESM modules may be wrapped in lazy __esmMin initializers to preserve evaluation order across entries. That wrapper can keep top-level module code alive even after tree-shaking. In this case, the wrapper body still uses the external bindings, but finalization removes the external import statement because the import record does not resolve to an internal module.

Minimal reproduction

import { rolldown } from 'rolldown';

const modules = {
  'entry-a': `import { value } from 'first'; console.log(value); export { read } from 'first';`,
  'entry-b': `import { value } from 'second'; console.log(value); export { read } from 'second';`,
  first: `export { value, read } from 'shared';`,
  second: `export { value, read } from 'shared';`,
  shared: `export { value, read } from 'leaf';`,
  leaf: `import { first, second } from 'dep'; export const value = first(second); export function read() { return value; }`,
};

const plugin = {
  name: 'virtual',
  resolveId(id) {
    if (id in modules) return id;
  },
  load(id) {
    return modules[id];
  },
};

const bundle = await rolldown({
  input: {
    a: 'entry-a',
    b: 'entry-b',
  },
  external: ['dep'],
  platform: 'browser',
  plugins: [plugin],
  treeshake: {
    moduleSideEffects: false,
  },
});

const { output } = await bundle.generate({
  format: 'es',
  strictExecutionOrder: true,
  minify: false,
});

for (const chunk of output) {
  if (chunk.type === 'chunk' && chunk.code.includes('first(second)')) {
    console.log(chunk.code);
  }
}

Related PR: #10009

Metadata

Metadata

Type

No type

Fields

Priority

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions