diff --git a/.gitignore b/.gitignore index c038b66451ca..fb4cf85ce7be 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ packages/server/test/support/fixtures/server/libs # from npm/react /npm/react/bin/* /npm/react/cypress/videos +/npm/react/.babel-cache # from runner-ct /packages/runner-ct/cypress/screenshots diff --git a/circle.yml b/circle.yml index df4f66de3d27..d3d3e7f15e3b 100644 --- a/circle.yml +++ b/circle.yml @@ -1086,6 +1086,9 @@ jobs: - attach_workspace: at: ~/ - check-conditional-ci + - restore_cache: + name: Restore yarn cache + key: v{{ .Environment.CACHE_VERSION }}-{{ arch }}-npm-react-babel-cache - run: name: Build command: yarn workspace @cypress/react build @@ -1093,6 +1096,11 @@ jobs: name: Run tests command: yarn workspace @cypress/react test - store-npm-logs + - save_cache: + name: Save Cache + key: v{{ .Environment.CACHE_VERSION }}-{{ arch }}-npm-react-babel-cache + paths: + - cypress/npm/react/.babel-cache - persist_to_workspace: root: ~/ paths: cypress/npm/react diff --git a/cli/types/cypress.d.ts b/cli/types/cypress.d.ts index 55cb1924ed9a..2af83a2e8e67 100644 --- a/cli/types/cypress.d.ts +++ b/cli/types/cypress.d.ts @@ -5134,11 +5134,7 @@ declare namespace Cypress { interface DevServerOptions { specs: Spec[] - config: { - supportFile?: string - projectRoot: string - webpackDevServerPublicPathRoute: string - }, + config: ResolvedConfigOptions & RuntimeConfigOptions, devServerEvents: NodeJS.EventEmitter, } diff --git a/npm/react/cypress/plugins/index.js b/npm/react/cypress/plugins/index.js index 06bea18ed664..fb10532a5a13 100644 --- a/npm/react/cypress/plugins/index.js +++ b/npm/react/cypress/plugins/index.js @@ -1,4 +1,6 @@ +// @ts-check const { startDevServer } = require('@cypress/webpack-dev-server') +const path = require('path') const babelConfig = require('../../babel.config.js') /** @type import("webpack").Configuration */ @@ -17,7 +19,7 @@ const webpackConfig = { { test: /\.(js|jsx|mjs|ts|tsx)$/, loader: 'babel-loader', - options: babelConfig, + options: { ...babelConfig, cacheDirectory: path.resolve(__dirname, '..', '..', '.babel-cache') }, }, { test: /\.modules\.css$/i, @@ -59,7 +61,7 @@ const webpackConfig = { * @type Cypress.PluginConfig */ module.exports = (on, config) => { - on('dev-server:start', (options) => startDevServer({ options, webpackConfig })) + on('dev-server:start', (options) => startDevServer({ options, webpackConfig, disableLazyCompilation: false })) return config } diff --git a/npm/webpack-dev-server/package.json b/npm/webpack-dev-server/package.json index 15cfed5da1c4..92c08d693980 100644 --- a/npm/webpack-dev-server/package.json +++ b/npm/webpack-dev-server/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "debug": "4.3.2", + "lazy-compile-webpack-plugin": "0.1.11", "semver": "^7.3.4", "webpack-merge": "^5.4.0" }, diff --git a/npm/webpack-dev-server/src/index.ts b/npm/webpack-dev-server/src/index.ts index 8a4a2f8773c7..85069652e489 100644 --- a/npm/webpack-dev-server/src/index.ts +++ b/npm/webpack-dev-server/src/index.ts @@ -1,18 +1,9 @@ -import { EventEmitter } from 'events' import { debug as debugFn } from 'debug' import { AddressInfo } from 'net' import { start as createDevServer } from './startServer' -const debug = debugFn('cypress:webpack-dev-server:webpack') +import { UserWebpackDevServerOptions } from './makeWebpackConfig' -export interface DevServerOptions { - specs: Cypress.Cypress['spec'][] - config: { - supportFile: string - projectRoot: string - webpackDevServerPublicPathRoute: string - } - devServerEvents: EventEmitter -} +const debug = debugFn('cypress:webpack-dev-server:webpack') type DoneCallback = () => unknown @@ -21,9 +12,9 @@ export interface ResolvedDevServerConfig { close: (done?: DoneCallback) => void } -export interface StartDevServer { +export interface StartDevServer extends UserWebpackDevServerOptions { /* this is the Cypress options object */ - options: DevServerOptions + options: Cypress.DevServerOptions /* support passing a path to the user's webpack config */ webpackConfig?: Record } diff --git a/npm/webpack-dev-server/src/makeWebpackConfig.ts b/npm/webpack-dev-server/src/makeWebpackConfig.ts index 8cef9c6bafc4..06efdfa3dd2e 100644 --- a/npm/webpack-dev-server/src/makeWebpackConfig.ts +++ b/npm/webpack-dev-server/src/makeWebpackConfig.ts @@ -1,28 +1,51 @@ import { debug as debugFn } from 'debug' import * as path from 'path' -import { Configuration } from 'webpack' +import * as webpack from 'webpack' import { merge } from 'webpack-merge' +import defaultWebpackConfig from './webpack.config' +import LazyCompilePlugin from 'lazy-compile-webpack-plugin' import CypressCTOptionsPlugin, { CypressCTOptionsPluginOptions } from './plugin' const debug = debugFn('cypress:webpack-dev-server:makeWebpackConfig') +const WEBPACK_MAJOR_VERSION = Number(webpack.version.split('.')[0]) + +export interface UserWebpackDevServerOptions { + /** + * if `true` will compile all the specs together when the first one is request and can slow up initial build time. + * @default false + */ + disableLazyCompilation?: boolean +} + +interface MakeWebpackConfigOptions extends CypressCTOptionsPluginOptions, UserWebpackDevServerOptions { + webpackDevServerPublicPathRoute: string + isOpenMode: boolean +} const mergePublicPath = (baseValue, userValue = '/') => { return path.join(baseValue, userValue, '/') } -interface MakeWebpackConfigOptions extends CypressCTOptionsPluginOptions { - webpackDevServerPublicPathRoute: string +function getLazyCompilationWebpackConfig (options: MakeWebpackConfigOptions): webpack.Configuration { + if (options.disableLazyCompilation || !options.isOpenMode) { + return {} + } + + switch (WEBPACK_MAJOR_VERSION) { + case 4: + return { plugins: [new LazyCompilePlugin()] } + case 5: + return { experiments: { lazyCompilation: true } } as webpack.Configuration + default: + return { } + } } -export async function makeWebpackConfig (userWebpackConfig: Configuration, options: MakeWebpackConfigOptions): Promise { +export async function makeWebpackConfig (userWebpackConfig: webpack.Configuration, options: MakeWebpackConfigOptions): Promise { const { projectRoot, webpackDevServerPublicPathRoute, files, supportFile, devServerEvents } = options debug(`User passed in webpack config with values %o`, userWebpackConfig) - const defaultWebpackConfig = require('./webpack.config') - - debug(`Merging Evergreen's webpack config with users'`) - debug(`New webpack entries %o`, files) debug(`Project root`, projectRoot) debug(`Support file`, supportFile) @@ -45,7 +68,12 @@ export async function makeWebpackConfig (userWebpackConfig: Configuration, optio ], } - const mergedConfig = merge(userWebpackConfig, defaultWebpackConfig, dynamicWebpackConfig) + const mergedConfig = merge( + userWebpackConfig, + defaultWebpackConfig, + dynamicWebpackConfig, + getLazyCompilationWebpackConfig(options), + ) mergedConfig.entry = entry diff --git a/npm/webpack-dev-server/src/startServer.ts b/npm/webpack-dev-server/src/startServer.ts index 9863dac3c96e..dbab4f0f05b3 100644 --- a/npm/webpack-dev-server/src/startServer.ts +++ b/npm/webpack-dev-server/src/startServer.ts @@ -6,19 +6,22 @@ import { makeWebpackConfig } from './makeWebpackConfig' const debug = Debug('cypress:webpack-dev-server:start') -export async function start ({ webpackConfig: userWebpackConfig, options }: StartDevServer): Promise { +export async function start ({ webpackConfig: userWebpackConfig, options, ...userOptions }: StartDevServer): Promise { if (!userWebpackConfig) { debug('User did not pass in any webpack configuration') } - const { projectRoot, webpackDevServerPublicPathRoute } = options.config + // @ts-expect-error ?? webpackDevServerPublicPathRoute is not a valid option of Cypress.Config + const { projectRoot, webpackDevServerPublicPathRoute, isTextTerminal } = options.config const webpackConfig = await makeWebpackConfig(userWebpackConfig || {}, { files: options.specs, projectRoot, webpackDevServerPublicPathRoute, devServerEvents: options.devServerEvents, - supportFile: options.config.supportFile, + supportFile: options.config.supportFile as string, + isOpenMode: !isTextTerminal, + ...userOptions, }) debug('compiling webpack') diff --git a/npm/webpack-dev-server/src/webpack.config.js b/npm/webpack-dev-server/src/webpack.config.js index 1f2eef5d09d2..968bec6c5311 100644 --- a/npm/webpack-dev-server/src/webpack.config.js +++ b/npm/webpack-dev-server/src/webpack.config.js @@ -1,6 +1,7 @@ const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') +/** @type {import('webpack').Configuration} */ module.exports = { mode: 'development', optimization: { diff --git a/npm/webpack-dev-server/test/e2e.spec.ts b/npm/webpack-dev-server/test/e2e.spec.ts index b74be273940e..7260b241566c 100644 --- a/npm/webpack-dev-server/test/e2e.spec.ts +++ b/npm/webpack-dev-server/test/e2e.spec.ts @@ -51,8 +51,9 @@ const specs: Cypress.Cypress['spec'][] = [ const config = { projectRoot: root, supportFile: '', + isTextTerminal: true, webpackDevServerPublicPathRoute: root, -} +} as any as Cypress.ResolvedConfigOptions & Cypress.RuntimeConfigOptions describe('#startDevServer', () => { it('serves specs via a webpack dev server', async () => { diff --git a/yarn.lock b/yarn.lock index 28c5332d0349..a24e04c8632f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13421,7 +13421,7 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== -detect-port-alt@1.1.6: +detect-port-alt@1.1.6, detect-port-alt@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== @@ -21463,6 +21463,14 @@ lazy-cache@^1.0.3: resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= +lazy-compile-webpack-plugin@0.1.11: + version "0.1.11" + resolved "https://registry.yarnpkg.com/lazy-compile-webpack-plugin/-/lazy-compile-webpack-plugin-0.1.11.tgz#be3b9487ccc731a606dc55bcfcd80000c72e4237" + integrity sha512-d2D72x0XqFSj83SRmgx1dvgRvmyIoXpC2lMj+XVS+xzt0FxXDPzF/2FbxOVmW9gzp6d8U29Ne4RGF4x+MTYwow== + dependencies: + detect-port-alt "^1.1.6" + loader-utils "^1.2.3" + lazy-property@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazy-property/-/lazy-property-1.0.0.tgz#84ddc4b370679ba8bd4cdcfa4c06b43d57111147"