Vitest cannot transpile with TypeScript `moduleResolution` set to `node16`
Describe the bug
When transpiling a trivial TypeScript library using the TypeScript-recommended module resolution (i.e.: "moduleResolution": "Node16", "module": "Node16"), tsc throws an error on Vitests' CommonJS version of the type declarations. These declarations in turn incorrectly reference the ES Module version of the declarations, which results in the error.
See https://github.com/softcraft-development/vitest-issue/blob/main/README.md for further details. It references https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html#im-writing-a-library, which in turn strongly recommends "moduleResolution": "Node16", "module": "Node16" as the tsconfig.json settings for Node libraries, both with and without the use of a bundler.
Correct handling of JavaScript/TypeScript modules is still overly complicated in 2024. In my own libraries I've taken to creating entirely separate ESM and CommonJS versions. It looks like Vitest is trying to mix the two strategies, which doesn't work under every circumstance.
Reproduction
- Clone the repository I created to demonstrate this issue: https://github.com/softcraft-development/vitest-issue
- Execute
npm installto install the dependencies, including TypeScript and Vitest. - Execute
npm run node16
This generates the error:
node_modules/vitest/index.d.cts:1:15 - error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("./dist/index.js")' call instead.
System Info
System:
OS: macOS 12.6
CPU: (8) x64 Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
Memory: 19.16 MB / 16.00 GB
Shell: 5.8.1 - /bin/zsh
Binaries:
Node: 18.18.2 - ~/.nvm/versions/node/v18.18.2/bin/node
npm: 9.8.1 - ~/.nvm/versions/node/v18.18.2/bin/npm
pnpm: 9.1.4 - ~/Library/pnpm/pnpm
Browsers:
Chrome: 125.0.6422.114
Safari: 16.1
npmPackages:
vitest: ^1.6.0 => 1.6.0
Used Package Manager
npm
Validations
- [X] Follow our Code of Conduct
- [X] Read the Contributing Guidelines.
- [X] Read the docs.
- [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- [X] Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- [X] The provided reproduction is a minimal reproducible example of the bug.
Why would you compile Vitest tests into CJS? Vitest doesn't expose an actual CJS entry point, only a fake one for the old node target.
Vitest doesn't expect tests to be compiled, it transforms files on the fly.
Why would you compile Vitest tests into CJS?
I'm not; I'm using tsc to do type checking on the test files (along side the source files). Note the noEmit: true setting in tsconfig.json.
@softcraft-development have you tried renaming your .ts files to .mts? Your package type is CommonJS, but you are using ESM syntax in .ts files - that's what probably confuses TypeScript types resolution. To confirm this is the source of your issue, you can add "skipLibCheck": true and "noEmit": false to your tsconfig.json and run npm run node16, and you'll see that TypeScript emits CJS code in this case. Alternatively, you can keep the .ts file extensions but switch your package to ESM ("type": "module") if that works for you.
I am also having this issue trying to use Node v20 and NodeNext module and moduleResolution values. I have tried a few combinations and couldn't get it to build unless I turn off lib checking like @igordanchenko noted above. Are there are non-bundler based tsconfig configurations that will build properly?
@mhintzke chances are your source code module system contradicts your package module system and TypeScript file extensions.
Here are the questions that will help you figure out the correct combination.
- Do you use ESM syntax? (you import modules with the
importstatements) - Is your package ESM or CommonJS?
- What file extensions do your TypeScript files use?
I assume you use ESM syntax, so I will cover only this scenario.
Here are the valid combinations:
-
Your package is CommonJS (there is either no
"type"field in yourpackage.jsonor"type": "commonjs"). In this case, your TypeScript files must use.mtsextensions, and you'll need to use.mjsextensions in your import statements. -
Your package is ESM (
"type": "module"is present in yourpackage.json). In this case, you can use.tsextensions for TypeScript files and.jsextensions in your import statements. You can also use.mts/.mjsextensions as above, but they are really unnecessary in this case.
@igordanchenko I appreciate the insight!
I will preface that I am in the middle of upgrading a large project from Node v10 and associated packages to Node v22 so a lot of the ESM stuff is pretty new to me. I will say that I fall in line with your second combination. I have package.json set to module type and use .ts file extensions with imports like import foo from './foo.js'. So I believe I have all of the necessary configuration elsewhere, it is just the tsconfig.json that I think needs to be adjusted to stop the error fromi happening.
Were you able to put skipLibCheck back to false at all? If so, what did you have to do besides the above to get it to work?
@mhintzke
Were you able to put skipLibCheck back to false at all?
Yes, of course. But just keep in mind that TypeScript itself sets the skipLibCheck option to true when you create new project configuration with tsc --init.
If so, what did you have to do besides the above to get it to work?
Nothing really special. Here's a minimal example - https://stackblitz.com/edit/vitest-5820?file=tsconfig.json
@igordanchenko Here is my setup:
package.json
tsconfig.json
vite.config.ts
I only have a single explicitly import of vitest. The rest are all to vitest-mock-extended whicih provides utilities for doing mocking.
Yet I continue to get this error:
@mhintzke your screenshot doesn't show the "type": "module" in your package.json. Can you confirm it's actually present?
It would also be helpful if you could provide a minimal repro on Stackblitz.
@igordanchenko Yes, can confirm.
I will start to do that and see how it goes. Thanks
@igordanchenko
Here is my reproduction. It definitely seems related to using vitest-mock-extended as once I added it and imported mock it blew up. I will go over to that package and see if I find any issues and if not, create one.
https://stackblitz.com/edit/vitest-5820-megvgc
@softcraft-development are you using vitest-mock-extended by chance? My above stackblitz reproduces the issue by simply utilizing that library in any form. I created https://github.com/eratio08/vitest-mock-extended/issues/546 to hopefully get it looked at by them.
@mhintzke I'm glad to hear you were able to identify your culprit. I took a quick look at vitest-mock-extended package.json and as far as I can tell, their module system and file extensions are all... backwards... The package is CommonJS, but they export ESM bundle with .js / .d.ts file extensions, while CJS bundle uses .cjs / .d.cts extensions. This makes no sense.
https://arethetypeswrong.github.io/?p=vitest-mock-extended%402.0.0
@igordanchenko oooh I did not even notice that yet. Good catch and cool tool there. I guess we will just see what they say, but this is definitely not a vitest issue. I am curious to see what @softcraft-development says as it will change whether or not this issue is still valid or not.
Thanks for the help!
I am curious to see what @softcraft-development says as it will change whether or not this issue is still valid or not.
It doesn't look like a valid issue at all. The original repro also has a mismatch between the module system and file extensions. I pointed this out in my very first comment on this thread.
@igordanchenko ah, yes after re-reading it with new found knowledge I see what his issue was. You are correct, this should probably be closed
Seems like it is a user error.