Skip to content

Support for Clean Running of .ts ESM Entry Points with ts-node/esm #2158

@paullewallencom

Description

@paullewallencom

Environment Information

  • Node.js version: 20.19.1
  • ts-node version: ts-node@10.9.2
  • TypeScript version: typescript@5.7.3
  • OS: macOS

tsconfig.json settings:

{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ES2022",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"skipLibCheck": true
}
}

Summary of the Problem

When launching a TypeScript .ts ESM entry point directly using:

node --loader ts-node/esm run-mocha.ts

Node.js throws:

Error [ERR_REQUIRE_CYCLE_MODULE]: Cannot require() ES Module run-mocha.ts in a cycle

This happens even when all imports inside the .ts file are using await import() dynamically (no static imports), and the tsconfig.json uses "module": "NodeNext" and "moduleResolution": "NodeNext" correctly.

Steps to Reproduce

  1. Clone this reproduction repository:
    👉 https://github.com/paullewallencom/issue-0000

  2. Install dependencies:

npm install

  1. Run the test script:

npm run test

  1. Observe the cycle error crashing immediately.

What I Expected to Happen

  • I expected ts-node/esm to properly defer static analysis or execution such that .ts ESM entrypoints could be executed cleanly without needing a .mjs bootstrap workaround.
  • I expected dynamic await import('mocha') to avoid early cycle detection issues.

Actual Behavior

  • Node detects a require cycle in the .ts entrypoint itself, even when using ts-node/esm.
  • The only way to avoid the crash is to create a separate .mjs bootstrap file (e.g., run-mocha-bootstrap.mjs) and dynamically import the .ts file from there.

Notes

  • I understand that Node's ESM loader is strict by design, but it seems like ts-node could provide a cleaner or more automated approach for .ts ESM entrypoints.
  • I am happy to help contribute to documentation or potential enhancements if needed.
  • Thank you very much for maintaining ts-node — it’s an incredibly valuable tool for TypeScript developers!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions