Skip to content

New example request: @cypress/webpack-preprocessor using exact same webpack config Cypress uses #25998

Open
@karlhorky

Description

Current behavior

It's hard to find the correct way to extend the webpack configuration which Cypress already uses (to keep the features that Cypress already has, such as TypeScript compilation).

It's even hard to find the Cypress webpack configuration, with multiple locations including fragments of webpack configuration:

  • const finalConfig = {
    mode: 'development',
    optimization,
    output: {
    filename: '[name].js',
    path: OUTPUT_PATH,
    publicPath,
    },
    plugins: [
    new (HtmlWebpackPlugin as typeof import('html-webpack-plugin-5'))({
    template: indexHtmlFile,
    // Angular generates all of it's scripts with <script type="module">. Live-reloading breaks without this option.
    // We need to manually set the base here to `/__cypress/src/` so that static assets load with our proxy
    ...(framework === 'angular' ? { scriptLoading: 'module', base: '/__cypress/src/' } : {}),
    }),
    new CypressCTWebpackPlugin({
    files,
    projectRoot,
    devServerEvents,
    supportFile,
    webpack,
    indexHtmlFile,
    }),
    ],
    devtool: 'inline-source-map',
    } as any
    if (isRunMode) {
    // Disable file watching when executing tests in `run` mode
    finalConfig.watchOptions = {
    ignored: '**/*',
    }
    }
    if (webpackDevServerMajorVersion === 4) {
    return {
    ...finalConfig,
    devServer: {
    client: {
    overlay: false,
    },
    },
    }
    }
    // @ts-ignore
    return {
    ...finalConfig,
    devServer: {
    overlay: false,
    },
    }
  • const commonConfig = getCommonConfig()
    const CopyWebpackPlugin = getCopyWebpackPlugin()
    // @ts-ignore
    const babelLoader = _.find(commonConfig.module.rules, (rule) => {
    // @ts-ignore
    return _.includes(rule.use.loader, 'babel-loader')
    })
    // @ts-ignore
    babelLoader.use.options.plugins.push([require.resolve('babel-plugin-prismjs'), {
    'languages': ['javascript', 'coffeescript', 'typescript', 'jsx', 'tsx'],
    'plugins': ['line-numbers', 'line-highlight'],
    'theme': 'default',
    'css': false,
    }])
    let pngRule
    // @ts-ignore
    const nonPngRules = _.filter(commonConfig.module.rules, (rule) => {
    // @ts-ignore
    if (rule.test.toString().includes('png')) {
    pngRule = rule
    return false
    }
    return true
    })
    pngRule.use[0].options = {
    name: '[name].[ext]',
    outputPath: 'img',
    publicPath: '/__cypress/runner/img/',
    }
    // @ts-ignore
    const mainConfig: webpack.Configuration = {
    ...commonConfig,
    module: {
    rules: [
    ...nonPngRules,
    pngRule,
    ],
    },
    entry: {
    cypress_runner: [path.resolve(__dirname, 'src/index.js')],
    },
    output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    },
    }
    // @ts-ignore
    mainConfig.plugins = [
    // @ts-ignore
    ...mainConfig.plugins,
    new CopyWebpackPlugin([{
    // @ts-ignore // There's a race condition in how these types are generated.
    from: cyIcons.getPathToFavicon('favicon.ico'),
    }]),
    ]
    mainConfig.resolve = {
    ...mainConfig.resolve,
    alias: {
    'bluebird': require.resolve('bluebird'),
    'lodash': require.resolve('lodash'),
    'mobx': require.resolve('mobx'),
    'mobx-react': require.resolve('mobx-react'),
    'react': require.resolve('react'),
    'react-dom': require.resolve('react-dom'),
    },
    }
    // @ts-ignore
    const crossOriginConfig: webpack.Configuration = {
    ...commonConfig,
    entry: {
    cypress_cross_origin_runner: [path.resolve(__dirname, 'src/cross-origin.js')],
    },
    output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    },
    }
    // @ts-ignore
    const mainInjectionConfig: webpack.Configuration = {
    ...getSimpleConfig(),
    mode: 'production',
    entry: {
    injection: [path.resolve(__dirname, 'injection/main.js')],
    },
    output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    },
    }
    // @ts-ignore
    const crossOriginInjectionConfig: webpack.Configuration = {
    ...getSimpleConfig(),
    mode: 'production',
    entry: {
    injection_cross_origin: [path.resolve(__dirname, 'injection/cross-origin.js')],
    },
    output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    },
    }
  • export const getCommonConfig = () => {
    const commonConfig: Configuration = {
    mode: 'none',
    node: {
    fs: 'empty',
    child_process: 'empty',
    net: 'empty',
    tls: 'empty',
    module: 'empty',
    },
    resolve: {
    extensions: ['.ts', '.js', '.jsx', '.tsx', '.scss', '.json'],
    },
    stats,
    optimization,
    module: {
    rules: [
    {
    test: /\.(ts|js|jsx|tsx)$/,
    exclude: /node_modules/,
    use: {
    loader: require.resolve('babel-loader'),
    options: {
    plugins: [
    // "istanbul",
    [require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }],
    [require.resolve('@babel/plugin-proposal-class-properties'), { loose: true }],
    ],
    presets: [
    babelPresetEnvConfig,
    require.resolve('@babel/preset-react'),
    babelPresetTypeScriptConfig,
    ],
    babelrc: false,
    },
    },
    },
    {
    test: /\.s?css$/,
    exclude: /node_modules/,
    use: [
    { loader: MiniCSSExtractWebpackPlugin.loader },
    ],
    },
    makeSassLoaders({ modules: false }),
    makeSassLoaders({ modules: true }),
    {
    test: /\.(eot|svg|ttf|woff|woff2)$/,
    use: [
    {
    loader: require.resolve('file-loader'),
    options: {
    name: './fonts/[name].[ext]',
    },
    },
    ],
    },
    {
    test: /\.(png|gif)$/,
    use: [
    {
    loader: require.resolve('file-loader'),
    options: {
    name: './img/[name].[ext]',
    },
    },
    ],
    },
    {
    test: /\.wasm$/,
    type: 'javascript/auto',
    use: [
    {
    loader: require.resolve('arraybuffer-loader'),
    },
    ],
    },
    ],
    },
    plugins: [
    new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
    new MiniCSSExtractWebpackPlugin(),
    // Enable source maps / eval maps
    // 'EvalDevtoolModulePlugin' is used in development
    // because it is fast and maps to filenames while showing compiled source
    // 'SourceMapDevToolPlugin' is used in production for the same reasons as 'eval', but it
    // shows full source and does not cause crossorigin errors like 'eval' (in Chromium < 63)
    // files will be mapped like: `cypress://../driver/cy/commands/click.coffee`
    // other sourcemap options:
    // [new webpack.SourceMapDevToolPlugin({
    // moduleFilenameTemplate: 'cypress://[namespace]/[resource-path]',
    // fallbackModuleFilenameTemplate: 'cypress://[namespace]/[resourcePath]?[hash]'
    // })] :
    ...[
    (env === 'production'
    ? new DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') })
    : evalDevToolPlugin
    ),
    ],
    ...(liveReloadEnabled ? [new LiveReloadPlugin({ appendScriptTag: 'true', port: 0, hostname: 'localhost', protocol: 'http' })] : []),
    ],
    cache: true,
    }
    return commonConfig
    }
    // eslint-disable-next-line @cypress/dev/arrow-body-multiline-braces
    export const getSimpleConfig = () => ({
    node: {
    fs: 'empty',
    child_process: 'empty',
    net: 'empty',
    tls: 'empty',
    module: 'empty',
    },
    resolve: {
    extensions: ['.js', '.ts', '.json'],
    },
    stats,
    optimization,
    cache: true,
    module: {
    rules: [
    {
    test: /\.(js|ts)$/,
    exclude: /node_modules/,
    use: {
    loader: require.resolve('babel-loader'),
    options: {
    plugins: [
    [require.resolve('@babel/plugin-proposal-class-properties'), { loose: true }],
    ],
    presets: [
    babelPresetEnvConfig,
    babelPresetTypeScriptConfig,
    ],
    babelrc: false,
    },
    },
    },
    // FIXME: we don't actually want or need wasm support in the
    // cross origin bundle that uses this config, but we need to refactor
    // the driver so that it doesn't load the wasm code in
    // packages/driver/src/cypress/source_map_utils.js when creating
    // the cross origin bundle. for now, this is necessary so the build
    // doesn't fail
    // https://github.com/cypress-io/cypress/issues/19888
    {
    test: /\.wasm$/,
    type: 'javascript/auto',
    use: [
    {
    loader: require.resolve('arraybuffer-loader'),
    },
    ],
    },
    ],
    },
    plugins: [
    new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
    ],
    })

Desired behavior

It would be great to have a new example project with the exact same webpack configuration as is used inside Cypress for bundling user test code, similar to the 4 existing examples in the system-tests/projects folder:

  • webpack-preprocessor-awesome-typescript-loader
  • webpack-preprocessor-ts-loader-compiler-options
  • webpack-preprocessor-ts-loader
  • webpack-preprocessor-webpack-5

This example project should have the cypress.config.js file with the setupNodeEvents method configured, similar to the other examples above.

Additionally, this example + documentation should be:

  • easy to use
    • copy + paste into Cypress config, not multiple steps
    • documented (what are some common use cases of extending the config?)
  • tested by the Cypress maintainers
  • maintained by the Cypress maintainers

This way, it's easy to extend the existing webpack config used by Cypress by copying and pasting this example config and then adding your own configuration.

Test code to reproduce

The point of this issue is that it is difficult to find working code. There are many examples of outdated or non-working code throughout the issues in this repository and other locations.

Cypress Version

12.6.0

Node version

18.14.1

Operating System

macOS Ventura 13.2.1 (22D68)

Debug Logs

No response

Other

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    type: enhancementRequested enhancement of existing feature

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions