Repository navigation
es build is not loadable by Node's ESM resolver #1002
Description
es build is not loadable by Node's ESM resolver ‚every entry point throws ERR_MODULE_NOT_FOUND
Summary
In @rc-component/picker@1.12.0 the exports map serves the es build to the import condition for every
subpath, but the es build is bundler-only output:
- its relative specifiers are extensionless (
import { commonLocale } from "./common";), and - there is no
"type": "module"marker ‚neither at the package root nor as a nestedes/package.json.
Consequence: any consumer that reaches the package through the import condition ‚ i.e. any "type": "module"
package, or anything running under plain Node ESM ‚I cannot load it at all. require() (the lib build) works fine;
bundlers work fine; Node ESM is completely broken.
Reproduction
mkdir rc-repro && cd rc-repro
npm init -y
npm pkg set type=module
npm i @rc-component/picker@1.12.0 react@19 react-dom@19 moment
node --input-type=module --eval 'import "@rc-component/picker"'
node --input-type=module --eval 'import "@rc-component/picker/locale/en_US"'Both fail (Node v24.12.0, npm-installed, unpatched):
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../@rc-component/picker/es/PickerInput/RangePicker'
imported from .../@rc-component/picker/es/index.js
code: 'ERR_MODULE_NOT_FOUND'
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../@rc-component/picker/es/locale/common'
imported from .../@rc-component/picker/es/locale/en_US.js
code: 'ERR_MODULE_NOT_FOUND'
The CJS path is healthy, which shows the problem is isolated to the es build:
node --input-type=commonjs --eval 'console.log(require("@rc-component/picker/locale/en_US").default.locale)'
# -> en_USBoth missing files exist on disk as es/PickerInput/RangePicker.js and es/locale/common.js ‚only the file
extension is missing from the specifier. Counted in 1.12.0:
- 391 extensionless relative specifiers across
es/**/*.js - 158 extensionless relative type specifiers across
es/**/*.d.ts(from '../interface',from '.', …),
whichmoduleResolution: "node16"/"nodenext"/"bundler"-style ESM type resolution also cannot follow
Note on Node versions: on Node ‚â• 22.7 the ESM syntax in es/*.js is auto-detected, so the files are parsed as ESM
and fail on the extensionless specifier as above. On older Node (no module-syntax detection) the same files are
parsed as CJS, because the nearest package.json has no "type" field, and fail with
SyntaxError: Cannot use import statement outside a module. Broken either way, just with a different message.
The breakage also looks inconsistent, which makes it hard to diagnose: @rc-component/picker/generate/moment
does load under Node ESM, because es/generate/moment.js happens to import only bare specifiers
(moment, @rc-component/util) and no relative paths.
Second, related problem: the default export needs runtime unwrapping
Because the es build uses export default while the package is not marked as ESM, the shape a consumer receives
depends on which build the resolver picked, so no single import form is portable:
// lib (CJS) build via ESM import ‚module.exports is the namespace
import m from ".../lib/locale/en_US.js"; // m = { __esModule: true, default: locale } -> m.locale is undefined
import * as ns from ".../lib/locale/en_US.js"; // Object.keys(ns) = ['__esModule','default','module.exports']
// the locale object is at ns.default.default
// es build inlined by a bundler ‚the locale object is at ns.defaultTypeScript agrees with the pessimistic reading: since the package has no "type": "module", es/locale/en_US.d.ts
is treated as CJS under nodenext, so export default locale types as a default property rather than a module
default. Consumers end up writing this at every rc-picker import site:
import * as momentGenerateConfigModule from "@rc-component/picker/generate/moment";
import { defaultImport } from "default-import";
const momentGenerateConfig = defaultImport(
momentGenerateConfigModule as unknown as { default: MomentGenerateConfig },
);…i.e. a cast plus a runtime interop helper (default-import) purely to survive the two shapes the same module can
arrive in (real ESM namespace when a bundler inlines it, CJS interop when a test runner or Node externalizes it).
What we tried, and why it doesn't work
Patching the exports map locally:
"./locale/*": {
"types": "./es/locale/*.d.ts",
"module": "./es/locale/*.js",
"import": "./lib/locale/*.js",
"require": "./lib/locale/*.js"
}"module"is inert for this purpose: Node's resolver only understandsnode,import,require,default
(andnode-addons) ‚"module"is a bundler-only convention, so it changes nothing for Node.- Pointing
importatlib/does make Node load the module, but it swaps the ESM namespace for CJS interop, which
is exactly the double-wrappeddefaultdescribed above ‚so consumer code that worked under a bundler now reads
the wrong shape. - And it would have to be applied to every subpath (
.,./generate/*,./interface, …), not just./locale/*.
Impact
A "type": "module" library that statically imports this package becomes un-importable under plain Node ‚the
failure is not local to the picker component, it propagates to the whole barrel/entry point that transitively
reaches it. That breaks SSR, node -e 'import "<pkg>"' package smoke tests, and test runners that externalize
node_modules instead of bundling them.
Our current workaround is to keep every rc-picker import behind a dynamic import() (React lazy) so it never
appears in the Node-reachable static graph, plus the defaultImport casts above.
Suggested fix (any one of these solves the primary issue)
- Emit extensions in the
esbuild ‚from "./common.js"‚and addes/package.jsoncontaining
{ "type": "module" }(or emit.mjs). Babel:babel-plugin-add-import-extension; tsc:
rewriteRelativeImportExtensions/ write specifiers with.js. Apply the same to the relative type specifiers
ines/**/*.d.ts. - Or point the
importcondition at a genuinely Node-resolvable ESM build and keepesbehind the bundler-only
"module"condition. - Also worth adding
"type": "commonjs"explicitly at the root solib/is unambiguous and Node's syntax
detection never has to guess.
Fixing (1) additionally removes the need for default-import-style unwrapping, because a real ESM module's default
export resolves identically in Node and in bundlers.
Environment
@rc-component/picker1.12.0 (unmodified, freshnpm i)- Node v24.12.0, npm 11.x, macOS (darwin 24.6.0)
- Consumer package:
"type": "module", TypeScriptmoduleResolution: nodenext, React 19, moment 2.30.1
Activity
ivanmjartan commentedon Aug 24, 2026
@zombieJ Hi there pls can you have look! This issues is blocking use this great lib in pure ESM Node envs like server rendering, also vite testing env etc.
thanks very much.
ivanmjartan commentedon Aug 27, 2026
Mybe there is easy fix, and it is define exports for node
zombieJ commentedon Sep 1, 2026
The shared build fix is now proposed in react-component/father-plugin#27.
It only activates when
exports.importpoints to the Father ESM output, so packages using the existingmain/modulesetup keep their current output unchanged. I also validated the rebuilt picker with native Node ESM, CommonJS, TypeScript NodeNext, and an antd DatePicker dev build.@PeachScript Could you please take a look from the Father side?
ivanmjartan commentedon Sep 2, 2026
Thanks very much guys it looks great pls can you merge the @zombieJ PR all is green and release new version ?
fireairforce commentedon Sep 2, 2026
@ivanmjartan we will support this case under father not father-plugin, the change will be later, i will pr this.