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

export default generates JavaScript and declaration file incompatible #58425

Closed
regseb opened this issue May 3, 2024 · 4 comments
Closed

export default generates JavaScript and declaration file incompatible #58425

regseb opened this issue May 3, 2024 · 4 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@regseb
Copy link

regseb commented May 3, 2024

🔎 Search Terms

export, default, cjs, import

I found this issue #47455 that relates a little to the same subject.

🕗 Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about default:

A default import of a commonjs module with a default in a esm file doesn't seem to be the default export of that module when module is node16 or nodenext.

  • TypeScript is exposing node's behavior here - when a esm module default imports a commonjs module, that whole commonjs module is made available as the default import. If you then want the actual default member of that module, you'll need to access the `default member of that import. Refer to the node documentation for more information.

⏯ Playground Link

https://www.typescriptlang.org/play/?module=0#code/KYDwDg9gTgLgBAE2AMwIYFcA28BEyIQ4DcQA

💻 Code

export default "foo";

🙁 Actual behavior

  • .js

    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.default = "foo";
    // ESM equivalent: export default { "default": "foo" }
  • .d.ts

    declare const _default: "foo";
    export default _default;

🙂 Expected behavior

  • .js

    "use strict";
    module.exports = "foo";
    Object.defineProperty(module.exports, "__esModule", { value: true });
  • .d.ts (this file hasn't changed)

    declare const _default: "foo";
    export default _default;

🙃 Alternative expected behavior

  • .js (this file hasn't changed)

    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.default = "foo";
  • .d.ts

    declare const _default: {
        default: string;
    };
    export default _default;

Additional information about the issue

The JavaScript file and the declaration file are incompatible:

  • JavaScript exports an object with default property: { "default": "foo" }
  • Declaration file exports a string: "foo"

I discovered this problem when importing projects that use TypeScript:

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label May 3, 2024
@RyanCavanaugh
Copy link
Member

The code said export default "foo"; and TS generated a file with an export named default. I'm not really understanding why you think this is wrong, especially when it's addressed in the FAQ. If you want a CJS module with export = semantics, write export =.

@regseb
Copy link
Author

regseb commented May 4, 2024

I found an inconsistency between the generated JavaScript and the generated declaration file from the same TypeScript file.

Files

  • package.json

    {
      "name": "testcase",
      "version": "1.0.0",
      "dependencies": {
        "typescript": "5.4.5"
      }
    }
  • foo.ts

    export default "foo";
  • index.mjs

    import foo from "./foo.js";
    
    console.log(foo.default.toUpperCase());
    console.log(foo.toUpperCase());

Steps

  1. npm install

  2. npx tsc --declaration foo.ts

  3. cat foo.js

    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.default = "foo";
  4. cat foo.d.ts

    declare const _default: "foo";
    export default _default;
  5. npx tsc --noEmit --checkJs index.mjs

    index.mjs:3:17 - error TS2339: Property 'default' does not exist on type '"foo"'.
    
    3 console.log(foo.default.toUpperCase());
                      ~~~~~~~
    
    
    Found 1 error in index.mjs:3
    
    
  6. node index.mjs

    FOO
    file:///home/regseb/testcase/index.mjs:4
    console.log(foo.toUpperCase());
                    ^
    
    TypeError: foo.toUpperCase is not a function
        at file:///home/regseb/testcase/index.mjs:4:17
        at ModuleJob.run (node:internal/modules/esm/module_job:222:25)
        at async ModuleLoader.import (node:internal/modules/esm/loader:323:24)
        at async loadESM (node:internal/process/esm_loader:28:7)
        at async handleMainPromise (node:internal/modules/run_main:113:12)
    
    Node.js v20.12.2
    

Summary

  • foo.default.toUpperCase()
    • works with JavaScript (node): FOO
    • fails with declaration file (tsc): Property 'default' does not exist on type '"foo"'.
  • foo.toUpperCase()
    • fails with JavaScript (node): foo.toUpperCase is not a function
    • passes with declaration file (tsc): no error

@RyanCavanaugh
Copy link
Member

This is a known issue with .d.ts files when cross-compiling module targets -- they don't say what their originating module settings were. If you're developing in ESM but shipping CJS+DTS, you'll need to be a bit more cautious (i.e. follow the docs) until this gets figured out.

@typescript-bot
Copy link
Collaborator

This issue has been marked as "Working as Intended" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale May 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants