ITADN

esm exports: `./compat/*` subpath exports resolve to CJS for ESM importers (no `import` condition)

#1756ClosedAmund211 创建于 2026-06-05
A
Amund211commented
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 条评论