Skip to content
Latchkey

tsx "ExperimentalWarning" / Loader Error - Fix Running TS as ESM in CI

tsx runs TypeScript directly via a Node loader. Failures in CI are usually a deprecated --loader invocation, a __dirname/module reference in an ESM context, or tsx not being registered before the entry file runs.

What this error means

Running a TS entry with tsx fails or warns: --experimental-loader/--loader is deprecated, module is not defined in ES module scope, or ReferenceError: __dirname is not defined. It often appears after a Node major bump changed loader semantics.

tsx output
(node:123) ExperimentalWarning: --experimental-loader may be removed
# or
ReferenceError: __dirname is not defined in ES module scope
# or
Error: Cannot find module 'tsx/cjs'

Common causes

Deprecated loader invocation

Older node --loader tsx file.ts forms are deprecated on newer Node. The current entrypoint is tsx file.ts (or node --import tsx).

CJS globals used in an ESM context

In ESM, __dirname/__filename/module are undefined. Code (or a dep) assuming CommonJS globals throws under the ESM loader.

How to fix it

Use the current tsx entrypoint

Invoke tsx directly, or register it with --import on modern Node.

Terminal
# run TypeScript directly
npx tsx src/index.ts
# or register via the import flag (Node 20+)
node --import tsx src/index.ts

Fix CJS-global usage under ESM

  1. Replace __dirname with path.dirname(fileURLToPath(import.meta.url)).
  2. Avoid module/require in ESM files (use dynamic import).
  3. Match tsx and Node versions so the loader API is consistent.

How to prevent it

  • Use tsx <file> or node --import tsx, not legacy --loader.
  • Avoid CommonJS globals in ESM source.
  • Keep tsx and Node versions aligned.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →