Skip to content

Automatic code splitting: circular CJS chunk → uninitialized binding ('n is not a function') (es-toolkit via Recharts, Vite 8) #9630

@sfabriece

Description

@sfabriece

Summary

In a production build only (dev + tests are fine), a route chunk that renders a CommonJS dependency throws TypeError: n is not a function at runtime. The error looks like a minified variable collision, but the real cause is a circular chunk import where a CJS module's binding is read before the chunk that defines it has executed.

This is the same failure class as #8803 — but that issue states automatic splitting "produces chunks that don't form cycles." Here it does form a cycle under plain Vite automatic/route-based splitting, with no codeSplitting.groups configured. So this case appears unreported.

Environment

  • vite@8.0.14 (bundler: rolldown@1.0.3)
  • recharts@3.8.1es-toolkit@1.47.0
  • No output.codeSplitting.groups — default Vite automatic splitting.

Evidence

A sourcemapped build maps the throw to es-toolkit/dist/compat/math/maxBy.js:2, a top-level const require_identity = require("../../function/identity.js").

The emitted (minified) code:

un = W((e => {
  var t = ln(), n = n(), r = r();          // <-- n=n() and r=r() are self-referential
  function i(e, i){ /* ... */ r.iteratee(i ?? n.identity) }
  e.maxBy = i
}))

t = ln() is fine (the module-init function and its result binding got distinct names), but n = n() / r = r() read a binding that is still uninitialized at that point — the cross-chunk factory and the binding it initializes collapsed to the same name. With minify: false it surfaces as a deconfliction failure instead, per #8803.

Chain: Recharts does import maxBy from 'es-toolkit/compat/maxBy'. es-toolkit's ./compat/* export is CJS-only (module.exports = require('../dist/compat/math/maxBy.js').maxBy), so rolldown bundles + wraps the nested-require chain and splits the helper modules (identity, iteratee) into a different chunk than the consumer — forming the cycle.

Workarounds (both confirm the cause)

Question

Should automatic (non-grouped) splitting avoid forming these CJS cycles so neither workaround is needed — or, if strictExecutionOrder is the intended answer, should the docs note it's required for automatic splitting with CJS deps too (not only codeSplitting.groups, as #8803 implies)?

I can put together a minimal Vite 8 + Recharts reproduction if that would help.

Metadata

Metadata

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