Skip to content

make experimental-specifier-resolution=node support extension-less files #41465

@lachrist

Description

@lachrist

What is the problem this feature will solve?

I thought that the --experimental-specifier-resolution=node CLI option was made to emulate legacy cjs resolution algorithm. However the legacy cjs module loader can handle extension-less files whereas which is not the case with this CLI option:

> cat yo
console.log("yo");
> node yo
yo
> node --experimental-specifier-resolution=node yo
(node:33081) ExperimentalWarning: The Node.js specifier resolution in ESM is experimental.
(Use `node --trace-warnings ...` to show where the warning was created)
node:internal/errors:464
    ErrorCaptureStackTrace(err);
    ^

TypeError [ERR_INVALID_MODULE_SPECIFIER]: Invalid module "file:///Users/soft/Desktop/workspace/appmap-agent-js/yo" 
    at new NodeError (node:internal/errors:371:5)
    at ESMLoader.load (node:internal/modules/esm/loader:380:13)
    at async ESMLoader.moduleProvider (node:internal/modules/esm/loader:280:47)
    at async link (node:internal/modules/esm/module_job:70:21) {
  code: 'ERR_INVALID_MODULE_SPECIFIER'
}

Node.js v17.3.0

These extension-less files are actually common in the bin directory of many popular projects -- eg: mocha. This makes it hard to use --experimental-loader on these projects because the below command fails for the same reason as above:

NODE_OPTIONS="--experimental-loader=./loader.mjs --experimental-specifier-resolution=node" npx mocha

What is the feature you are proposing to solve the problem?

Consider extension-less files as commonjs modules as the legacy cjs loader does. This is can done be done easily by modifying legacyExtensionFormatMap in lib/internal/modules/esm/get_format.js:

const legacyExtensionFormatMap = {
  '__proto__': null,
  '.cjs': 'commonjs',
  '.js': 'commonjs',
  '.json': 'commonjs',
  '.mjs': 'module',
  '.node': 'commonjs',
  '': 'commonjs' // Added line
};

Is there a good reason why this has not been considered?

What alternatives have you considered?

  1. Resolve the module in the loader provided by --experimental-loader and do not pass it to the default loader. This is a bazooka solution because the loader API basically enables to write one's own module resolution algorithm. We don't want to emulate the entire module resolution algorithm for such a minimal change.
  2. Rename and change the target of the symbolic link installed by npm to add a .js extension. This must happen in a preloaded module. It is extremely hacky and does not work on windows.

Metadata

Metadata

Assignees

No one assigned

    Labels

    esmIssues and PRs related to the ECMAScript Modules implementation.experimentalIssues and PRs related to experimental features.feature requestIssues that request new features to be added to Node.js.loadersIssues and PRs related to ES module loadersmoduleIssues and PRs related to the module subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions