Skip to content

feat: ship webpack-bundle-analyzer with@cypress/webpack-dev-server and @cypress/webpack-batteries-included-preprocessor #31588

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/cache-version.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Bump this version to force CI to re-create the cache from scratch.

4-29-2025
5-12-2025
10 changes: 5 additions & 5 deletions .circleci/workflows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ mainBuildFilters: &mainBuildFilters
- /^release\/\d+\.\d+\.\d+$/
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- 'update-v8-snapshot-cache-on-develop'
- 'update-trash'
- 'feat/add_webpack_bundle_analyzer'

# usually we don't build Mac app - it takes a long time
# but sometimes we want to really confirm we are doing the right thing
Expand All @@ -49,7 +49,7 @@ macWorkflowFilters: &darwin-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'update-trash', << pipeline.git.branch >> ]
- equal: [ 'feat/add_webpack_bundle_analyzer', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
Expand All @@ -60,7 +60,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'update-trash', << pipeline.git.branch >> ]
- equal: [ 'feat/add_webpack_bundle_analyzer', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
Expand All @@ -83,7 +83,7 @@ windowsWorkflowFilters: &windows-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'chore/fix_reporter_app_cy_in_cy_flake', << pipeline.git.branch >> ]
- equal: [ 'feat/add_webpack_bundle_analyzer', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
Expand Down Expand Up @@ -157,7 +157,7 @@ commands:
name: Set environment variable to determine whether or not to persist artifacts
command: |
echo "Setting SHOULD_PERSIST_ARTIFACTS variable"
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "update-trash" ]]; then
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "feat/add_webpack_bundle_analyzer" ]]; then
export SHOULD_PERSIST_ARTIFACTS=true
fi' >> "$BASH_ENV"
# You must run `setup_should_persist_artifacts` command and be using bash before running this command
Expand Down
7 changes: 5 additions & 2 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
<!-- See the ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
## 14.3.4
## 14.4.0

_Released 5/20/2025 (PENDING)_

**Features:**

- `@cypress/webpack-dev-server` and `@cypress/webpack-batteries-included-preprocessor` now ship with [webpack-bundle-analyzer](https://www.npmjs.com/package/webpack-bundle-analyzer) as a diagnostic tool to determine bundle statistics, which can be enabled via `DEBUG=cypress-verbose:webpack-dev-server:bundle-analyzer` ( component tests using webpack) or `DEBUG=cypress-verbose:webpack-batteries-included-preprocessor:bundle-analyzer` (e2e tests using webpack, which is the default preprocessor), respectively. Addresses [#30461](https://github.com/cypress-io/cypress/issues/30461).

**Dependency Updates:**

- Upgraded `trash` from `5.2.0` to `7.2.0`. Addressed in [#31667](https://github.com/cypress-io/cypress/pull/31667).


## 14.3.3

_Released 5/6/2025_
Expand Down
4 changes: 4 additions & 0 deletions npm/webpack-batteries-included-preprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ module.exports = (on) => {

Other than the `typescript` option, this preprocessor supports the same options as [@cypress/webpack-preprocessor](https://github.com/cypress-io/cypress/tree/develop/npm/webpack-preprocessor#readme), so see its [README](https://github.com/cypress-io/cypress/tree/develop/npm/webpack-preprocessor#readme) for more information.

## Debugging

If having issues with chunk load errors or bundle size problems, specifically in your end-to-end tests, please try setting `DEBUG=cypress-verbose:webpack-batteries-included-preprocessor:bundle-analyzer` before starting Cypress to get a `webpack-bundle-analyzer` report to help determine the cause of the issue. If filing an issue with Cypress, please include this report with your issue to better help us serve your issue.

## Contributing

Use the [version of Node that matches Cypress](https://github.com/cypress-io/cypress/blob/develop/.node-version).
Expand Down
6 changes: 6 additions & 0 deletions npm/webpack-batteries-included-preprocessor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ const path = require('path')
const webpack = require('webpack')
const Debug = require('debug')
const webpackPreprocessor = require('@cypress/webpack-preprocessor')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

const debug = Debug('cypress:webpack-batteries-included-preprocessor')
const WBADebugNamespace = 'cypress-verbose:webpack-batteries-included-preprocessor:bundle-analyzer'

const hasTsLoader = (rules) => {
return rules.some((rule) => {
Expand Down Expand Up @@ -136,6 +138,10 @@ const getDefaultWebpackOptions = () => {
// @see https://github.com/cypress-io/cypress/issues/27947.
process: require.resolve('process/browser.js'),
}),
// If the user is trying to debug their bundle, we'll add the BundleAnalyzerPlugin
// to see the size of the support file (first bundle when running `cypress open`)
// and spec files (subsequent bundles when running `cypress open`)
...(Debug.enabled(WBADebugNamespace) ? [new BundleAnalyzerPlugin()] : []),
],
resolve: {
extensions: ['.js', '.json', '.jsx', '.mjs', '.coffee'],
Expand Down
3 changes: 2 additions & 1 deletion npm/webpack-batteries-included-preprocessor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"url": "^0.11.1",
"util": "^0.12.5",
"vm-browserify": "^1.1.2",
"webpack": "^5.88.2"
"webpack": "^5.88.2",
"webpack-bundle-analyzer": "4.10.2"
},
"devDependencies": {
"@cypress/webpack-preprocessor": "0.0.0-development",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { expect } = require('chai')
const decache = require('decache')
const mock = require('mock-require')
const sinon = require('sinon')
const Debug = require('debug')

describe('webpack-batteries-included-preprocessor', () => {
beforeEach(() => {
Expand Down Expand Up @@ -29,6 +30,20 @@ describe('webpack-batteries-included-preprocessor', () => {
expect(result.module.rules).to.have.length(4)
expect(result.module.rules[3].use[0].loader).to.include('ts-loader')
})

it('adds the BundleAnalyzerPlugin if the user is trying to debug their bundle', () => {
Debug.enable('cypress-verbose:webpack-batteries-included-preprocessor:bundle-analyzer')

// since debug needs to be hydrated before requiring the preprocessor, we need to decache
// and require again
decache('../../index')
preprocessor = require('../../index')
const result = preprocessor.getFullWebpackOptions('file/path', 'typescript/path')

expect(result.plugins).to.have.length(2)
expect(result.plugins[1].constructor.name).to.equal('BundleAnalyzerPlugin')
Debug.disable()
})
})

context('#getTSCompilerOptionsForUser', () => {
Expand Down
4 changes: 4 additions & 0 deletions npm/webpack-dev-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export default defineConfig({
})
```

## Debugging

If having issues with chunk load errors or bundle size problems, specifically in your component tests, please try setting `DEBUG=cypress-verbose:webpack-dev-server:bundle-analyzer` before starting Cypress to get a `webpack-bundle-analyzer` report to help determine the cause of the issue. If filing an issue with Cypress, please include this report with your issue to better help us serve your issue.

## Testing

Unit tests can be run with `yarn test`. Integration tests can be run with `yarn cypress:run`
Expand Down
2 changes: 2 additions & 0 deletions npm/webpack-dev-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
"semver": "^7.7.1",
"speed-measure-webpack-plugin": "1.4.2",
"tslib": "^2.3.1",
"webpack-bundle-analyzer": "4.10.2",
"webpack-dev-server": "^5.1.0",
"webpack-merge": "^5.4.0"
},
"devDependencies": {
"@types/node": "20.16.0",
"@types/proxyquire": "^1.3.28",
"@types/speed-measure-webpack-plugin": "^1.3.4",
"@types/webpack-bundle-analyzer": "4.7.0",
"chai": "^4.3.6",
"dedent": "^0.7.0",
"mocha": "^9.2.2",
Expand Down
9 changes: 9 additions & 0 deletions npm/webpack-dev-server/src/createWebpackDevServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Configuration as WebpackDevServer4Configuration } from 'webpack-de
import type { WebpackDevServerConfig } from './devServer'
import type { SourceRelativeWebpackResult } from './helpers/sourceRelativeWebpackModules'
import { makeWebpackConfig } from './makeWebpackConfig'
import { isWebpackBundleAnalyzerEnabled } from './util'

const debug = debugLib('cypress:webpack-dev-server:start')

Expand Down Expand Up @@ -76,6 +77,10 @@ function webpackDevServer5 (
devMiddleware: {
publicPath: devServerPublicPathRoute,
stats: finalWebpackConfig.stats ?? 'minimal',
...(isWebpackBundleAnalyzerEnabled() ? {
// the bundle needs to be written to disk in order to determine source map sizes
writeToDisk: true,
} : {}),
},
hot: false,
// Only enable file watching & reload when executing tests in `open` mode
Expand Down Expand Up @@ -111,6 +116,10 @@ function webpackDevServer4 (
devMiddleware: {
publicPath: devServerPublicPathRoute,
stats: finalWebpackConfig.stats ?? 'minimal',
...(isWebpackBundleAnalyzerEnabled() ? {
// the bundle needs to be written to disk in order to determine source map sizes
writeToDisk: true,
} : {}),
},
hot: false,
// Only enable file watching & reload when executing tests in `open` mode
Expand Down
7 changes: 7 additions & 0 deletions npm/webpack-dev-server/src/makeDefaultWebpackConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import debugLib from 'debug'
import type { Configuration } from 'webpack'
import type { CreateFinalWebpackConfig } from './createWebpackDevServer'
import { CypressCTWebpackPlugin } from './CypressCTWebpackPlugin'
import { isWebpackBundleAnalyzerEnabled, WBADebugNamespace } from './util'
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'

const debug = debugLib('cypress:webpack-dev-server:makeDefaultWebpackConfig')

Expand Down Expand Up @@ -95,10 +97,15 @@ export function makeCypressWebpackConfig (
webpack,
indexHtmlFile,
}),
...(isWebpackBundleAnalyzerEnabled() ? [new BundleAnalyzerPlugin()] : []),
],
devtool: 'inline-source-map',
} as any

if (isWebpackBundleAnalyzerEnabled()) {
debugLib(WBADebugNamespace)('webpack-bundle-analyzer is enabled.')
}

if (isRunMode) {
// if justInTimeCompile is configured, we need to watch for file changes as the spec entries are going to be updated per test
const ignored = justInTimeCompile ? /node_modules/ : '**/*'
Expand Down
7 changes: 7 additions & 0 deletions npm/webpack-dev-server/src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import debug from 'debug'

export const WBADebugNamespace = 'cypress-verbose:webpack-dev-server:bundle-analyzer'

export const isWebpackBundleAnalyzerEnabled = () => {
return debug.enabled(WBADebugNamespace)
}
41 changes: 41 additions & 0 deletions npm/webpack-dev-server/test/devServer-unit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { expect } from 'chai'

import { createModuleMatrixResult } from './test-helpers/createModuleMatrixResult'
import EventEmitter from 'events'
import debug from 'debug'

const cypressConfig = {
projectRoot: path.join(__dirname, 'test-fixtures'),
Expand Down Expand Up @@ -79,4 +80,44 @@ describe('devServer', function () {
expect(result.server).to.be.instanceOf(require('webpack-dev-server'))
expect(result.version).to.eq(5)
})

// Writing to disk includes the correct source map size, where the difference will be made up from stat size vs parsed size
// This is critical if a user is trying to debug to determine if they have large source maps or other large files in their dev-server under test
describe('writes to disk if DEBUG=cypress-verbose:webpack-dev-server:bundle-analyzer is set', async () => {
const WEBPACK_DEV_SERVER_VERSIONS: (4 | 5)[] = [4, 5]

beforeEach(() => {
debug.enable('cypress-verbose:webpack-dev-server:bundle-analyzer')
})

afterEach(() => {
debug.disable()
})

WEBPACK_DEV_SERVER_VERSIONS.forEach((version) => {
it(`works for webpack-dev-server v${version}`, async () => {
const { devServer } = proxyquire('../src/devServer', {
'./helpers/sourceRelativeWebpackModules': {
sourceDefaultWebpackDependencies: () => {
// using webpack version to wds version as it really doesn't matter much when testing here.
// webpack config is tested separately in makeWebpackConfig tests
return createModuleMatrixResult({
webpack: version,
webpackDevServer: version,
})
} },
}) as typeof import('../src/devServer')

const result = await devServer.create({
specs: [],
cypressConfig,
webpackConfig: {},
devServerEvents: new EventEmitter(),
})

// @ts-expect-error
expect(result.server.options.devMiddleware.writeToDisk).to.be.true
})
})
})
})
44 changes: 43 additions & 1 deletion npm/webpack-dev-server/test/makeWebpackConfig.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import Chai, { expect } from 'chai'
import EventEmitter from 'events'
import snapshot from 'snap-shot-it'
import path from 'path'
import debug from 'debug'
import { IgnorePlugin } from 'webpack'
import { WebpackDevServerConfig } from '../src/devServer'
import { CYPRESS_WEBPACK_ENTRYPOINT, makeWebpackConfig } from '../src/makeWebpackConfig'
import { createModuleMatrixResult } from './test-helpers/createModuleMatrixResult'
import sinon from 'sinon'
import SinonChai from 'sinon-chai'
import type { SourceRelativeWebpackResult } from '../src/helpers/sourceRelativeWebpackModules'
import path from 'path'
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'

Chai.use(SinonChai)

Expand Down Expand Up @@ -451,4 +453,44 @@ describe('makeWebpackConfig', () => {
})
})
})

// Gives users a diagnostic output with webpack-bundle-analyzer to get a visible representation of their webpack bundle, which they can send to us
// to give us an idea what issues they may be experiencing
describe('enables webpack-bundle-analyzer if DEBUG=cypress-verbose:webpack-dev-server:bundle-analyzer is set', async () => {
const WEBPACK_VERSIONS: (4 | 5)[] = [4, 5]

beforeEach(() => {
debug.enable('cypress-verbose:webpack-dev-server:bundle-analyzer')
})

afterEach(() => {
debug.disable()
})

WEBPACK_VERSIONS.forEach((version) => {
it(`works for webpack v${version}`, async () => {
const actual = await makeWebpackConfig({
devServerConfig: {
specs: [],
cypressConfig: {
projectRoot: '.',
devServerPublicPathRoute: '/test-public-path',
baseUrl: null,
} as Cypress.PluginConfigOptions,
webpackConfig: {
entry: { main: 'src/index.js' },
},
devServerEvents: new EventEmitter(),
},
sourceWebpackModulesResult: createModuleMatrixResult({
webpack: version,
webpackDevServer: version,
}),
})

expect(actual.plugins).to.have.length(3)
expect(actual.plugins[2]).to.be.instanceOf(BundleAnalyzerPlugin)
})
})
})
})
Loading
Loading