{
  "doc": {
    "id": "api/node-events/preprocessors-api",
    "title": "Preprocessors API",
    "description": "Prepare support and spec files for the browser with preprocessors.",
    "section": "api",
    "source_path": "/llm/markdown/api/node-events/preprocessors-api.md",
    "version": "8d4bb7c9d1af182786c46b47bac667515b9e5d39",
    "updated_at": "2026-08-07T13:17:53.464Z",
    "headings": [
      {
        "id": "api/node-events/preprocessors-api#preprocessors-api",
        "text": "Preprocessors API",
        "level": 1
      },
      {
        "id": "api/node-events/preprocessors-api#examples",
        "text": "Examples",
        "level": 2
      },
      {
        "id": "api/node-events/preprocessors-api#defaults",
        "text": "Defaults",
        "level": 2
      },
      {
        "id": "api/node-events/preprocessors-api#resolving-import-path-aliases",
        "text": "Resolving import path aliases",
        "level": 2
      },
      {
        "id": "api/node-events/preprocessors-api#excluding-files-from-being-watched",
        "text": "Excluding files from being watched",
        "level": 2
      },
      {
        "id": "api/node-events/preprocessors-api#usage",
        "text": "Usage",
        "level": 2
      },
      {
        "id": "api/node-events/preprocessors-api#the-callback-function-should-return-one-of-the-following",
        "text": "The callback function should return one of the following:",
        "level": 3
      },
      {
        "id": "api/node-events/preprocessors-api#file-object",
        "text": "File object",
        "level": 2
      },
      {
        "id": "api/node-events/preprocessors-api#file-events",
        "text": "File events",
        "level": 2
      },
      {
        "id": "api/node-events/preprocessors-api#receiving-close-event",
        "text": "Receiving 'close' event",
        "level": 3
      },
      {
        "id": "api/node-events/preprocessors-api#sending-rerun-event",
        "text": "Sending 'rerun' event",
        "level": 3
      },
      {
        "id": "api/node-events/preprocessors-api#source-maps",
        "text": "Source maps",
        "level": 2
      },
      {
        "id": "api/node-events/preprocessors-api#publishing",
        "text": "Publishing",
        "level": 2
      }
    ]
  },
  "chunks": [
    {
      "id": "api/node-events/preprocessors-api#examples",
      "doc_id": "api/node-events/preprocessors-api",
      "heading": "Examples",
      "heading_level": 2,
      "content_markdown": "## Examples\n\nWe've created some example preprocessors as reference for you to look at. These are fully functioning preprocessors.\n\nThe code contains comments that explain how it utilizes the preprocessor API.\n\n*   [webpack preprocessor](https://github.com/cypress-io/cypress/tree/develop/npm/webpack-preprocessor)\n*   [Browserify preprocessor](https://github.com/cypress-io/cypress-browserify-preprocessor)\n",
      "section": "api",
      "anchors": [
        "examples"
      ],
      "path": "/llm/json/chunked/api/node-events/preprocessors-api.json",
      "token_estimate": 49
    },
    {
      "id": "api/node-events/preprocessors-api#defaults",
      "doc_id": "api/node-events/preprocessors-api",
      "heading": "Defaults",
      "heading_level": 2,
      "content_markdown": "## Defaults\n\nBy default, Cypress comes packaged with the **webpack preprocessor** already installed.\n\nThe webpack preprocessor handles:\n\n*   ES2015 and JSX via Babel\n*   TypeScript\n*   CoffeeScript `1.x.x`\n*   Watching and caching files\n\nAre you looking to change the **default options** for webpack?\n\nIf you already use webpack in your project, you can pass in your webpack config as [shown here](https://github.com/cypress-io/cypress/tree/develop/npm/webpack-preprocessor#options).\n\nIf you don't use webpack in your project or would like to keep the majority of the default options, you can [modify the default options](https://github.com/cypress-io/cypress/tree/develop/npm/webpack-preprocessor#modifying-default-options). Editing the options allows you to do things like:\n\n*   Add your own Babel plugins\n*   Modify options for TypeScript compilation\n*   Add support for CoffeeScript `2.x.x`\n",
      "section": "api",
      "anchors": [
        "defaults"
      ],
      "path": "/llm/json/chunked/api/node-events/preprocessors-api.json",
      "token_estimate": 151
    },
    {
      "id": "api/node-events/preprocessors-api#resolving-import-path-aliases",
      "doc_id": "api/node-events/preprocessors-api",
      "heading": "Resolving import path aliases",
      "heading_level": 2,
      "content_markdown": "## Resolving import path aliases\n\nIf your spec or support files import modules through path aliases (for example `import { user } from '@/fixtures/user'`), the preprocessor that bundles those files must know how to resolve the alias, otherwise the spec fails to compile with a \"module not found\" error.\n\nThe default webpack preprocessor does **not** read aliases from your `package.json` `_moduleAliases` field, and it does **not** automatically apply the `compilerOptions.paths` from your `tsconfig.json` (those are a TypeScript type-checking feature, not a bundler feature). You need to tell the bundler about the aliases explicitly.\n\nThe most direct option is to add a `resolve.alias` entry to the webpack config you pass to the preprocessor:\n\n*   cypress.config.js\n*   cypress.config.ts\n\n```\nconst { defineConfig } = require('cypress')module.exports = defineConfig({  // setupNodeEvents can be defined in either  // the e2e or component configuration  e2e: {    setupNodeEvents(on, config) {      const webpackPreprocessor = require('@cypress/webpack-preprocessor')      const path = require('path')      const options = webpackPreprocessor.defaultOptions      options.webpackOptions.resolve = {        ...options.webpackOptions.resolve,        alias: {          '@': path.resolve(__dirname, '../../src'),        },      }      on('file:preprocessor', webpackPreprocessor(options))    },  },})\n```\n\n```\nimport { defineConfig } from 'cypress'export default defineConfig({  // setupNodeEvents can be defined in either  // the e2e or component configuration  e2e: {    setupNodeEvents(on, config) {      const webpackPreprocessor = require('@cypress/webpack-preprocessor')      const path = require('path')      const options = webpackPreprocessor.defaultOptions      options.webpackOptions.resolve = {        ...options.webpackOptions.resolve,        alias: {          '@': path.resolve(__dirname, '../../src'),        },      }      on('file:preprocessor', webpackPreprocessor(options))    },  },})\n```\n\nTo reuse the aliases already declared under `compilerOptions.paths` in your `tsconfig.json` instead of restating them, add [`tsconfig-paths-webpack-plugin`](https://www.npmjs.com/package/tsconfig-paths-webpack-plugin) to `resolve.plugins`:\n\n*   cypress.config.js\n*   cypress.config.ts\n\n```\nconst { defineConfig } = require('cypress')module.exports = defineConfig({  // setupNodeEvents can be defined in either  // the e2e or component configuration  e2e: {    setupNodeEvents(on, config) {      const webpackPreprocessor = require('@cypress/webpack-preprocessor')      const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')      const options = webpackPreprocessor.defaultOptions      options.webpackOptions.resolve = {        ...options.webpackOptions.resolve,        plugins: [new TsconfigPathsPlugin()],      }      on('file:preprocessor', webpackPreprocessor(options))    },  },})\n```\n\n```\nimport { defineConfig } from 'cypress'export default defineConfig({  // setupNodeEvents can be defined in either  // the e2e or component configuration  e2e: {    setupNodeEvents(on, config) {      const webpackPreprocessor = require('@cypress/webpack-preprocessor')      const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')      const options = webpackPreprocessor.defaultOptions      options.webpackOptions.resolve = {        ...options.webpackOptions.resolve,        plugins: [new TsconfigPathsPlugin()],      }      on('file:preprocessor', webpackPreprocessor(options))    },  },})\n```\n\nThis page covers aliases in **end-to-end** spec and support files, which are bundled by the preprocessor. For **component testing**, aliases are resolved by the dev server's Vite or Webpack config instead. See [Resolving import path aliases](/llm/markdown/app/component-testing/component-framework-configuration.md#Resolving-import-path-aliases).\n",
      "section": "api",
      "anchors": [
        "resolving-import-path-aliases"
      ],
      "path": "/llm/json/chunked/api/node-events/preprocessors-api.json",
      "token_estimate": 519
    },
    {
      "id": "api/node-events/preprocessors-api#excluding-files-from-being-watched",
      "doc_id": "api/node-events/preprocessors-api",
      "heading": "Excluding files from being watched",
      "heading_level": 2,
      "content_markdown": "## Excluding files from being watched\n\nCertain tools generate files as a side effect of a build (for example, `*.d.ts` files emitted by `css-modules-typescript-loader`, or `*.hot-update.js` HMR files). If Cypress watches those generated files and they are re-emitted on each build, you can end up in an **infinite reload loop**: Cypress detects a change → rebuilds → the tool generates new files → Cypress detects another change → …\n\nTo break this cycle, pass a `watchOptions.ignored` glob pattern (or array of patterns) to the webpack preprocessor. The value is forwarded directly to webpack's file watcher, which uses [`anymatch`](https://github.com/micromatch/anymatch) internally and therefore accepts glob strings, regular expressions, or functions.\n\n*   cypress.config.js\n*   cypress.config.ts\n\n```\nconst { defineConfig } = require('cypress')module.exports = defineConfig({  // setupNodeEvents can be defined in either  // the e2e or component configuration  e2e: {    setupNodeEvents(on, config) {      const webpackPreprocessor = require('@cypress/webpack-preprocessor')      on(        'file:preprocessor',        webpackPreprocessor({          watchOptions: {            // Ignore TypeScript declaration files generated during builds            ignored: '**/*.d.ts',          },        })      )    },  },})\n```\n\n```\nimport { defineConfig } from 'cypress'export default defineConfig({  // setupNodeEvents can be defined in either  // the e2e or component configuration  e2e: {    setupNodeEvents(on, config) {      const webpackPreprocessor = require('@cypress/webpack-preprocessor')      on(        'file:preprocessor',        webpackPreprocessor({          watchOptions: {            // Ignore TypeScript declaration files generated during builds            ignored: '**/*.d.ts',          },        })      )    },  },})\n```\n\nYou can also provide an array to ignore several patterns at once:\n\n*   cypress.config.js\n*   cypress.config.ts\n\n```\nconst { defineConfig } = require('cypress')module.exports = defineConfig({  // setupNodeEvents can be defined in either  // the e2e or component configuration  e2e: {    setupNodeEvents(on, config) {      const webpackPreprocessor = require('@cypress/webpack-preprocessor')      on(        'file:preprocessor',        webpackPreprocessor({          watchOptions: {            ignored: ['**/*.d.ts', '**/*.hot-update.js'],          },        })      )    },  },})\n```\n\n```\nimport { defineConfig } from 'cypress'export default defineConfig({  // setupNodeEvents can be defined in either  // the e2e or component configuration  e2e: {    setupNodeEvents(on, config) {      const webpackPreprocessor = require('@cypress/webpack-preprocessor')      on(        'file:preprocessor',        webpackPreprocessor({          watchOptions: {            ignored: ['**/*.d.ts', '**/*.hot-update.js'],          },        })      )    },  },})\n```\n\nFor more details on the available `watchOptions` values, see [webpack's watch configuration docs](https://webpack.js.org/configuration/watch/#watchoptionsignored).\n\nThe `excludeSpecPattern` configuration option hides files from the **list of specs** shown in the Cypress UI — it does not affect which files the preprocessor watches. Use `watchOptions.ignored` (shown above) when you need to prevent specific files from triggering a test rerun.\n",
      "section": "api",
      "anchors": [
        "excluding-files-from-being-watched"
      ],
      "path": "/llm/json/chunked/api/node-events/preprocessors-api.json",
      "token_estimate": 503
    },
    {
      "id": "api/node-events/preprocessors-api#usage",
      "doc_id": "api/node-events/preprocessors-api",
      "heading": "Usage",
      "heading_level": 2,
      "content_markdown": "## Usage\n\n⚠️ This code is part of the [setupNodeEvents](/llm/markdown/app/plugins/plugins-guide.md#Using-a-plugin) function and thus executes in the Node environment. You cannot call `Cypress` or `cy` commands in this function, but you do have the direct access to the file system and the rest of the operating system.\n\nTo use a preprocessor, you should bind to the `file:preprocessor` event in your [setupNodeEvents](/llm/markdown/app/plugins/plugins-guide.md#Using-a-plugin) function:\n\n*   cypress.config.js\n*   cypress.config.ts\n\n```\nconst { defineConfig } = require('cypress')module.exports = defineConfig({  // setupNodeEvents can be defined in either  // the e2e or component configuration  e2e: {    setupNodeEvents(on, config) {      on('file:preprocessor', (file) => {        // ...      })    },  },})\n```\n\n```\nimport { defineConfig } from 'cypress'export default defineConfig({  // setupNodeEvents can be defined in either  // the e2e or component configuration  e2e: {    setupNodeEvents(on, config) {      on('file:preprocessor', (file) => {        // ...      })    },  },})\n```\n\n### The callback function should return one of the following:\n\n*   A promise\\* that eventually resolves the path to the **built file**\\*\\*.\n*   A promise\\* that eventually rejects with an error that occurred during processing.\n\n\\* The promise should resolve only after the file has completed writing to disk. The promise resolving is a signal that the file is ready to be served to the browser.\n\n* * *\n\n\\*\\* The built file is the file that is created by the preprocessor that will eventually be served to the browser.\n\nIf, for example, the source file is `spec.coffee`, the preprocessor should:\n\n1.  Compile the CoffeeScript into JavaScript `spec.js`\n2.  Write that JavaScript file to disk (example: `/Users/foo/tmp/spec.js`)\n3.  Resolve with the absolute path to that file: `/Users/foo/tmp/spec.js`\n\nThis callback function can and _will_ be called multiple times with the same `filePath`.\n\nThe callback function is called any time a file is requested by the browser. This happens on each run of the tests.\n\nMake sure not to start a new watcher each time it is called. Instead, cache the watcher and, on subsequent calls, return a promise that resolves when the latest version of the file has been processed.\n",
      "section": "api",
      "anchors": [
        "usage"
      ],
      "path": "/llm/json/chunked/api/node-events/preprocessors-api.json",
      "token_estimate": 451
    },
    {
      "id": "api/node-events/preprocessors-api#the-callback-function-should-return-one-of-the-following",
      "doc_id": "api/node-events/preprocessors-api",
      "heading": "The callback function should return one of the following:",
      "heading_level": 3,
      "content_markdown": "### The callback function should return one of the following:\n\n*   A promise\\* that eventually resolves the path to the **built file**\\*\\*.\n*   A promise\\* that eventually rejects with an error that occurred during processing.\n\n\\* The promise should resolve only after the file has completed writing to disk. The promise resolving is a signal that the file is ready to be served to the browser.\n\n* * *\n\n\\*\\* The built file is the file that is created by the preprocessor that will eventually be served to the browser.\n\nIf, for example, the source file is `spec.coffee`, the preprocessor should:\n\n1.  Compile the CoffeeScript into JavaScript `spec.js`\n2.  Write that JavaScript file to disk (example: `/Users/foo/tmp/spec.js`)\n3.  Resolve with the absolute path to that file: `/Users/foo/tmp/spec.js`\n\nThis callback function can and _will_ be called multiple times with the same `filePath`.\n\nThe callback function is called any time a file is requested by the browser. This happens on each run of the tests.\n\nMake sure not to start a new watcher each time it is called. Instead, cache the watcher and, on subsequent calls, return a promise that resolves when the latest version of the file has been processed.\n",
      "section": "api",
      "anchors": [
        "the-callback-function-should-return-one-of-the-following"
      ],
      "path": "/llm/json/chunked/api/node-events/preprocessors-api.json",
      "token_estimate": 265
    },
    {
      "id": "api/node-events/preprocessors-api#file-object",
      "doc_id": "api/node-events/preprocessors-api",
      "heading": "File object",
      "heading_level": 2,
      "content_markdown": "## File object\n\nThe `file` object passed to the callback function has the following properties:\n\n| Property | Description |\n| --- | --- |\n| `filePath` | The full path to the source file. |\n| `outputPath` | The suggested path for saving the preprocessed file to disk. This is unique to the source file. A preprocessor can choose to write the file elsewhere, but Cypress automatically provides you this value as a convenient default. |\n| `shouldWatch` | A boolean indicating whether the preprocessor should watch for file changes or not. |\n",
      "section": "api",
      "anchors": [
        "file-object"
      ],
      "path": "/llm/json/chunked/api/node-events/preprocessors-api.json",
      "token_estimate": 125
    },
    {
      "id": "api/node-events/preprocessors-api#file-events",
      "doc_id": "api/node-events/preprocessors-api",
      "heading": "File events",
      "heading_level": 2,
      "content_markdown": "## File events\n\nThe `file` object passed to the callback function is an [Event Emitter](https://nodejs.org/api/events.html#events_class_eventemitter).\n\n### Receiving 'close' event\n\nWhen the running spec, the project, or the browser is closed while running tests, the `close` event will be emitted. The preprocessor should do any necessary cleanup in this function, like closing the watcher when watching.\n\n```\n// exampleconst watcher = fs.watch(filePath /* ... */)file.on('close', () => {  watcher.close()})\n```\n\n### Sending 'rerun' event\n\nIf watching for file changes, emit `rerun` after a file has finished being processed to let Cypress know to rerun the tests.\n\n```\n// examplefs.watch(filePath, () => {  file.emit('rerun')})\n```\n",
      "section": "api",
      "anchors": [
        "file-events"
      ],
      "path": "/llm/json/chunked/api/node-events/preprocessors-api.json",
      "token_estimate": 137
    },
    {
      "id": "api/node-events/preprocessors-api#receiving-close-event",
      "doc_id": "api/node-events/preprocessors-api",
      "heading": "Receiving 'close' event",
      "heading_level": 3,
      "content_markdown": "### Receiving 'close' event\n\nWhen the running spec, the project, or the browser is closed while running tests, the `close` event will be emitted. The preprocessor should do any necessary cleanup in this function, like closing the watcher when watching.\n\n```\n// exampleconst watcher = fs.watch(filePath /* ... */)file.on('close', () => {  watcher.close()})\n```\n",
      "section": "api",
      "anchors": [
        "receiving-close-event"
      ],
      "path": "/llm/json/chunked/api/node-events/preprocessors-api.json",
      "token_estimate": 72
    },
    {
      "id": "api/node-events/preprocessors-api#sending-rerun-event",
      "doc_id": "api/node-events/preprocessors-api",
      "heading": "Sending 'rerun' event",
      "heading_level": 3,
      "content_markdown": "### Sending 'rerun' event\n\nIf watching for file changes, emit `rerun` after a file has finished being processed to let Cypress know to rerun the tests.\n\n```\n// examplefs.watch(filePath, () => {  file.emit('rerun')})\n```\n",
      "section": "api",
      "anchors": [
        "sending-rerun-event"
      ],
      "path": "/llm/json/chunked/api/node-events/preprocessors-api.json",
      "token_estimate": 45
    },
    {
      "id": "api/node-events/preprocessors-api#source-maps",
      "doc_id": "api/node-events/preprocessors-api",
      "heading": "Source maps",
      "heading_level": 2,
      "content_markdown": "## Source maps\n\nCypress utilizes source maps to enhance the error experience. Stack traces are translated so that your source files are shown instead of the generated file that is loaded by the browser. This also enables displaying code frames. Without inline source maps, you will not see code frames.\n\nBy default, Cypress will include an inline source map in your spec file, so you will get the most out of the error experience. If you [modify the preprocessor](/llm/markdown/api/node-events/preprocessors-api.md#Usage), ensure that inline source maps are enabled to get the same experience. Some examples of this include:\n\n*   With webpack and the [webpack preprocessor](https://github.com/cypress-io/cypress/tree/develop/npm/webpack-preprocessor), set [the `devtool` option](https://webpack.js.org/configuration/devtool/) to `inline-source-map`.\n*   With esbuild and the [esbuild preprocessor](https://github.com/bahmutov/cypress-esbuild-preprocessor/tree/main), set [the `sourcemap` option](https://esbuild.github.io/api/#sourcemap) to `inline` when [creating the bundler](https://github.com/bahmutov/cypress-esbuild-preprocessor#esbuild-options).\n*   With cucumber and the [cucumber preprocessor](https://github.com/badeball/cypress-cucumber-preprocessor/tree/master) third party bundlers are used to bundle the code.\n    *   For esbuild and the [esbuild preprocessor](https://github.com/bahmutov/cypress-esbuild-preprocessor/tree/main), set [the `sourcemap` option](https://esbuild.github.io/api/#sourcemap) to `inline` when [creating the bundler](https://github.com/bahmutov/cypress-esbuild-preprocessor#esbuild-options). Note: the cucumber preprocessor documentation for the esbuild bundler describes an experimental option: `prettySourceMaps`, but this option is buggy and setting source maps on the esbuild bundler directly works better.\n    *   The other bundlers all default their source maps appropriately.\n\nIf using [TypeScript](https://www.typescriptlang.org/) with any custom preprocessor, you will want to make sure that the [TypeScript](https://www.typescriptlang.org/) compiler is generating source maps. This can be done by setting the [`sourceMap`](https://www.typescriptlang.org/tsconfig/#sourceMap) option in your `tsconfig.json` to `true`. The [`inlineSourceMap`](https://www.typescriptlang.org/tsconfig/#inlineSourceMap) option is **not** recommended as it does not provide an accurate code frame. Please see our recommended [tsconfig.json](/llm/markdown/app/tooling/typescript-support.md#Configure-tsconfigjson) as a reference.\n",
      "section": "api",
      "anchors": [
        "source-maps"
      ],
      "path": "/llm/json/chunked/api/node-events/preprocessors-api.json",
      "token_estimate": 343
    },
    {
      "id": "api/node-events/preprocessors-api#publishing",
      "doc_id": "api/node-events/preprocessors-api",
      "heading": "Publishing",
      "heading_level": 2,
      "content_markdown": "## Publishing\n\nPublish preprocessors to [npm](https://www.npmjs.com/) with the naming convention `cypress-*-preprocessor` (e.g. cypress-clojurescript-preprocessor).\n\nUse the following npm keywords:\n\n```\n\"keywords\": [  \"cypress\",  \"cypress-plugin\",  \"cypress-preprocessor\"]\n```\n\nFeel free to submit your published plugins to our [list of plugins](/llm/markdown/app/plugins/plugins-list.md).\n",
      "section": "api",
      "anchors": [
        "publishing"
      ],
      "path": "/llm/json/chunked/api/node-events/preprocessors-api.json",
      "token_estimate": 49
    }
  ]
}