Skip to content

CommonJS globals permitted for ES module builds with no compiler error.Β #58658

Description

πŸ”Ž Search Terms

nodenext, module, __dirname

πŸ•— Version & Regression Information

Version 5.4.5

⏯ Playground Link

No response

πŸ’» Code

package.json

"type": "module"

tsconfig.json

{
  "compilerOptions": {
    "module": "NodeNext"
  },
  "include": ["src"]
}

file.ts

console.log(__dirname)

πŸ™ Actual behavior

No compiler error, but this causes a runtime error in Node:

ReferenceError: __dirname is not defined in ES module scope

πŸ™‚ Expected behavior

That the compiler issues an error similar to the inverse situation.

For example, when targeting CommonJS:

package.json

"type": "commonjs"

file.ts

console.log(import.meta.dirname)

The compiler issues the following error:

error TS1470: The 'import.meta' meta-property is not allowed in files which will build into CommonJS output.

Additional information about the issue

Perhaps there is a good reason for this that I'm not understanding, but I would think tsc should 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 install
  • npm run esm (note no compile error but the output causes a runtime error)
  • npm run cjs (note there is a compile error)

Activity

  1. knightedcodemonkey commented on May 25, 2024

    @knightedcodemonkey
    Author

    If when targeting "type": "module" and instead using console.log(globalThis.__dirname) there is still no compiler error and node does not throw the ReferenceError about __dirname since it is now a property of globalThis. However, if instead you do console.log(require.main), you still get no compile error, and node does throw the ReferenceError about require not 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))
  2. jakebailey commented on May 29, 2024

    @jakebailey
    Member

    This is more or less expected as these are defined by @types/node as global, and there's no way to say "declare global but only if the current file is CJS".

  3. knightedcodemonkey commented on May 30, 2024

    @knightedcodemonkey
    Author

    Thanks 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 module setting and the type value from the package.json file?

  4. jakebailey commented on May 30, 2024

    @jakebailey
    Member

    TS 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 named require to be require for purposes of module resolution.

    There are lint rules that can be strict about this, if you fit within their guidelines.

  5. knightedcodemonkey commented on Jun 4, 2024

    @knightedcodemonkey
    Author

    Another interesting thing about the compiler behavior is

    Given:

    cjs.ts

    exports.foo = 'bar';

    package.json

    "type": "module"

    file.ts

    import './cjs.js'

    With module of NodeNext the compiler does not convert the exports.foo to a export const foo. Instead you get

    exports.foo = 'bar';
    export {};

    Also, there is no compiler error, only a runtime error.

    Is this somehow related to #56678?

  6. joshden commented on Sep 26, 2024

    @joshden

    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 tsc to know what code changes I need to make. I have a .ts file that has no errors when I compile with tsc. So I was surprised when I executed the .ts file with with tsx that failed with this error at runtime: ReferenceError: require is not defined in ES module scope, you can use import instead

    I'm expecting that tsc should 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.4

  7. jroru commented on Nov 14, 2024

    @jroru

    This would be a very useful feature.

  8. knightedcodemonkey commented on Dec 28, 2025

    @knightedcodemonkey
    Author

    @knighted/duel mitigates this issue by running @knighted/module as a pre-tsc transform when you pass --modules. @knighted/duel copies the sources, rewrites module globals/specifiers for the dual target, and only then runs tsc, so the checker no longer complains about import.meta in CJS or __dirname in ESM.

    If you're not using duel, you can apply @knighted/module directly to your sources before tsc (e.g., use a glob to collect your source files, then transform them in place transform({ 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/module fixes the tsc compiler 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.

  9. knightedcodemonkey commented on Jan 1, 2026

    @knightedcodemonkey
    Author

    Honestly, this lack of a multi-emit mode with tsc is 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions