ESM build is not loadable by Node: extensionless 'lodash/debounce' specifier in dist/index.mjs
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 137
- Forks
- 239
- Avg merge
- 4d 14h
- Merged PRs (30d)
- 5
Description
Summary
dist/index.mjs emits an extensionless subpath specifier for lodash, which Node's ESM resolver rejects. The ESM build is therefore not loadable by Node directly.
Reproduce
npm run build
node --input-type=module -e "import('./dist/index.mjs')"
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '<repo>/node_modules/lodash/debounce'
imported from <repo>/dist/index.mjs
Did you mean to import "lodash/debounce.js"?
Cause
src/custom/SearchBar.tsx:3:
import debounce from 'lodash/debounce';
lodash ships no exports map in its package.json, so under Node ESM a subpath import must carry the file extension (lodash/debounce.js). tsup keeps the specifier verbatim in the ESM output because lodash is external, so the unresolvable form reaches the published artifact.
Contrast @mui/icons-material/X, which is also extensionless in the output but resolves fine - that package does declare an exports map. So this is specific to lodash, not a general problem with the build config.
Impact
Low today, but real:
- Bundler consumers are unaffected - webpack, vite, rollup and Next.js all apply Node-CJS-style resolution to bare specifiers, so Meshery UI and Kanvas do not see this.
- Node ESM consumers are affected - anything doing
import '@sistent/sistent'from a real ESM context (SSR, a script, a test runner in ESM mode, RSC server bundles) fails at load with a message naminglodash, not sistent.
It is the same shape of trap as #1735: the failure surfaces at import time, names a third-party package, and is invisible to CI because the repo always resolves through a bundler or jest's CJS transform.
Suggested fix
Either:
import { debounce } from 'lodash';- resolves under both module systems. Costs bundle size unless tree-shaking is reliable here.import debounce from 'lodash/debounce.js';- smallest change, keeps the subpath, valid for both Node ESM and bundlers.- Move
lodashfrom external to bundled for the ESM output intsup.config.ts.
(2) is the least invasive. Whichever is chosen, it is worth extending the guard added in #1737 (src/__testing__/optionalPeerDependencies.test.ts) or adding a sibling check that actually imports the built dist/index.mjs under Node ESM, so the build output is verified rather than only the source import graph. That would catch this class directly instead of by inspection.
Notes
Pre-existing on master, unrelated to the optional-peer work in #1732/#1737. Found while content-verifying the v0.21.40 tarball; filed separately rather than folded into an unrelated patch release.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with src/custom/SearchBar.tsx and reproduce the issue with npm run build followed by the Node ESM import command. Review the existing guard in src/testing/optionalPeerDependencies.test.ts and tsup.config.ts, then verify that the built dist/index.mjs loads successfully under Node ESM without the lodash resolution error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- build-system, testing
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100