testdouble / quibble

Makes it easy to replace require'd dependencies.
94 stars 25 forks source link

import throws ENOENT instead of ERR_MODULE_NOT_FOUND when a module is not found #100

Open sgravrock opened 1 year ago

sgravrock commented 1 year ago

Normally, Node throws an error with code ERR_MODULE_NOT_FOUND when the requested module is not found. But when the testdouble ESM loader is used, an error with code ENOENT is thrown instead:

$ cat package.json 
{
  "devDependencies": {
    "testdouble": "^3.18.0"
  }
}
$ cat test.mjs 
import './nosuch.mjs';
$ node test.mjs 
node:internal/errors:465
    ErrorCaptureStackTrace(err);
    ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '[...]/nosuch.mjs' imported from [...]/test.mjs
    at new NodeError (node:internal/errors:372:5)
    at finalizeResolution (node:internal/modules/esm/resolve:405:11)
    at moduleResolve (node:internal/modules/esm/resolve:966:10)
    at defaultResolve (node:internal/modules/esm/resolve:1176:11)
    at ESMLoader.resolve (node:internal/modules/esm/loader:605:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:318:18)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:80:40)
    at link (node:internal/modules/esm/module_job:78:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

Node.js v18.0.0
$ node --loader=testdouble test.mjs                   
(node:17583) ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
node:internal/process/esm_loader:91
    internalBinding('errors').triggerUncaughtException(
                              ^

[Error: ENOENT: no such file or directory, open [...]/nosuch.mjs'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: '[...]/nosuch.mjs'
}

Node.js v18.0.0

I ran into this when documenting ways to mock ES modules with Jasmine. Jasmine tries to distinguish between a configuration file not existing and other types of errors loading a config file by checking the error code. The testdouble loader breaks that check, and that combined with another bug (jasmine tries to load jasmine.js even if jasmine.json is present) means that Jasmine doesn't work with the Testdouble loader if the default jasmine.json config file is used. I have a workaround that will ship in Jasmine 5.1.0, but the same problem could come up with other tools.

I'm not sure if this is fixable or not. My guess is that Node expects that a loader's resolve method will only return URLs referring to files that exist, but that approach seems like it could cause problems when multiple loaders are chained together.

(edited to fix some confusing copypasta)

searls commented 1 year ago

This is a great issue. I unfortunately don't know the answer because I'm not familiar with the ESM side of quibble. @giltayar may know if it's possible to detect this is the cause and fire our own exception that looks like the real one