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

Re-exporting CJS module with export = from ESM via export * causes type error #51923

Open
dsherret opened this issue Dec 16, 2022 · 2 comments
Open
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@dsherret
Copy link
Contributor

dsherret commented Dec 16, 2022

Bug Report

TypeScript raises diagnostics when re-exporting a cjs package whose type declaration is defined with export = via export * in an ES module.

🔎 Search Terms

  • "'export =' and cannot be used with 'export *'"

🕗 Version & Regression Information

5.0.0-dev.20221216

⏯ Playground Link

N/A - multiple files

💻 Code

  1. mkdir scratch && cd scratch
  2. npm init -y
  3. npm i react @types/react typescript@next
  4. tsconfig.json
    {
      "compilerOptions": {
        "target": "ESNext",
        "moduleResolution": "NodeNext",
        "strict": true
       }
    }
  5. react.mts
    export * from "react";
    
    // notice this import is fine and does not error
    import { useCallback } from "react";
  6. main.mts
    import { useCallback } from "./react.mjs";
    console.log(useCallback);

🙁 Actual behavior

Try type checking & emitting:

> npx tsc
main.mts:1:10 - error TS2305: Module '"./react.mjs"' has no exported member 'useCallback'.

1 import { useCallback } from "./react.mjs";
           ~~~~~~~~~~~

react.mts:1:15 - error TS2498: Module '"V:/scratch-npm/node_modules/@types/react/index"' uses 'export =' and cannot be used with 'export *'.

1 export * from "react";
                ~~~~~~~


Found 2 errors in 2 files.

Errors  Files
     1  main.mts:1
     1  react.mts:1

It will emit, so try running it and see that it works:

> node main.mjs
[Function: useCallback]

🙂 Expected behavior

Should type check fine because this works at runtime and export = matches the runtime code which has a single module .exports = ... assignment.

@weswigham
Copy link
Member

So, IIRC, we didn't allow this because what meanings, exactly, the export * may pull from the cjs-y export= defined module was ambiguous. Now, in node16, we have a fairly canonical answer nowadays, namely that the syntactically detected cjs named exports will be reexported (which we can probably assume are the named members in the declaration file). Downlevel, it's still a bit unclear - does it strip the call and construct signatures from the assigned module object? The __exportStar helper has a behavior, but it's maybe not the one people would expect (especially without allowSyntheticDefaultImports or esModuleInterop), which, afaik, is why we have this error, which is omega old. Removing the error entirely only makes 3 tests fail, but those tests do bring up some weird cases, since you can export= a lot of stuff, things like

    declare module "interface" {
        interface Foo {
            x: number;
            y: number;
        }
        export = Foo;
    }

or, more interestingly,

    declare module "variable" {
        var Foo: {
            a: number;
            b: number;
        }
        export = Foo;
    }

Even in modern node, it can be confusing - take something like

// @filename: a.ts
export = { a: 0 }
    
// @filename: b.ts
export * from './a';

It's maybe unclear that export * from './a'; reexports nothing, because a.ts has no syntactically hoisted members, and the module-as-default isn't included in the reexported members (since defaults never are).

So while we could remove the error, I think it's probably still pointing out likely issues when people cross module system boundaries.

@dsherret
Copy link
Contributor Author

dsherret commented Jan 3, 2023

It's maybe unclear that export * from './a'; reexports nothing, because a.ts has no syntactically hoisted members, and the module-as-default isn't included in the reexported members (since defaults never are).

So while we could remove the error, I think it's probably still pointing out likely issues when people cross module system boundaries.

There's lots of cases where a cjs package described with export = could not work with named imports in an es module, but TypeScript will allow you to write a named import. I think the same should apply to re-exports?

kylejeske pushed a commit to electrode-io/fastify-server that referenced this issue Jun 7, 2023
add workaround for exporting types from fastify's cjs module "export ="
microsoft/TypeScript#51923
kylejeske added a commit to electrode-io/fastify-server that referenced this issue Jun 13, 2023
* fix: explicit type export

add workaround for exporting types from fastify's cjs module "export ="
microsoft/TypeScript#51923

* security: update mocha to v10.2.0

upgrade mocha to fix npm audit with a high result
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

3 participants