⚠️ 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.
- vite
8.0.14(ships withrolldown@1.0.2) - vitest
4.1.7,@vitest/browser4.1.7,@vitest/browser-playwright4.1.7 @vue/test-utils2.4.10vue3.5.34- node
>= 22
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.mjsprints
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.
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 loadinit_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.
Two things have to be true for the broken shape to appear:
@vue/compiler-domends up split into its own sub-chunk. That happens because Vitest's optimizer pass sets resolve conditions of roughly["node", "import", "default", DEV_PROD]andmainFields: [](seevitest/dist/chunks/cli-api.*.js→getDefaultResolveOptions/getDefaultServerConditions). With those,@vue/compiler-core(used transitively by@vue/compiler-dom) resolves through itsnodecondition 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.jsbecomes a thin re-export facade in front of it.@vue/test-utilsconsumes that sub-chunk. Its source has a namespace import:The other top-level chunks reachingimport * 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"
@vue/compiler-domonly use named imports — they get theirinit_shared_esm_bundlerimport wired up correctly. Combine the CJS-split path with the namespace import on the consumer side and the cross-chunk binding pass drops theinit_shared_esm_bundlerimport for that one chunk.
- 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 throughoptimizeDeps.rolldownOptions. This repro contains nomanualChunksat 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.
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.
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.