Why the import suddenly breaks
Rollup’s CommonJS interop (Vite 7 and earlier) was permissive and could vary depending on exactly how a module was loaded. Vite 8 replaces it with Rolldown’s stricter, standardized rule, stated verbatim in Vite’s own migration guide: “Default import handling from CommonJS modules now operates consistently. The default import represents module.exports when: the importer is .mjs or .mts, the closest package.json specifies type: "module", the importee’s module.exports.__esModule is not true.”
Older CommonJS packages commonly export a single function directly, without setting module.exports.__esModule:
module.exports = function doThing() {
/* ... */
};Under Rollup’s old interop, a default import from a package like this could resolve to the function itself in some cases. Under Rolldown’s rule above, the same import now consistently resolves to module.exports as a whole, which, since it is the function here, should still work, but plenty of real packages wrap their export differently enough that the resolved value stops being callable. The failure only shows up at the call site, once code tries to invoke something that’s no longer a function:
import doThing from "some-legacy-lib";
doThing(); // TypeError: doThing is not a functionNothing about your code or the dependency changed. Only the bundler’s interop rule did.
Fix it: restore the old interop temporarily
Before: default Vite 8 config
export default defineConfig({
// no legacy override — Rolldown's strict CJS interop applies (BROKEN
// for packages relying on the old permissive resolution)
});After: opt back into the old behavior
export default defineConfig({
legacy: {
inconsistentCjsInterop: true, // FIXED: restores Vite 7's permissive interop
},
});This only fixes one specific failure mode
legacy.inconsistentCjsInterop restores Rollup’s permissive default-export
resolution. It does nothing for other CJS/ESM interop differences, like
execution-order changes or dynamic import() regressions. If your crash
doesn’t match the exact “default import resolves to the wrong value” shape,
this flag won’t fix it.
Confirmed version range
Documented in Vite’s own current migration guide as an intentional Vite 8 behavior change, not a bug. legacy.inconsistentCjsInterop is the officially documented escape hatch, not a community workaround. This site’s own dependencies are all modern ESM-first packages, so this specific interop change wasn’t independently reproducible against this repo’s own build; treat the fix above as effective for any Vite 8 project hitting the exact TypeError: <x> is not a function shape on a previously-working default import from a CommonJS dependency. See also Fix Vite 8 Externalized require() Behavior Change, a related but distinct change to the same CJS/ESM boundary, from the require side rather than the import side. Browse more posts like this in the Guides & Fixes archive.







