# How Cypress Handles TypeScript Compilation and ESM Modules in Spec Files

> Discover how Cypress seamlessly handles TypeScript compilation and ESM modules in spec files using esbuild and a custom Node.js loader for efficient testing.

- Repository: [Cypress.io/cypress](https://github.com/cypress-io/cypress)
- Tags: internals
- Published: 2026-07-12

---

**Cypress compiles TypeScript spec files on-the-fly using esbuild and executes ESM modules via a custom Node.js loader that injects Cypress globals, eliminating the need for manual pre-processing or separate tsc builds.**

The `cypress-io/cypress` repository provides built-in support for modern JavaScript and TypeScript workflows. When you run `cypress open` or `cypress run`, the test runner automatically handles **TypeScript compilation and ESM modules** in spec files without requiring external pre-processors. This is achieved through a sophisticated bundling pipeline that detects file extensions and applies the appropriate transformation strategy.

## TypeScript Compilation Pipeline

### Detection and Routing

When Cypress discovers a spec file, it checks the file extension in `@packages/driver/src/bundler/loader.ts`. Files ending in `.ts` or `.tsx` are routed to the TypeScript transformation pipeline, while `.js` files pass through unchanged. This orchestration happens on-the-fly as the test runner scans the `specPattern` defined in your configuration.

### esbuild Transformation

The actual compilation happens in `@packages/driver/src/bundler/esbuild-transformer.ts`. This transformer invokes `esbuild.buildSync()` with the `tsx` loader and targets ES2020:

```typescript
esbuild.buildSync({
  entryPoints: [specPath],
  loader: 'tsx',
  target: ['es2020']
})

```

This produces JavaScript output with JSX handling and attached source maps, all without invoking `tsc` directly. The transformation occurs entirely in memory, ensuring fast cold-start times for large test suites.

### Source Map Integration

The `@packages/driver/src/bundler/source-map-handler.ts` file attaches the generated source maps to the bundle. This ensures that stack traces in the Cypress runner point back to the original TypeScript source lines rather than the compiled JavaScript output.

## ESM Module Support

### Native ESM Loading

For spec files using the `.mjs` extension or located within packages declaring `"type": "module"`, Cypress utilizes a custom ESM loader implemented in `@packages/driver/src/bundler/esm-loader.ts`. This wrapper around Node's native ESM loader rewrites `import.meta` statements and ensures that Cypress-specific globals such as `cy` and `Cypress` are properly injected into the module scope before execution.

### CommonJS Interoperability

When an ESM spec imports a CommonJS module, the `@packages/driver/src/bundler/esm-interop.ts` shim provides a default export mirroring TypeScript's `"esModuleInterop": true` behavior. This allows you to write `import foo from './cjs-module'` without requiring namespace imports or `.default` accessors.

### Execution Flow

After transformation, the bundle is handed to `@packages/rewriter`, which injects Cypress command hooks into the compiled output. The rewritten bundle is then sent to the browser runner where it executes as part of the test run.

## Configuration and Usage

You do not need to configure a custom pre-processor to use TypeScript or ESM in Cypress. Simply place your spec files with the appropriate extensions:

```typescript
// cypress.config.ts
import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    specPattern: '**/*.spec.{js,ts,tsx,mjs}',
  },
})

```

TypeScript spec example:

```typescript
// cypress/e2e/example.spec.ts
describe('TypeScript spec', () => {
  it('uses type safety', () => {
    const value: number = 42
    expect(value).to.equal(42)
  })
})

```

ESM spec example:

```javascript
// cypress/e2e/example.mjs
import { expect } from 'chai'

describe('ESM spec', () => {
  it('runs a test', () => {
    expect(true).to.be.true
  })
})

```

## Summary

- **File extension detection** in `@packages/driver/src/bundler/loader.ts` determines whether to apply TypeScript compilation or ESM loading
- **esbuild** handles TypeScript transformation with `tsx` loader targeting ES2020, eliminating the need for separate `tsc` builds
- **Source maps** are automatically attached via `@packages/driver/src/bundler/source-map-handler.ts` to preserve original line numbers in stack traces
- **Native ESM** spec files are processed through a custom loader in `@packages/driver/src/bundler/esm-loader.ts` that rewrites `import.meta` and injects Cypress globals
- **CommonJS interoperability** is provided by `@packages/driver/src/bundler/esm-interop.ts`, supporting standard ES module import syntax

## Frequently Asked Questions

### Does Cypress require a separate TypeScript configuration file?

No. Cypress does not require a [`tsconfig.json`](https://github.com/cypress-io/cypress/blob/main/tsconfig.json) to compile TypeScript spec files. The built-in esbuild transformer in `@packages/driver/src/bundler/esbuild-transformer.ts` handles compilation internally using default settings optimized for test files. However, if you provide a [`tsconfig.json`](https://github.com/cypress-io/cypress/blob/main/tsconfig.json), Cypress will respect it for type-checking purposes in your IDE while still using esbuild for runtime compilation.

### Can I use ES modules in my Cypress spec files without experimental flags?

Yes. As implemented in `cypress-io/cypress`, you can use `.mjs` extensions or set `"type": "module"` in your [`package.json`](https://github.com/cypress-io/cypress/blob/main/package.json) to enable native ESM support. The custom loader in `@packages/driver/src/bundler/esm-loader.ts` handles the module execution without requiring Node's experimental ESM flags, as it integrates directly with the Node.js module system.

### How does Cypress handle source maps for TypeScript error stack traces?

Cypress automatically generates and attaches source maps during the esbuild transformation phase. The `@packages/driver/src/bundler/source-map-handler.ts` ensures that when an error occurs in your TypeScript spec, the stack trace displayed in the Cypress runner points to the exact line in your `.ts` source file, not the compiled JavaScript output.

### What happens when an ESM spec imports a CommonJS module?

The `@packages/driver/src/bundler/esm-interop.ts` module provides a compatibility layer that mimics TypeScript's `esModuleInterop` behavior. When an ESM spec imports a CommonJS module, this shim automatically provides the `module.exports` as a default export, allowing you to use standard `import` syntax without destructuring or accessing `.default` explicitly.