layer5io / layer5io/sistent

ESM build is not loadable by Node: extensionless 'lodash/debounce' specifier in dist/index.mjs

Open Beginner friendly
#1,739 1 comment 0 reactions 0 assignees View on GitHub

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 naming lodash, 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:

  1. import { debounce } from 'lodash'; - resolves under both module systems. Costs bundle size unless tree-shaking is reliable here.
  2. import debounce from 'lodash/debounce.js'; - smallest change, keeps the subpath, valid for both Node ESM and bundlers.
  3. Move lodash from external to bundled for the ESM output in tsup.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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.