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.tsdetermines whether to apply TypeScript compilation or ESM loading - esbuild handles TypeScript transformation with
tsxloader targeting ES2020, eliminating the need for separatetscbuilds - Source maps are automatically attached via
@packages/driver/src/bundler/source-map-handler.tsto preserve original line numbers in stack traces - Native ESM spec files are processed through a custom loader in
@packages/driver/src/bundler/esm-loader.tsthat rewritesimport.metaand 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →