Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CommonJS hybrid module with exports.import configured still treated as CommonJS with module:ES2020 and moduleResolution:Node16 #50083

Closed
valeriangalliat opened this issue Jul 28, 2022 · 3 comments
Assignees
Labels
By Design Deprecated - use "Working as Intended" or "Design Limitation" instead External Relates to another program, environment, or user action which we cannot control.

Comments

@valeriangalliat
Copy link

valeriangalliat commented Jul 28, 2022

Bug Report

πŸ”Ž Search Terms

  • typescript hybrid commonjs exports import mismatch module es2020 moduleresolution node16

πŸ•— Version & Regression Information

4.7.4, 4.8.0-dev.20220728

  • This is the behavior in every version I tried

⏯ Playground Link

The playground doesn't seem to allow me to configure module: es2020 and moduleResolution: node16 so here's a repo with the smallest repro I could come up with instead:

https://github.com/valeriangalliat/typescript-exports-bug-repro

πŸ’» Code

Main project:

// tsconfig.json
{
  "compilerOptions": {
    "module": "ES2020",
    "moduleResolution": "Node16"
  },
  "include": ["index.ts"]
}
// index.ts
import foo from 'foo'

console.log(foo.bar())

node_modules/foo dependency:

// package.json
{
  "main": "./foo.js",
  "module": "./foo.mjs",
  "exports": {
    ".": {
      "require": {
        "types": "./foo.d.ts",
        "default": "./foo.js"
      },
      "import": {
        "types": "./foo.d.ts",
        "default": "./foo.mjs"
      }
    }
  },
  "types": "./foo.d.ts"
}
// foo.mjs
export default {
  bar() { return 42 }
}
// foo.d.ts
declare const _default: {
    bar(): any;
};
export default _default;

πŸ™ Actual behavior

During build, the following error occurs:

index.ts:3:17 - error TS2339: Property 'bar' does not exist on type 'typeof import("/repro/node_modules/foo/foo")'.

3 console.log(foo.bar())

πŸ™‚ Expected behavior

Builds successfully.


It appears that despite following the types in exports['.'].import.types, TypeScript still interprets the types as being CommonJS because the package.json of the dependency doesn't set "type": "module".

For example the above examples has TypeScript build when doing foo.default.bar(), but it doesn't result in runnable code.

Essentially it looks like TypeScript supports hybrid dependencies that default to ESM and declare exports['.'].require, but doesn't support hybrid dependencies that default to CJS and declare exports['.'].import.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Jul 28, 2022
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 4.9.0 milestone Jul 28, 2022
@weswigham
Copy link
Member

TypeScript still interprets the types as being CommonJS because the package.json of the dependency doesn't set "type": "module".

Yep. That's it. A given file can only actually define one format, either cjs or esm - cjs is just the slightly more portable of the two. This is working as intended (or as best it can, anyway). You need a separate declaration file for accurate types for separate formats of entrypoint.

@weswigham weswigham added By Design Deprecated - use "Working as Intended" or "Design Limitation" instead External Relates to another program, environment, or user action which we cannot control. and removed Needs Investigation This issue needs a team member to investigate its status. labels Jul 29, 2022
@weswigham weswigham closed this as not planned Won't fix, can't repro, duplicate, stale Jul 29, 2022
@valeriangalliat
Copy link
Author

That is correct. However setting exports['.'].import to a given file should treat it as ESM, not CJS like it currently does

It treats the ESM file as CJS even if the filename ends in .mjs and even if it's put in a subdir with a package.json containing {"type":"module"}

This makes it impossible to write a module that supports both being imported as CJS and ESM with TypeScript

Are you sure that's the intended behavior?

@weswigham
Copy link
Member

weswigham commented Jul 29, 2022

However setting exports['.'].import to a given file should treat it as ESM, not CJS like it currently does

That's not how it works. File extension/type module scope determine format, not condition. You're free to point an import condition at any format you want. It's treated as cjs because your types entry has a cjs format.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
By Design Deprecated - use "Working as Intended" or "Design Limitation" instead External Relates to another program, environment, or user action which we cannot control.
Projects
None yet
Development

No branches or pull requests

3 participants