esmock: `invalid moduleId` in a TypeScript monorepo

I have a monorepo with two packages rdf-web-access-control and hydra-box-web-access-control. The latter imports the former

import { check } from 'rdf-web-access-control'

In tests I want to mock this import

const aclSpy = await esmock('../index.ts', {
  'rdf-web-access-control': {
    check: sinon.stub(),
  },
})

This fails:

Error: invalid moduleId: "rdf-web-access-control" (used by file:///<root>/packages/hydra-box-web-access-control/test/index.test.ts)
    at Object.Ee [as errModuleIdNotFound] (file:///<root>/node_modules/esmock/src/esmock.js:1:61)
    at K (file:///<root>/node_modules/esmock/src/esmock.js:2:5883)
    at wt (file:///<root>/node_modules/esmock/src/esmock.js:2:6379)
    at file:///<root>/node_modules/esmock/src/esmock.js:3:161
    at async Context.<anonymous> (file:///<root>/packages/hydra-box-web-access-control/test/index.test.ts:34:14)

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Comments: 35 (21 by maintainers)

Most upvoted comments

@tpluscode @koshic esmock should support workspaces. I’ll make a PR to add workspaces support to resolvewithplus soonish. Support could be added without using module.createRequire

If you want to publish JS code from a TypeScript project, or compile to run without TS loader in runtime, you will need to use .js

No ) ‘.js’ is required only if you want to use tsc as compiler. For all other tools like esbuild, swc, babel or even tsc as type checker you can use ‘.ts’.

You cannot change main field in package to JS because it will stop working once a package in compiled and published to NPM.

You can ) To solve issues like that, when published package has a different entry point (like ‘dist/index.js’ instead of ‘src/index.js’) you can use ‘publishConfig’ field, supported by all package managers - https://docs.npmjs.com/cli/v8/configuring-npm/package-json#publishconfig.

Small example (real code from one of my projects):

  "imports": {
    "#utils/*": "./src/utils/*.ts",
    "#load": "./src/load/hook.ts",
    "#resolve": "./src/resolve/hook.ts",
    "#src/*": "./src/*.ts"
  },
  "exports": "./dev/loader.js",
  "files": [
    "dist"
  ],
  "publishConfig": {
    "imports": {
      "#utils/*": "./dist/utils/*.js",
      "#load": "./dist/load/hook.js",
      "#resolve": "./dist/resolve/hook.js"
    },
    "exports": "./dist/loader.js"
  },

That purpose being that others can use my library

I mean some libraries may be ‘internal’ part of monorepo which is not published directly.

Conditional exports are not intended to be used with TS. They exist to cater for dual CJS+ESM packages

It’s not quite true. According to https://nodejs.org/dist/latest-v20.x/docs/api/packages.html#conditional-exports, “Conditional exports provide a way to map to different paths depending on certain conditions”. Moreover, TS support types export trough special condition ‘types’, https://www.typescriptlang.org/docs/handbook/esm-node.html#packagejson-exports-imports-and-self-referencing.

What I want to say - those conditions can be used (and already used!) for various tasks when package should provide different exports depending on some conditions. You can treat them as a replacement for various non-standard fields like ‘browser’, 'module, ‘types’ which force developers to reimplement package.json parsing again and again while Node resolver supports single ‘main’ field.

This approach is mentioned in the wiki https://github.com/iambumblehead/esmock/wiki#call-esmock-npm-workspaces thank you. If there are no objections over the next few days, this issue will be closed

No worries. Thankfully, there is a workaround 🙏

@tpluscode thanks. Some radical changes in node 20.6 are using my time https://github.com/iambumblehead/esmock/issues/234 there might be some delay before we can resolve this typescript monorepo issue you’ve opened.