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

TS 4.7.1-rc cannot resolve default CJS or ESM exports with module nodenext. #49189

Closed
scott-lc opened this issue May 20, 2022 · 5 comments
Closed
Labels
External Relates to another program, environment, or user action which we cannot control.

Comments

@scott-lc
Copy link

Bug Report

In React development the NPM packages classnames and clsx, are extremely popular, but TS 4.7.1-rc cannot resolve their default export:

import clsx from "clsx";
import classNames from "classnames";

console.log(clsx("a", "b", "c"));
console.log(classNames("a", "b", "c"));

Results in the error(s):

This expression is not callable.
  Type 'typeof import("...")' has no call signatures.ts(2349)

Looking at the package.json files for both the clsx and classnames packages, they each specify the types property.

clsx/package.json

  "module": "dist/clsx.m.js",
  "main": "dist/clsx.js",
  "types": "clsx.d.ts",

clsx/clsx.d.ts

...
declare const clsx: (...classes: ClassValue[]) => string;
export default clsx;

classnames/package.json

  "main": "index.js",
  "types": "./index.d.ts",

classnames/index.d.ts

export default function classNames(...args: Argument[]): string;

🔎 Search Terms

nodenext, node16, default export, expression is not callable

🕗 Version & Regression Information

Exists in TS 4.7.0-beta, 4.7.1-rc and 4.8.0-dev.20220520.

  • I was unable to test this on prior versions because nodenext is a new feature.

⏯ Playground Link

Playground link - N/A requires external dependencies.

Sample repo - https://github.com/scott-lc/tsc-issue-220520

💻 Code

import clsx from "clsx";
import classNames from "classnames";

console.log(clsx("a", "b", "c"));
console.log(classNames("a", "b", "c"));

tsconfig.json

{
  "compilerOptions": {
    "esModuleInterop": true,
    "alwaysStrict": true,
    "module": "NodeNext",
    // "moduleResolution": "NodeNext",   # works with `node` but not `nodenext`
    "target": "ESNext"
  },
  "include": ["./index.ts"]
}

Also see this repo.

🙁 Actual behavior

Code fails to compile with module: nodenext. Works fine pre 4.7 with module: esnext.

🙂 Expected behavior

nodenext module resolution to not require changes to popular third-party NPM code.

@scott-lc
Copy link
Author

Possibly related to #48845

@andrewbranch andrewbranch added the External Relates to another program, environment, or user action which we cannot control. label May 25, 2022
@andrewbranch
Copy link
Member

The issue is that the typings describe an ES module with a default export (which actually exists at dist/clsx.m.js), but that module is not provided to Node or TypeScript. (Note that the module key is nonstandard and used primarily by bundlers, if I understand correctly.) So while Node sees a CJS module with a module.exports of some function (which you can default-import into an ES module because of Node’s interop), TypeScript sees a CJS module whose module.exports.default is that function. In other words, the typings are incorrect—since they have been loaded through the CJS entrypoint, they should accurately describe the shape of the CJS implementation. If the package wants to declare typings both for CJS and ESM, it needs to do so via package.json exports (and will need two separate .d.ts files).

@scott-lc
Copy link
Author

Thanks @andrewbranch. Similar to what you mentioned here, I think the community will need to open PRs with these individual libraries to update their types.

@scott-lc
Copy link
Author

To those that run into this issue, I'd like to give a shoutout to In the interim @JacobLey who published default-import as an interim workaround as described here.

@Edweis
Copy link

Edweis commented Nov 21, 2022

On my side, I hacked my way by redefining the import explicitly:

import _useSWR, {  SWRHook } from 'swr';

const useSWR = _useSWR as unknown as  SWRHook

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
External Relates to another program, environment, or user action which we cannot control.
Projects
None yet
Development

No branches or pull requests

3 participants