Skip to content

ESM import resolution fails in Node.js — extensionless matrix-js-sdk subpath imports #3

Description

@glifocat

Bug

Importing @beeper/chat-adapter-matrix in a Node.js ESM project crashes at startup:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module
  '/path/node_modules/matrix-js-sdk/lib/http-api/errors'
  imported from /path/node_modules/@beeper/chat-adapter-matrix/dist/index.js
Did you mean to import "matrix-js-sdk/lib/http-api/errors.js"?

Environment

  • Node.js 22.16.0
  • @beeper/chat-adapter-matrix 0.2.0
  • matrix-js-sdk 41.3.0 (type: "module")
  • No bundler — direct Node.js ESM execution

Root cause

tsup.config.ts marks matrix-js-sdk as external, so tsup copies the import specifiers verbatim from the TypeScript source into dist/index.js. TypeScript resolves these without .js extensions via its own module algorithm, but Node.js ESM requires explicit .js for subpath imports into other ESM packages that don't have a matching exports map entry.

Five imports in dist/index.js are affected:

import { MatrixError } from "matrix-js-sdk/lib/http-api/errors";               // needs errors.js
import { decodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key";  // needs recovery-key.js
import { logger as matrixSDKLogger } from "matrix-js-sdk/lib/logger";           // needs logger.js
import { SyncAccumulator } from "matrix-js-sdk/lib/sync-accumulator";           // needs sync-accumulator.js
import { MemoryStore } from "matrix-js-sdk/lib/store/memory";                   // needs memory.js

matrix-js-sdk has been type: "module" since at least v39.0.0 and does not define exports entries for these deep paths, so Node falls back to direct file resolution which requires the .js extension.

Suggested fix

A few options:

  1. Source-level: write imports with .js in the TypeScript source and set moduleResolution: "node16" or "nodenext" in tsconfig.json — TypeScript will resolve them correctly and they'll pass through to the output as-is.

  2. Build-level: remove matrix-js-sdk from external in tsup.config.ts so tsup bundles it into the output — avoids the subpath import entirely.

  3. Post-build: add an esbuild plugin or postbuild script to append .js to extensionless external imports targeting matrix-js-sdk/lib/.

Option 1 is the cleanest long-term.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions