How Cypress Handles TypeScript Compilation and ESM Modules in Spec Files

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:

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:

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

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

TypeScript spec example:

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

ESM spec example:

// 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 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, 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →