/
makeWebpackConfig.ts
151 lines (124 loc) · 5.57 KB
/
makeWebpackConfig.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import { debug as debugFn } from 'debug'
import * as path from 'path'
import { merge } from 'webpack-merge'
import { importModule } from 'local-pkg'
import type { Configuration, EntryObject } from 'webpack'
import { makeCypressWebpackConfig } from './makeDefaultWebpackConfig'
import type { CreateFinalWebpackConfig } from './createWebpackDevServer'
import { configFiles } from './constants'
import { dynamicImport } from './dynamic-import'
const debug = debugFn('cypress:webpack-dev-server:makeWebpackConfig')
const removeList = [
// We provide a webpack-html-plugin config pinned to a specific version (4.x)
// that we have tested and are confident works with all common configurations.
// https://github.com/cypress-io/cypress/issues/15865
'HtmlWebpackPlugin',
// This plugin is an optimization for HtmlWebpackPlugin for use in
// production environments, not relevant for testing.
'PreloadPlugin',
// Another optimization not relevant in a testing environment.
'HtmlPwaPlugin',
// We already reload when webpack recompiles (via listeners on
// devServerEvents). Removing this plugin can prevent double-refreshes
// in some setups.
'HotModuleReplacementPlugin',
]
// CaseSensitivePathsPlugin checks the paths of every loaded module to enforce
// case sensitivity - this helps developers on mac catch issues that will bite
// them later, but on linux the OS already does this by default. The plugin
// is somewhat slow, accounting for ~15% of compile time on a couple of CRA
// based projects (where it's included by default) tested.
if (process.platform === 'linux') {
removeList.push('CaseSensitivePathsPlugin')
}
export const CYPRESS_WEBPACK_ENTRYPOINT = path.resolve(__dirname, 'browser.js')
/**
* Removes and/or modifies certain plugins known to conflict
* when used with cypress/webpack-dev-server.
*/
function modifyWebpackConfigForCypress (webpackConfig: Partial<Configuration>) {
if (webpackConfig?.plugins) {
webpackConfig.plugins = webpackConfig.plugins.filter((plugin) => !removeList.includes(plugin.constructor.name))
}
if (typeof webpackConfig?.module?.unsafeCache === 'function') {
const originalCachePredicate = webpackConfig.module.unsafeCache
webpackConfig.module.unsafeCache = (module: any) => {
return originalCachePredicate(module) && !/[\\/]webpack-dev-server[\\/]dist[\\/]browser\.js/.test(module.resource)
}
}
return webpackConfig
}
async function getWebpackConfigFromProjectRoot (projectRoot: string) {
const { findUp } = await dynamicImport<typeof import('find-up')>('find-up')
return await findUp(configFiles, { cwd: projectRoot })
}
/**
* Creates a webpack 4/5 compatible webpack "configuration"
* to pass to the sourced webpack function
*/
export async function makeWebpackConfig (
config: CreateFinalWebpackConfig,
) {
let userWebpackConfig = config.devServerConfig.webpackConfig
const frameworkWebpackConfig = config.frameworkConfig as Partial<Configuration>
const {
cypressConfig: {
projectRoot,
supportFile,
},
specs: files,
framework,
} = config.devServerConfig
if (!userWebpackConfig && !frameworkWebpackConfig) {
debug('Not user or framework webpack config received. Trying to automatically source it')
const configFile = await getWebpackConfigFromProjectRoot(projectRoot)
if (configFile) {
debug('found webpack config %s', configFile)
const sourcedConfig = await importModule(configFile)
debug('config contains %o', sourcedConfig)
if (sourcedConfig && typeof sourcedConfig === 'object') {
userWebpackConfig = sourcedConfig.default ?? sourcedConfig
}
}
if (!userWebpackConfig) {
debug('could not find webpack.config!')
if (config.devServerConfig?.onConfigNotFound) {
config.devServerConfig.onConfigNotFound('webpack', projectRoot, configFiles)
// The config process will be killed from the parent, but we want to early exit so we don't get
// any additional errors related to not having a config
process.exit(0)
} else {
throw new Error(`Your Cypress devServer config is missing a required webpackConfig property, since we could not automatically detect one.\nPlease add one to your ${config.devServerConfig.cypressConfig.configFile}`)
}
}
}
userWebpackConfig = typeof userWebpackConfig === 'function'
? await userWebpackConfig()
: userWebpackConfig
const userAndFrameworkWebpackConfig = modifyWebpackConfigForCypress(
merge(frameworkWebpackConfig ?? {}, userWebpackConfig ?? {}),
)
debug(`User passed in user and framework webpack config with values %o`, userAndFrameworkWebpackConfig)
debug(`New webpack entries %o`, files)
debug(`Project root`, projectRoot)
debug(`Support file`, supportFile)
const mergedConfig = merge(
userAndFrameworkWebpackConfig,
makeCypressWebpackConfig(config),
)
// Some frameworks (like Next.js) change this value which changes the path we would need to use to fetch our spec.
// (eg, http://localhost:xxxx/<dev-server-public-path>/static/chunks/spec-<x>.js). Deleting this key to normalize
// the spec URL to `*/spec-<x>.js` which we need to know up-front so we can fetch the sourcemaps.
delete mergedConfig.output?.chunkFilename
// Angular loads global styles and polyfills via script injection in the index.html
if (framework === 'angular') {
mergedConfig.entry = {
...(mergedConfig.entry as EntryObject) || {},
'cypress-entry': CYPRESS_WEBPACK_ENTRYPOINT,
}
} else {
mergedConfig.entry = CYPRESS_WEBPACK_ENTRYPOINT
}
debug('Merged webpack config %o', mergedConfig)
return mergedConfig
}