Skip to content

alfeg/rolldown-init-bug-repro

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rolldown dep-optimizer: missing init_shared_esm_bundler import in @vue/test-utils chunk

⚠️ This repository (code, configuration, and write-up) was generated by Claude AI (Anthropic) while investigating the bug in a downstream project. Review before using as the basis of an upstream issue.

Versions

  • vite 8.0.14 (ships with rolldown@1.0.2)
  • vitest 4.1.7, @vitest/browser 4.1.7, @vitest/browser-playwright 4.1.7
  • @vue/test-utils 2.4.10
  • vue 3.5.34
  • node >= 22

Reproduce

npm install
npm test          # runs `playwright install --with-deps chromium && vitest run`

Expected: FAIL with

Error: Failed to import test file …/repro.spec.ts
Caused by: ReferenceError: init_shared_esm_bundler is not defined
 ❯ node_modules/.vite/vitest/<hash>/deps/@vue_test-utils.js:5:1

Inspect the cache directly:

node check.mjs

prints

  OK     @vue_server-renderer.js                (calls=1, imports=1)
  ❌ BUG  @vue_test-utils.js                     (calls=1, imports=0)
  OK     compiler-dom.esm-bundler-XXXXXXXX.js   (calls=2, imports=1)
  OK     vue.runtime.esm-bundler-XXXXXXXX.js    (calls=3, imports=1)
FAIL: A chunk calls init_shared_esm_bundler() without importing it.

What's happening

Rolldown's dep optimizer code-splits the pre-bundle into per-package chunks. To preserve ESM side-effect ordering across chunks it wraps each module in __esmMin(() => { … }) and emits init_<module>_<format>() calls at every use-site that depends on those side effects. Every consumer chunk must therefore both call the init function and import it from the chunk that defines it.

For @vue/test-utils, Rolldown emits the call but omits the import binding:

// node_modules/.vite/vitest/<hash>/deps/@vue_test-utils.js (first lines)
import { , n as vue_runtime_esm_bundler_exports,  } from "./vue.runtime.esm-bundler-XXXX.js";
import { h as compile } from "./compiler-dom.esm-bundler-XXXX.js";
import { renderToString as renderToString$1 } from "./@vue_server-renderer.js";
//#region node_modules/@vue/test-utils/dist/vue-test-utils.esm-bundler.mjs
init_shared_esm_bundler();   // ← ReferenceError at module load

init_shared_esm_bundler is defined in (and exported as v by) shared.esm-bundler-XXXX.js. The sibling chunks @vue_server-renderer.js, vue.runtime.esm-bundler-XXXX.js, and compiler-dom.esm-bundler-XXXX.js all import it correctly. Only @vue_test-utils.js is missing the import.

Why this configuration triggers it

Two things have to be true for the broken shape to appear:

  1. @vue/compiler-dom ends up split into its own sub-chunk. That happens because Vitest's optimizer pass sets resolve conditions of roughly ["node", "import", "default", DEV_PROD] and mainFields: [] (see vitest/dist/chunks/cli-api.*.jsgetDefaultResolveOptions / getDefaultServerConditions). With those, @vue/compiler-core (used transitively by @vue/compiler-dom) resolves through its node condition to a CJS entry. Rolldown then splits the compiler-dom module body into a CJS-interop sub-chunk (compiler-dom.esm-bundler-<hash>.js) that uses __commonJSMin / require_compiler_core_cjs(). The top-level @vue_compiler-dom.js becomes a thin re-export facade in front of it.
  2. @vue/test-utils consumes that sub-chunk. Its source has a namespace import:
    import * as Vue from "vue"                          // ← namespace import (the trigger)
    import { nextTick, Fragment,  } from "vue"
    import { compile } from "@vue/compiler-dom"
    import { renderToString } from "@vue/server-renderer"
    The other top-level chunks reaching @vue/compiler-dom only use named imports — they get their init_shared_esm_bundler import wired up correctly. Combine the CJS-split path with the namespace import on the consumer side and the cross-chunk binding pass drops the init_shared_esm_bundler import for that one chunk.

Not the cause

  • It is not triggered by build.rolldownOptions.output.manualChunks. That option only affects the production build pipeline (vite build). The dep optimizer is a separate Rolldown instance configured through optimizeDeps.rolldownOptions. This repro contains no manualChunks at all.
  • It is not @vue/test-utils' fault. Its source is valid ESM and every other bundler handles the namespace import correctly.
  • It is not Vite's responsibility — Vite simply configures Rolldown with reasonable defaults; the chunk-emission bug is in Rolldown.

Downstream impact

This breaks all Vitest browser-mode test suites under Vite 8 that use @vue/test-utils, because @vitest/browser automatically adds @vue/test-utils to optimizeDeps.include.

Workaround (in the consumer's vite.config.ts)

Patch the optimizer output in a generateBundle plugin: find the shared.esm-bundler-*.js chunk name and prepend the missing import to any chunk that calls init_shared_esm_bundler() without importing it.

About

Repro for issue

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors