esm exports: `./compat/*` subpath exports resolve to CJS for ESM importers (no `import` condition)
Disclaimer: Report generated by AI :^)
## Problem
The exports map entry for compat subpaths offers only the root CJS stubs, for **all** resolution conditions:
```jsonc
// package.json (es-toolkit 1.47.0)
"./compat/*": {
"default": {
"types": "./compat/*.d.ts",
"default": "./compat/*.js" // CJS: module.exports = require('../dist/compat/...').x
}
}
```
So `import get from "es-toolkit/compat/get"` always resolves to a CommonJS module, even in fully-ESM graphs. Compare with `./compat` (the index), which correctly splits `import` → `dist/compat/index.mjs` / `require` → `dist/compat/index.js`.
This matters in practice because **recharts 3 deep-imports these subpaths** (`es-toolkit/compat/get`, `maxBy`, `minBy`, `omit`, `range`, `sortBy`, `sumBy`, `throttle`, `uniqBy`, `last`, `isPlainObject`), so every recharts 3 app bundles CJS es-toolkit with interop wrappers regardless of bundler.
## Why we noticed: it triggers a rolldown chunk-rendering bug (runtime crashes)
rolldown (vite 8's bundler) has a chunk-rendering bug where CJS `require_*` helpers shared across code-split chunks can be emitted as self-referencing declarations:
```js
const require_identity = require_identity(); // ReferenceError / minified: TypeError: t is not a function
```
In our app every lazy-loaded route containing a recharts 3 chart crashed in production builds. The *only* CJS modules involved were these es-toolkit compat stubs — with an ESM resolution for `./compat/*` the entire class of problem disappears (we now work around it with a resolve plugin that rewrites `es-toolkit/compat/<fn>` to `export { <fn> as default } from "es-toolkit/compat"`).
That bundler bug is rolldown's to fix (reported separately: https://github.com/rolldown/rolldown/issues/9653), but es-toolkit shipping CJS to `import` consumers is what exposes it — and independently costs interop wrappers and worse tree-shaking for everyone on ESM.
A second contributing detail: the dist CJS files declare their require locals with `require_*` names (e.g. `const require_identity = require("../../function/identity.js")` in `dist/compat/math/maxBy.js`), which is the exact naming scheme rolldown uses for its generated require-helpers — that collision is what the rolldown bug fails to deconflict across chunks. Shipping ESM for these subpaths sidesteps it entirely.
## Suggestion
Provide ESM for the `./compat/*` wildcard, e.g. either:
1. an `import` condition pointing at per-function `.mjs` stubs (`export { get } from "../dist/compat/index.mjs"` — or deep ESM files mirroring the existing CJS stubs), or
2. fold compat into the per-function granular exports work in #1668 with proper `import`/`require` conditions.
## Repro of the resolution (no bundler needed)
```js
// node >= 20, run in an ESM context (type: module)
console.log(import.meta.resolve("es-toolkit/compat/get"));
// → .../node_modules/es-toolkit/compat/get.js (CJS), no ESM alternative offered
```
es-toolkit 1.47.0, node 22.
关闭于 2026-06-07 1 条评论