Skip to content

Import named default is typed differently than default import (with moduleResolution: node16) #49567

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

Closed
calebeby opened this issue Jun 15, 2022 · 6 comments · Fixed by #49814
Closed
Assignees
Labels
Bug A bug in TypeScript

Comments

@calebeby
Copy link

calebeby commented Jun 15, 2022

Bug Report

🔎 Search Terms

default import, default export, named default export, module resolution, node 16

🕗 Version & Regression Information

Version 4.7.3

I also tried nightly, it is present there too.

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about default imports/exports
  • I was unable to test this on prior versions because they didn't have the node16 setting

⏯ Playground Link

Can't use playground because it requires multiple files.

https://github.com/calebeby/ts-import-bug

💻 Code

In node, mod2 and def are the "CJS namespace import" as described in the Node docs.

import mod2, {default as def} from 'foo'

// TS thinks this is false; it is actually true
console.log(def === mod2)

// TS thinks this works but it doesn't (at runtime def === mod2)
def()

foo.d.ts:

export declare function def(): void
export default def

If I switch moduleResolution to node, mod2 and def have the same type (not matching node's native ESM behavior, but that is expected since moduleResolution is not set to node16)

🙁 Actual behavior

a.mts:11:13 - error TS2367: This condition will always return 'false' since the types '() => void' and 'typeof import("/Users/calebeby/Projects/ts-import-bug/foo/foo")' have no overlap.

🙂 Expected behavior

def and mod2 should have the same type (the CJS namespace). TS should say that the def() line is an error. def === mod2 should not say This condition will always return 'false'

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Jun 15, 2022
@weswigham
Copy link
Member

Hm, I'm guessing we're missing the logic we use for resolving default imports on named-imports-named-default. Should probably unify those codepaths.

@calebeby
Copy link
Author

calebeby commented Jun 16, 2022

I have some more information - it seems related:

If foo points to a .cjs file / not type:module:

foo.cjs

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.def = void 0;
function def() { }
exports.def = def;
exports.default = def;
import mod2, {default as def} from 'foo'

In node:
mod2 and def are both the module object like { def: ..., default: ... }

In TS:

mod2 is the module object like { def: ..., default: ... }, def is the function.


If foo points to a .mjs file / type:module:

foo.mjs

export function def() {}
export default def
import mod2, {default as def} from 'foo'

In node:
mod2 and def are both the function

In TS:

mod2 is the module object like { def: ..., default: ... }, def is the function.


It seems like TS needs to read the .d.ts differently depending on whether the actual import resolves to a cjs or mjs (since that's what Node does).

@weswigham
Copy link
Member

Oh, I misread the repro initially -

    "module": "ESNext",
    "moduleResolution": "Node16",

is a questionable configuration. module: node16 or module: nodenext replicate node's import behavior - module: esnext with moduleResolution: node16 is some weird browser/bundler configuration without really great specs on its behavior. Specifically, it's behavior is mostly "whatever we used to do for module: esnext but with export map traversal", and not "what node actually does".

You probably wanna set module: nodenext - checking for the node cross-format default import interop behavior is gated behind the module flag, not the moduleResolution one.

@weswigham weswigham added Working as Intended The behavior described is the intended behavior; this is not a bug and removed Needs Investigation This issue needs a team member to investigate its status. labels Jul 6, 2022
@calebeby
Copy link
Author

calebeby commented Jul 6, 2022

@weswigham In the repro repo I switched module to NodeNext and it still has the same issue

@weswigham
Copy link
Member

Ah, you're right, my initial assessment was right; I was just thrown off by the incorrect compiler option. It's probably worth noting that this divergent behavior between the two kinds of default imports checking-wise can also be observed in older emit modes just using esModuleInterop.

@aidenlx
Copy link

aidenlx commented Sep 25, 2022

Was this issue actually fixed in v4.8.3? changing typescript version to ^4.8.3 in https://github.com/calebeby/ts-import-bug produce the exact same result as before.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants