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:

If you were using module: esnext, one of these was likely to happen:


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:

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.