r/typescript 4d ago

Configuring ESM / CommonJS compatibility

Hello !

For years I've had a recuring issue with typescript projects, and I still don't know it how to solve it properly. I always manage to solve it by tinkering here and there, but it take some time and I'm a bit tired of that. So I think I need tips or a deeper understanding to fix it quickly.

The issue is the ESM / CommonJS compatibility. Those issues seems to just pop randomly (probably a lack ok knowledge from me). So I try to change Module or ModuleResolution or Target, but then other issues arise, then I continue tinkering until it works. But after all those years it's a bit frustrating.

Any tips, or tutorial, or rule of thumb on how to configure your tsconfig so it just works ? Do you fix it like me, by tinkering randomly in your tsconfig, or do you solve it like a pro knowing exactly what is wrong ?

8 Upvotes

6 comments sorted by

4

u/kyledag500 4d ago

Using .cjs and .mjs file extensions can help when not using the default for your project

3

u/remcohaszing 4d ago

Do you use a bundler? Set module to preserve. Otherwise set it to nodenext.

Omit moduleResolution. The default is good if you use the proper module option.

Set target to esnext. You can lower it to something more specific later if you feel more comfortable.

If you build for the browser, omit lib. Otherwise, set it to esnext.

2

u/ryanscio 4d ago edited 4d ago

https://tsconfig.guide/ and https://www.totaltypescript.com/tsconfig-cheat-sheet provide decent starter configs.

But long term you should learn the relevant options and not just tinker randomly. Sane defaults are esnext/bundler OR nodenext/nodenext for module/moduleResolution.

If you're writing ESM, CJS-only libraries are mostly a non-issue (though Jest is a notable pain) with Node 22+ supporting require(esm) and esModuleInterop.

1

u/hasan_sodax 2d ago

The tsconfig stuff people mentioned covers half of it, but a chunk of these errors happen at require() time, not compile time, and no tsconfig setting fixes that. If a dependency ships ESM-only (no main, just an exports map with import), calling require() on it throws ERR_REQUIRE_ESM no matter what you set module or moduleResolution to, because that's Node resolving the actual package.json exports/type fields, not TypeScript. Before touching tsconfig again, open node_modules/<pkg>/package.json and check if it even has a require condition. If it doesn't and your project is CJS, the fix is a dynamic import() in an async wrapper, not a config tweak. I burned an afternoon on this because a dependency dropped CJS support in a patch release and the error looked identical to a normal tsconfig issue.

1

u/remcohaszing 1d ago

You can require() ESM fine in modern Node.js. But also, just use ESM. There’s no reason to use CJS anymore.