Repository navigation
experimental.bundledDev: dynamic import of a CommonJS dependency loses its default export #23558
Description
Describe the bug
With experimental.bundledDev enabled, await import() of a CommonJS dependency returns a namespace object with no default export. The namespace carries the static properties hanging off module.exports, but the exported value itself is gone, so there is no way to reach it.
With jszip (whose module.exports is the JSZip constructor):
const mod = await import('jszip');
Object.keys(mod); // ['support', 'defaults', 'version', 'loadAsync', 'external']
typeof mod.default; // 'undefined'
new mod.default(); // TypeError: mod.default is not a constructorA static import JSZip from 'jszip' in the same file works, so it is specific to the dynamic-import path.
I narrowed it to Rolldown's lazy dynamic-import compilation: setting build.rolldownOptions.experimental.devMode.lazy = false makes default come back.
Reproduction
Four files, no framework, 28 npm packages.
package.json
{
"name": "vite-bundleddev-cjs-default",
"private": true,
"type": "module",
"scripts": { "dev": "vite", "build": "vite build" },
"dependencies": { "jszip": "3.10.1" },
"devDependencies": { "vite": "8.3.0" },
"overrides": { "rolldown": "1.2.6" }
}vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
experimental: { bundledDev: true },
});index.html
<!doctype html>
<html><body><pre id="out">running…</pre><script type="module" src="/main.js"></script></body></html>main.js
import StaticJSZip from 'jszip';
const results = {};
const tryNew = (Ctor) => {
try {
new Ctor();
return 'ok';
} catch (error) {
return String(error);
}
};
results.staticImport = tryNew(StaticJSZip);
const mod = await import('jszip');
results.dynamicNamespaceKeys = Object.keys(mod);
results.dynamicDefaultType = typeof mod.default;
results.dynamicImport = tryNew(mod.default);
document.getElementById('out').textContent = JSON.stringify(results, null, 2);npm install && npm run dev, then open the page.
Steps to reproduce
npm installnpm run dev- Open the dev server in a browser and read the
<pre>.
Expected behavior
mod.default is the JSZip constructor, matching what the production build, the unbundled dev server, and the static import all give.
Actual behavior
{
"staticImport": "ok",
"dynamicNamespaceKeys": ["support", "defaults", "version", "loadAsync", "external"],
"dynamicDefaultType": "undefined",
"dynamicImport": "TypeError: Ctor is not a constructor"
}What I checked
Same repro, four configurations:
| Configuration | await import('jszip') → default |
|---|---|
experimental.bundledDev: true |
❌ missing |
experimental.bundledDev: true + build.rolldownOptions.experimental.devMode.lazy: false |
✅ present |
unbundled dev server (no bundledDev) |
✅ present |
vite build + static file server |
✅ present |
So only the lazy dynamic-import path under bundledDev is affected, and turning that path off is a working workaround.
Notably, await import() of a JSON file is fine in this repro, so this is not "all dynamic imports under bundledDev" — it is specifically the CommonJS interop on that path.
System Info
System:
OS: macOS 26.6.2
CPU: (10) arm64 Apple M1 Pro
Memory: 16.00 GB
Binaries:
Node: 22.22.3
npm: 10.9.8
Browsers:
Chrome: 153.0.8010.54
Safari: 26.6.2
npmPackages:
vite: 8.3.0 => 8.3.0
rolldown: 1.2.6
jszip: 3.10.1 => 3.10.1
rolldown is pinned to 1.2.6 because 1.2.9 hits #23547 with Vite 8.3.0.
Used Package Manager
npm
Activity
test-cases: refine optimize-deps triage rules; log #23877 and rolldow…