Repository navigation
CommonJS globals permitted for ES module builds with no compiler error.Β #58658
Description
Activity
knightedcodemonkey commented
on May 25, 2024 AuthorMore actionsIf when targeting
"type": "module"and instead usingconsole.log(globalThis.__dirname)there is still no compiler error and node does not throw the ReferenceError about__dirnamesince it is now a property ofglobalThis. However, if instead you doconsole.log(require.main), you still get no compile error, and node does throw the ReferenceError aboutrequirenot being defined is ES module scope.What is the recommended usage pattern here? I know people when writing ES modules often do something like this
const __dirname = dirname(fileURLToPath(import.meta.url))
This is more or less expected as these are defined by
@types/nodeas global, and there's no way to say "declare global but only if the current file is CJS".knightedcodemonkey commented
on May 30, 2024 AuthorMore actionsThanks for the feedback.
I guess I'm wondering why the compiler doesn't introduce some heuristic to at least warn about using CJS globals when targeting ES module output based on the
modulesetting and thetypevalue from the package.json file?Reacted by Josh HaydenTS does not have a concept of "warnings", though a "suggestion" diagnostic is close. It's also non-trivial because people frequently write ESM with an assumption about transpilation (but maybe want to
typeof require != "undefined"etc), and we also consider any function namedrequireto berequirefor purposes of module resolution.There are lint rules that can be strict about this, if you fit within their guidelines.
knightedcodemonkey commented
on Jun 4, 2024 AuthorMore actionsAnother interesting thing about the compiler behavior is
Given:
cjs.ts
exports.foo = 'bar';
package.json
"type": "module"
file.ts
import './cjs.js'
With
moduleofNodeNextthe compiler does not convert theexports.footo aexport const foo. Instead you getexports.foo = 'bar'; export {};
Also, there is no compiler error, only a runtime error.
Is this somehow related to #56678?
Reacted by Toni Villena- addedSuggestionAn idea for TypeScriptAn idea for TypeScriptAwaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureThis means we'd like to hear from more people who would be helped by this feature
on Jun 13, 2024 I am working on converting a Node.js TypeScript project from CJS to ESM and noticed a related issue with a dynamic
require.Using TypeScript, I'm relying heavily on
tscto know what code changes I need to make. I have a .ts file that has no errors when I compile withtsc. So I was surprised when I executed the .ts file with withtsxthat failed with this error at runtime:ReferenceError: require is not defined in ES module scope, you can use import insteadI'm expecting that
tscshould report this error.This can be replicated with this tconfig.json file:
{ "extends": "@tsconfig/node20/tsconfig.json" }and using
{ type: "module" }in package.json.Node.js v20.17.0
typescript 5.6.2
@tsconfig/node20 20.1.4This would be a very useful feature.
knightedcodemonkey commented
on Dec 28, 2025 AuthorMore actions@knighted/duelmitigates this issue by running@knighted/moduleas a pre-tsc transform when you pass --modules.@knighted/duelcopies the sources, rewrites module globals/specifiers for the dual target, and only then runstsc, so the checker no longer complains aboutimport.metain CJS or__dirnamein ESM.If you're not using duel, you can apply
@knighted/moduledirectly to your sources beforetsc(e.g., use a glob to collect your source files, then transform them in placetransform({ target: 'commonjs' | 'module', inPlace: true })) to achieve the same effect.I would consider this issue fixed at the userland level in
@knighted/[email protected].The smoking gun that
@knighted/modulefixes thetsccompiler limitations (the ESM leak): https://github.com/knightedcodemonkey/module/blob/main/test/cli.ts#L191-L261. Would be better if the solution wasn't needed in userland.knightedcodemonkey commented
on Jan 1, 2026 AuthorMore actionsHonestly, this lack of a multi-emit mode with
tscis infuriating. If you want to be a superset of JavaScript while support Node.js resolution, you need to step up your game or be pushed by the wayside.Reacted by Kevin Aminzadeh
π Search Terms
nodenext, module, __dirname
π Version & Regression Information
Version 5.4.5
β― Playground Link
No response
π» Code
package.json
tsconfig.json
{ "compilerOptions": { "module": "NodeNext" }, "include": ["src"] }file.ts
π Actual behavior
No compiler error, but this causes a runtime error in Node:
π Expected behavior
That the compiler issues an error similar to the inverse situation.
For example, when targeting CommonJS:
package.json
file.ts
The compiler issues the following error:
Additional information about the issue
Perhaps there is a good reason for this that I'm not understanding, but I would think
tscshould issue an error or warning for any syntax that would produce a runtime error.Here is a more complete example: https://github.com/knightedcodemonkey/tsc-module-globals
npm installnpm run esm(note no compile error but the output causes a runtime error)npm run cjs(note there is a compile error)