Cypress Webpack bundler
By default, Cypress bundles the spec files using the built-in preprocessor that uses Webpack under the hood. I wanted to see how long it takes to bundle average spec files in the cypress-io/cypress-example-todomvc application. Unfortunately, the preprocessor does not expose the timings directly, but we can always hack on Cypress code right in the binary.
1 | $ npx cypress info |
Next, I open the file in that binary cache folder that serves the spec files to the browser. In this case it is the file:
1 | /Users/gleb/Library/Caches/Cypress/6.7.1/Cypress.app/Contents/Resources/app/packages/server/lib/controllers/spec.js |
Tip: this is the transpiled file, you can find the original in Cypress repo
The controllers/spec.js
uses the debug module to print the debug messages
1 | const debug = require('debug')('cypress:server:controllers:spec') |
Tip: most source files in Cypress Test Runner use debug
to print debug messages, allowing you to peek under the hood. Find some common log sources in the docs.
I have inserted a few additional debug statements to measure how long the preprocessor takes to bundle the spec file.
1 | const debug = require('debug')('cypress:server:controllers:spec') |
Let's run the tests in using cypress run
command. During the execution the spec and the support files are each bundled just once. It is a cold start - Cypress does not store or load any bundling information to speed things up.
1 | $ DEBUG=cypress:server:controllers:spec npm run test:ci |
I have run the same command 3 times on my Mac to get the following timings:
Support file (ms) | Spec file (ms) |
---|---|
1702 | 1143 |
1708 | 1178 |
1715 | 1171 |
Ok, so about 1700ms to bundle the support file and 1100ms to bundle the spec file.
We can look at the files we are bundling. The support file only imports two other modules:
1 | require('./commands') |
The commands.js
file only defines 3 custom commands:
1 | Cypress.Commands.add('createDefaultTodos', function () { ... }) |
The support is pretty much just bundling the cypress-axe NPM module.
The spec file has no imports, it is a single standalone JavaScript spec file.
ESBuild
The ESBuild is the new bundler that uses an optimized binary bundler. Let's see how fast it is.
1 | $ npm i -D esbuild |
We can bundle the files from the command line
1 | $ npx esbuild cypress/support/index.js --bundle --outfile=out.js |
Wow. Ok. Can we use the esbuild
bundler from Cypress?
ESBuild file preprocessor
ESBuild has good JavaScript API which we can use to write our own file preprocessor. You can find my NPM module in bahmutov/cypress-esbuild-preprocessor repo and install via NPM
1 | $ npm i -D @bahmutov/cypress-esbuild-preprocessor |
Note: esbuild
is a peer dependency.
For example to build the spec file, the preprocessor does the following:
1 | const esbuild = require('esbuild') |
We can point Cypress to use the above preprocessor
1 | module.exports = (on, config) => { |
My preprocessor includes timing, thus we can directly see the bundling performance
1 | $ DEBUG=cypress-esbuild-preprocessor npm run test:ci |
I ran the test three times, and here are the timings
Support file (ms) | Spec file (ms) |
---|---|
104 | 22 |
104 | 22 |
101 | 22 |
That's pretty strong statement: the ESBuild preprocessor is 17 times faster in bundling the support file and 50 (fifty!) times faster in bundling the spec file.
I am pretty impressed.