Skip to content

[Bug]: CJS subpath import in lazy chunk throws "X is not a function" — chunk-split interop mismatch in rolldown 1.0.0 / 1.0.1 #9441

@thedoublejay

Description

@thedoublejay

Reproduction link or steps

Repo (starting point, see "partial repro" note below):
https://github.com/thedoublejay/rolldown-chunk-split-cjs-repro

Open in Stackblitz:
https://stackblitz.com/github/thedoublejay/rolldown-chunk-split-cjs-repro

git clone https://github.com/thedoublejay/rolldown-chunk-split-cjs-repro
cd rolldown-chunk-split-cjs-repro
npm install
npm run build       # vite 8.0.13, rolldown 1.0.1
npm run preview     # browse http://localhost:4173

The repo sets up the same conditions where a real-world Vite 8.0.13 build broke:

  • 3 × React.lazy(() => import(...)) boundaries, all importing the same lodash/<sub> CJS submodules (forces rolldown to extract them to a shared vendor chunk)
  • ESM libs (@mui/material, @tanstack/react-query, clsx, @emotion/react) mixed with the CJS modules in the shared chunk, matching the real failing chunk's shape
  • tss-react/mui withStyles in a lazy component (matches the legacy component that surfaced the bug)
  • vite.config.js mirrors the failing config's distinctive flags: react({ jsxImportSource: "@emotion/react" }) + rolldown.moduleTypes: { ".js": "jsx" }

Important — partial repro

This standalone repo does not yet trigger the broken emission on its own — rolldown emits a consistent interop shape here. The same Vite/rolldown versions + the same lodash subpaths + the same lazy boundary structure DO reproduce in a larger codebase (~5000 modules / ~1200-source vendor chunks). The trigger appears to depend on graph shape we couldn't isolate down to a small case.

I'm filing this rather than waiting because the production-side evidence below is concrete (minified output + bisect-narrowed regression range), and a maintainer hint at the chunking heuristic that changed between rolldown 1.0.0-rc.18 and 1.0.0 / 1.0.1 would let me push the repo over the edge. Happy to invest more time on shrinking the case.

What is expected?

When rolldown splits a CJS module into a separate output chunk, the import-site code and the export-side emission should agree on the interop shape:

  • Either both sides treat the export as a __commonJSMin(...) lazy-wrapper function, or
  • both sides treat the export as a static namespace value.

This is what rolldown 1.0.0-rc.18 (shipped in Vite 8.0.11) does, and what the same source produces correctly there.

What is actually happening?

In rolldown 1.0.0 / 1.0.1, when a CJS module ends up in a separate output chunk (not the main bundle), the importer and exporter disagree:

  • The import-site emits a(c()) — i.e. calls c as a __commonJSMin() lazy-wrapper, then passes the result to _interopRequireDefault.
  • The export-side emits the resolved module namespace directly as the export, not wrapped in a callable getter.

Result: TypeError: <minified> is not a function thrown synchronously at top-level of the lazy chunk (before any of its export bindings evaluate), which surfaces as a React.lazy rejection caught by the nearest ErrorBoundary.

Real-world emitted code (anonymized)

// Importing chunk (lazy)
import { Vt as t, fi as n, p as r, q as i, vi as a } from "./main.js";
import { n as s } from "./shared-A.chunk.js";
import { T as c } from "./shared-B.chunk.js";   // ← shared CJS+ESM vendor chunk

var l = a(n()),     // ← works: n() returns the __commonJSMin wrapper from main
    u = a(i()),     // ← works: i() returns the __commonJSMin wrapper from main
    d = a(c())      // ← throws: c is not callable, exporter emitted a value

Browser console

TypeError: c is not a function
    at app.<hash>.chunk.js:1:272
Caused by: React ErrorBoundary TypeError: c is not a function
    at Lazy (<anonymous>)
    at Suspense (<anonymous>)

Reproducible with both [email protected] and [email protected], both @vitejs/[email protected] and @vitejs/[email protected], so neither of those is the variable.

System Info

System:
  OS: macOS 26.5
  CPU: arm64 Apple Silicon
Binaries:
  Node: 24.14.1
  npm: 11.11.0
  Yarn: 4.13.0
npmPackages (broken configuration):
  vite: 8.0.13
  rolldown: 1.0.1 (transitive via vite)
  @vitejs/plugin-react: 6.0.2
  react / react-dom: 19.2.x

Any additional comments?

Bisect: which Vite/rolldown versions are affected?

Vite version Bundled rolldown Status
8.0.1 1.0.0-rc.10 ✅ works
8.0.11 1.0.0-rc.18 (Apr 29, 2026 — last RC) ✅ works
8.0.12 1.0.0 (May 7, 2026 — first stable) ⚠️ likely affected (same rolldown family) — not directly retested
8.0.13 1.0.1 (May 13, 2026) ❌ reproduces

Bisect was carried out against the real-world failing app: same source, same install, only the Vite/rolldown version changed.

Workaround in use

Pin Vite to exact 8.0.11.

Related

  • #9407 — distinct bug, same rolldown 1.0.1 family: require("react") left as runtime __require even when react is inlined elsewhere in the same build. Different surface (SSR + dangling __require) but same theme of CJS-interop emission inconsistency across the bundle graph.
  • #7449 — incorrect execution order with CJS + advancedChunks (different mechanism, but also CJS chunking-related).

Triage hint

If a maintainer can hint at what specifically changed in chunk emission between rolldown 1.0.0-rc.18 and 1.0.0 / 1.0.1 for shared CJS modules — particularly when a vendor chunk mixes ESM and __commonJSMin-wrapped CJS modules — I should be able to take the linked repo over the edge into a clean standalone repro.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions