The script should display Success!
, and does do so if mypackage/Lib.js
is renamed to mypackage/index.js
.
internal/modules/esm/resolve.js:61
let url = moduleWrapResolve(specifier, parentURL);
^
Error: Cannot find module /home/dandv/prg/node-cant-find-module-with-main-not-index.js/mypackage imported from /home/dandv/prg/node-cant-find-module-with-main-not-index.js/run.js
at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:61:13)
at Loader.resolve (internal/modules/esm/loader.js:85:40)
at Loader.getModuleJob (internal/modules/esm/loader.js:191:28)
at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:42:40)
at link (internal/modules/esm/module_job.js:41:36) {
code: 'ERR_MODULE_NOT_FOUND'
}
I'm trying to run node with -experimental-specifier-resolution=node
because TypeScript can't output .mjs files and I want to use extension-less import
statements. I prefer to use Lib.js
instead of index.js
to distinguish in my IDE between the main files of multiple packages in my monorepo that otherwise would all look like index.js
.
/cc @nodejs/modules
The repro link seems correct to me; if so, this seems like a bug.
If you use explicit extensions, and don't use experimental specifier resolution, what happens?
I'm trying to run node with
-experimental-specifier-resolution=node
because TypeScript can't output .mjs files and I want to use extension-lessimport
statements.
TypeScript has only limited support for ES modules so far. But in many cases the following workaround works:
"type": "module"
to package.json
.import './some-file.js'
in your TypeScript source code. TypeScript will find .ts
files if you use .js
in the specifier.(That's the more verbose version of Jordan's "If you use explicit extensions". :))
If you use explicit extensions, and don't use experimental specifier resolution, what happens?
Then the script runs correctly.
So the issue appears to be that to support that we introduced for experimental-specifier-resolution
does not respect the package.json main field. I can get the example to work by changing the Lib.js
file to be index.js
. This is definitely a bug in the support for experimental resolution. I'm not 100% where the bug lives but this is where we should be resolving package main for experimental resolution
https://github.com/nodejs/node/blob/master/src/module_wrap.cc#L1180-L1186
This is where it seems like where we are doing the resolution itself
https://github.com/nodejs/node/blob/master/src/module_wrap.cc#L826-L862
It is possible that some order of operations bug is not even checking for the package.main... and tbh the work we've done around exports is going to confuse this a bit too. I'll try and find some time to dig in but this is going to have to be lower priority for me personally, so if anyone else wants to pick this up please go ahead!
as an aside, thanks so much for these amazing bug reports @dandv
I figured out the exact reason.
Note: Related C++ code is rewritten using JS in this commit, new related code location link:
https://github.com/nodejs/node/blob/master/lib/internal/modules/esm/resolve.js#L594
More comments can be found in the historical C++ file in the commit link above.
function moduleResolve(specifier /* string */, base /* URL */) { /* -> URL */
// Order swapped from spec for minor perf gain.
// Ok since relative URLs cannot parse as URLs.
let resolved;
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
resolved = new URL(specifier, base);
} else {
try {
resolved = new URL(specifier);
} catch {
return packageResolve(specifier, base);
}
}
return finalizeResolution(resolved, base);
}
The parameter specifier
is ./mypackage
here, so it passes the relative path check and returns as is directly. So the package main resolve
related code won't run.
I am working on this, maybe I will submit a PR in about a few days.
EDIT: This is working as intended, I misunderstood the spec. See https://nodejs.org/api/esm.html#esm_import_specifiers
Leaving the below for history only.
Seeing the same thing on Node 14.1, OSX. In my package.json I have
"type": "module",
I then have one file, server.js
, which in turn imports another file, serverModule.js
:
server.js
import startServer from './serverModule';
startServer({});
serverModule.js
import args from './get-args';
// ......
export default startServer;
Now, I notice that adding .js
to any import in the chain solves it for that particular file. For example, this solves the import of serverModule.js
in server.js
:
import startServer from './serverModule.js';
But then in turn it complains about import args from './get-args';
in that file, and so on. It seems like file endings are required, but that runs contrary to the description here: https://medium.com/@nodejs/announcing-core-node-js-support-for-ecmascript-modules-c5d6dc29b663
Files ending in .js, or extensionless files, when the nearest parent package.json file contains a top-level field “type” with a value of “module”.
Thanks! This solution worked for me.
This seems resolved, closing. Feel free to reopen if something needs to be discussed further, thank you.
@ryzokuken I believe this actually still remains an implementation bug and I believe my suggestion in https://github.com/nodejs/node/pull/32612#discussion_r403340432 might be related to the fix here.
That said, we might end up deprecating this flag before the fix at this rate...
Most helpful comment
TypeScript has only limited support for ES modules so far. But in many cases the following workaround works:
"type": "module"
topackage.json
.import './some-file.js'
in your TypeScript source code. TypeScript will find.ts
files if you use.js
in the specifier.(That's the more verbose version of Jordan's "If you use explicit extensions". :))