vite-plugin-react: ts error when moduleResolution is "node16"

Describe the bug

I am migrating my node application from cjs to mjs. When I set "moduleResolution": "node16" in tsconfig.json, the vite.config.ts reports This expression is not callable. Type 'typeof import("/node_modules/@vitejs/plugin-react/dist/index")' has no call signatures.ts(2349). I tried to change reactPlugin() to reactPlugin.default(), the error disappears but vite build failed with error TypeError: reactPlugin.default is not a function

Reproduction

https://stackblitz.com/edit/vitejs-vite-4x7wea?file=tsconfig.json,vite.config.ts,tsconfig.node.json&terminal=dev

Steps to reproduce

set compilerOptions.moduleResolution = ‘node16’ in tsconfig.json

System Info

System:
    OS: macOS 13.0.1
    CPU: (8) arm64 Apple M1 Pro
    Memory: 44.64 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 16.14.0 - ~/.nvm/versions/node/v16.14.0/bin/node
    Yarn: 1.22.17 - ~/.nvm/versions/node/v16.14.0/bin/yarn
    npm: 8.19.2 - ~/.nvm/versions/node/v16.14.0/bin/npm
  Browsers:
    Chrome: 109.0.5414.119
    Firefox: 108.0.2
    Safari: 16.1
  npmPackages:
    @vitejs/plugin-react: ^3.1.0 => 3.1.0 
    vite: ^4.1.1 => 4.1.1

Used Package Manager

npm

Logs

No response

Validations

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 1
  • Comments: 20 (7 by maintainers)

Commits related to this issue

Most upvoted comments

Removing entries from the export map feels like going backward. Would this not break bundler resolution ?

Every TypeScript resolution mode supports extension matching to sibling files. If every index.mjs has a sibling index.d.mts and every index.cjs has a sibling index.d.cts and every index.js has a sibling index.d.ts, then nothing in package.json needs to explicitly reference the declaration files. TypeScript can always figure it out by following the implementation file config and then looking for a sibling.

If the types are not colocated with the implementation, or you just want to be explicit in the export map, you can use nested conditions:

"exports": {
  "import": {
    "types": "./types/index.d.mts",
    "default": "./esm/index.mjs"
  },
  "require": {
    "types": "./types/index.d.cts",
    "default": "./cjs/index.cjs"
  }
}

Just note that this does not make it possible to reuse a single .d.ts for both the CJS and ESM entrypoint—the file extension and package.json "type": "module" (or lack thereof) is the only thing that determines the module kind here, so a nested config like

"exports": {
  "import": {
    "types": "./types/index.d.ts", // <-- .d.ts
    "default": "./esm/index.mjs"
  },
  "require": {
    "types": "./types/index.d.ts", // <-- .d.ts
    "default": "./cjs/index.cjs"
  }
}

is equivalent to what is published today. In other words, just because you reach a file via the import condition in an export map during a given resolution does not automatically make it an ES module; rather, a file’s module kind status is statically determined by information from the file system alone (its file extension and package.json "type"). And again, let me stress that these are not our rules, they’re Node’s. We just apply Node’s algorithm to declaration files such that when each implementation file has a corresponding declaration file, we can predict what Node will do (which is obviously necessary to type check across multiple files). Don’t shoot the messenger 😅

This is exactly why the TS team added the bundler resolution, so that TS resolutions is more close (and maybe totally identical when using pure esm with explicit extensions) to how bundler like Vite resolve modules.

So yeah you can now update TS to v5, and then update your TS config to use this new resolution, opt-in to allowImportingTsExtensions, remove forceConsistentCasingInFileNames that is now true by default.

Here is my config:

    "target": "ESNext",
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noPropertyAccessFromIndexSignature": true,
    "noFallthroughCasesInSwitch": true

I will add a note in the readme to advice using this resolution algorithm.

Hi!

With the team we decided to not invest more time in the node16 resolutions issues. This IMO a bad design and this as been fixed with TS 5 bundler mode that more clearly represent how most apps and projects are built. If you’re not able to migrate, we recommend keeping the old “node” resolution algorithm.