Require module/moduleResolution to match when either is node16/nodenext by andrewbranch · Pull Request #54567 · microsoft/TypeScript (original) (raw)
FAQ if you landed here because of errors upgrading to 5.2
I was using module: commonjs
with moduleResolution: nodenext
because I need to emit CommonJS but I want package.json "exports"
support, etc. How do I do that now?
If you’re running your code in Node.js, or writing a library where someone might run it in Node.js, just use module: nodenext
(which defaults moduleResolution
to nodenext
too). It emits CommonJS by default. ESM is only emitted for .mts
files and for .ts
files whose nearest ancestor package.json contains "type": "module"
.
I was using module: esnext
with moduleResolution: nodenext
because I need to emit ESM but I want package.json "exports"
support, etc. How do I do that now?
If you’re running your code through a bundler, switch to moduleResolution: bundler
and leave your module
setting as esnext
.
If you’re running your code in Node.js, or writing a library where someone might run it in Node.js, just use module: nodenext
, and either write .mts
files, or ensure your package.json contains "type": "module"
. ESM is emitted for .mts
files and for .ts
files whose nearest ancestor package.json contains "type": "module"
.
Why did you change this? It was working fine.
If it was working fine, you got lucky. The original PR description below explains in a bit more detail, but if you were using module: commonjs
with moduleResolution: nodenext
, one of these consequences was likely to happen:
- Any
.mts
files emitted would be completely invalid for Node.js - If the package.json had
"type": "module"
, all.ts
files emitted would be completely invalid for Node.js - Module resolution would likely have resolved to the wrong format of dependency types for dual ESM/CJS packages, which may or may not cause disagreements about how default imports work
If you were using module: esnext
, one of these was likely to happen:
- Any
.cts
files emitted would be completely invalid for Node.js - If the package.json didn’t have
"type": "module"
, all.ts
files emitted would be completely invalid for Node.js - Module resolution would be completely unsafe and incorrect for Node.js. (If you used an eslint rule to enforce having file extensions on your imports, you can remove that now. TS handles that for you when properly configured.)
Original post
Design discussion: #54735
--module node16
and --moduleResolution node16
were designed as a pair, but historically we’ve allowed them to be mixed with other module
/moduleResolution
modes. Over time, however, it’s become clear that using them not as a pair doesn’t make a lot of sense and is a real footgun. Many people seem to think to emit ES modules in Node, they need --moduleResolution node16 --module esnext
, when what they really need is --module node16
with "type": "module"
in their package.json. The former (and no "type": "module"
) has many problems:
- Type checking will proceed as if we’re going to emit CommonJS, but after checking, we’ll actually emit ES modules. Node will be incapable of consuming these files, because it (like TypeScript) expects them to be CommonJS format.
- Type checking of default imports of CJS dependencies will be incorrect since
esModuleInterop
for CJS implements a different interop strategy than Node ESM. - Incorrect import module specifiers will be allowed to compile without error, since module resolution will assume that imports will actually be emitted as
require
calls, which use a different module resolution algorithm in Node.
Essentially, changing module
to something other than node16
when moduleResolution
is set to node16
can break every assumption made during module resolution and type checking.
Among the breaks, I found one interesting case in cheerio, where they’re intentionally overriding module
twice in order to do dual emit. They currently have additional eslint infrastructure set up to help protect them from some of the aforementioned pitfalls, but the latest release of the package hasn’t managed to escape shipping types with resolution errors. I’m going to submit a PR to that one with a safer alternative.
I also found that @tsconfig/docusaurus
(and their new official package @docusaurus/tsconfig
) were using a combination intended to simulate --moduleResolution bundler
, and they’re happy to switch to that in the next release of @docusaurus/tsconfig
.