esbuild-kit/esm-loader

Resolve failed when the same name file and directory

sxzz opened this issue · 3 comments

sxzz commented

Reproduction: https://github.com/sxzz/esbuild-esm-loader-issue

src/
├── index.ts
├── utils        # this is a directory
└── utils.ts
node --loader @esbuild-kit/esm-loader src/index.ts
Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '/path/esbuild-esm-loader-issue/src/utils' is not supported resolving ES modules imported from /path/esbuild-esm-loader-issue/src/index.ts
    at __node_internal_captureLargerStackTrace (node:internal/errors:465:5)
    at new NodeError (node:internal/errors:372:5)
    at finalizeResolution (node:internal/modules/esm/resolve:433:17)
    at moduleResolve (node:internal/modules/esm/resolve:1009:10)
    at defaultResolve (node:internal/modules/esm/resolve:1218:11)
    at i (file:///path/esbuild-esm-loader-issue/node_modules/.pnpm/@esbuild-kit+esm-loader@2.1.0/node_modules/@esbuild-kit/esm-loader/dist/index.js:87:17)
    at ESMLoader.resolve (node:internal/modules/esm/loader:580:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:294:18)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:80:40)
    at link (node:internal/modules/esm/module_job:78:36) {
  code: 'ERR_UNSUPPORTED_DIR_IMPORT',
  url: 'file:///path/esbuild-esm-loader-issue/src/utils'
}

If rename or remove src/utils, it works.

IMHO, the problem caused by here.

if ((error as any).code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
return resolve(`${specifier}/index`, context, defaultResolve);
}

Hmm, I'm not sure if this is a bug.

When you import a path ./utils, and it explicitly matches a directory name, I think that should take precedence over an implicit file name match.

Besides, since this is how Node.js resolves it (rename your files to .js and run node src/index.js), changing this behavior will introduce a breaking change to how native ESM resolves files.

sxzz commented

I think a better way is to be consistent with ts-node, esbuild and esmo (<0.14.x).

  • If you try to click the import path with ctrl key, the VSCode will jump to src/utils.ts. We should respect resolution algorithm of TypeScript.

  • esbuild Demo

     > esbuild src/index.ts --bundle
     (() => {
       // src/utils.ts
       var foo = "bar";
    
       // src/index.ts
       console.log(foo);
     })();

Also, native ESM has no default extension and resolve index, so changing .ts to .js directly will not work. (even if the utils folder does not exist)

To use Node.js's classic resolution algorithm, you can use the --experimental-specifier-resolution=node flag:

$ node --loader @esbuild-kit/esm-loader --experimental-specifier-resolution=node src/index.js
(node:34643) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:34643) ExperimentalWarning: The Node.js specifier resolution flag is experimental. It could change or be removed at any time.
bar

Or with tsx:

$ npx tsx --experimental-specifier-resolution=node src/index.js
bar

With this flag, users have a way to toggle between both resolution algorithms. If we override the default behavior to force Node's classic resolution algorithm, users will be limited to one behavior. For this reason, I don't think we should change it.

I agree we should try to resolve /index.js instead of /index though. Hasn't happened yet but it's bound to. Happy to accept a PR to fix that.