Skip to content

Commit 2019c5f

Browse files
Merge branch 'develop' into bump-win-version-info
2 parents 632b187 + 7bdf1e9 commit 2019c5f

File tree

23 files changed

+830
-677
lines changed

23 files changed

+830
-677
lines changed

cli/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ _Released 3/25/2025 (PENDING)_
77

88
- Applies a fix from [#30730](https://github.com/cypress-io/cypress/pull/30730) and [#30099](https://github.com/cypress-io/cypress/pull/30099) related to Node.js turning on ESM flags by default in Node.js version `20.19.0`. Fixed in [#31308](https://github.com/cypress-io/cypress/pull/31308).
99
- Fixed an issue where Firefox BiDi was not correctly removing prerequests on expected network request failures. Fixes [#31325](https://github.com/cypress-io/cypress/issues/31325).
10+
- Fixed an issue in `@cypress/webpack-batteries-included-preprocessor` and `@cypress/webpack-preprocessor` where sourceMaps were not being set correctly in TypeScript 5. This should now make error code frames accurate for TypeScript 5 users. Fixes [#29614](https://github.com/cypress-io/cypress/issues/29614).
1011

1112
**Misc:**
1213

npm/webpack-batteries-included-preprocessor/index.js

+12-40
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
const path = require('path')
2-
const fs = require('fs-extra')
3-
const JSON5 = require('json5')
42
const webpack = require('webpack')
53
const Debug = require('debug')
64
const webpackPreprocessor = require('@cypress/webpack-preprocessor')
@@ -17,38 +15,6 @@ const hasTsLoader = (rules) => {
1715
})
1816
}
1917

20-
const getTSCompilerOptionsForUser = (configFilePath) => {
21-
const compilerOptions = {
22-
sourceMap: false,
23-
inlineSourceMap: true,
24-
inlineSources: true,
25-
downlevelIteration: true,
26-
}
27-
28-
if (!configFilePath) {
29-
return compilerOptions
30-
}
31-
32-
try {
33-
// If possible, try to read the user's tsconfig.json and see if sourceMap is configured
34-
// eslint-disable-next-line no-restricted-syntax
35-
const tsconfigJSON = fs.readFileSync(configFilePath, 'utf8')
36-
// file might have trailing commas, new lines, etc. JSON5 can parse those correctly
37-
const parsedJSON = JSON5.parse(tsconfigJSON)
38-
39-
// if the user has sourceMap's configured, set the option to true and turn off inlineSourceMaps
40-
if (parsedJSON?.compilerOptions?.sourceMap) {
41-
compilerOptions.sourceMap = true
42-
compilerOptions.inlineSourceMap = false
43-
compilerOptions.inlineSources = false
44-
}
45-
} catch (e) {
46-
debug(`error in getTSCompilerOptionsForUser. Returning default...`, e)
47-
} finally {
48-
return compilerOptions
49-
}
50-
}
51-
5218
const addTypeScriptConfig = (file, options) => {
5319
// shortcut if we know we've already added typescript support
5420
if (options.__typescriptSupportAdded) return options
@@ -60,14 +26,21 @@ const addTypeScriptConfig = (file, options) => {
6026
if (!rules || !Array.isArray(rules)) return options
6127

6228
// if we find ts-loader configured, don't add it again
63-
if (hasTsLoader(rules)) return options
29+
if (hasTsLoader(rules)) {
30+
debug('ts-loader already configured, not adding again')
31+
32+
return options
33+
}
6434

6535
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
6636
// node will try to load a projects tsconfig.json instead of the node
67-
// package using require('tsconfig'), so we alias it as 'tsconfig-aliased-for-wbip'
68-
const configFile = require('tsconfig-aliased-for-wbip').findSync(path.dirname(file.filePath))
6937

70-
const compilerOptions = getTSCompilerOptionsForUser(configFile)
38+
const getTsConfig = require('get-tsconfig')
39+
40+
// returns null if tsconfig cannot be found in the path/parent hierarchy
41+
const configFile = getTsConfig.getTsconfig(file.filePath)
42+
43+
configFile ? debug(`found user tsconfig.json at ${configFile?.path} with compilerOptions: ${JSON.stringify(configFile?.config?.compilerOptions)}`) : debug('no user tsconfig.json found')
7144

7245
webpackOptions.module.rules.push({
7346
test: /\.tsx?$/,
@@ -77,7 +50,6 @@ const addTypeScriptConfig = (file, options) => {
7750
loader: require.resolve('ts-loader'),
7851
options: {
7952
compiler: options.typescript,
80-
compilerOptions,
8153
logLevel: 'error',
8254
silent: true,
8355
transpileOnly: true,
@@ -88,7 +60,7 @@ const addTypeScriptConfig = (file, options) => {
8860

8961
webpackOptions.resolve.extensions = webpackOptions.resolve.extensions.concat(['.ts', '.tsx'])
9062
webpackOptions.resolve.plugins = [new TsconfigPathsPlugin({
91-
configFile,
63+
configFile: configFile?.path,
9264
silent: true,
9365
})]
9466

npm/webpack-batteries-included-preprocessor/package.json

+2-4
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@
2727
"debug": "^4.3.4",
2828
"domain-browser": "^4.22.0",
2929
"events": "^3.3.0",
30-
"fs-extra": "^9.1.0",
30+
"get-tsconfig": "^4.10.0",
3131
"https-browserify": "^1.0.0",
32-
"json5": "2.2.3",
3332
"os-browserify": "^0.3.0",
3433
"path-browserify": "^1.0.1",
3534
"process": "^0.11.10",
@@ -39,8 +38,7 @@
3938
"stream-http": "^3.2.0",
4039
"string_decoder": "1.3.0",
4140
"timers-browserify": "^2.0.12",
42-
"ts-loader": "9.4.4",
43-
"tsconfig-aliased-for-wbip": "npm:tsconfig@^7.0.0",
41+
"ts-loader": "9.5.2",
4442
"tsconfig-paths-webpack-plugin": "^3.5.2",
4543
"tty-browserify": "^0.0.1",
4644
"url": "^0.11.1",

npm/webpack-batteries-included-preprocessor/test/unit/index.spec.js

+9-92
Original file line numberDiff line numberDiff line change
@@ -32,36 +32,24 @@ describe('webpack-batteries-included-preprocessor', () => {
3232
})
3333

3434
context('#getTSCompilerOptionsForUser', () => {
35-
const mockTsconfigPath = '/path/to/tsconfig.json'
36-
let readFileTsConfigMock
35+
let getTsConfigMock
3736
let preprocessor
38-
let readFileTsConfigStub
3937
let webpackOptions
4038

4139
beforeEach(() => {
4240
const tsConfigPathSpy = sinon.spy()
4341

44-
readFileTsConfigMock = () => {
45-
throw new Error('Could not read file!')
46-
}
47-
4842
mock('tsconfig-paths-webpack-plugin', tsConfigPathSpy)
4943
mock('@cypress/webpack-preprocessor', (options) => {
5044
return (file) => undefined
5145
})
5246

53-
const tsconfig = require('tsconfig-aliased-for-wbip')
47+
const getTsConfig = require('get-tsconfig')
5448

55-
sinon.stub(tsconfig, 'findSync').callsFake(() => mockTsconfigPath)
49+
getTsConfigMock = sinon.stub(getTsConfig, 'getTsconfig')
5650

5751
preprocessor = require('../../index')
5852

59-
const fs = require('fs-extra')
60-
61-
readFileTsConfigStub = sinon.stub(fs, 'readFileSync').withArgs(mockTsconfigPath, 'utf8').callsFake(() => {
62-
return readFileTsConfigMock()
63-
})
64-
6553
webpackOptions = {
6654
module: {
6755
rules: [],
@@ -74,12 +62,14 @@ describe('webpack-batteries-included-preprocessor', () => {
7462
})
7563

7664
afterEach(() => {
77-
// Remove the mock
65+
// Remove the mock
7866
mock.stop('tsconfig-paths-webpack-plugin')
7967
mock.stop('@cypress/webpack-preprocessor')
8068
})
8169

82-
it('always returns compilerOptions even if there is an error discovering the user\'s tsconfig.json', () => {
70+
it('always returns loader options even if there is an error discovering the user\'s tsconfig.json', () => {
71+
getTsConfigMock.returns(null)
72+
8373
const preprocessorCB = preprocessor({
8474
typescript: true,
8575
webpackOptions,
@@ -90,7 +80,6 @@ describe('webpack-batteries-included-preprocessor', () => {
9080
outputPath: '.js',
9181
})
9282

93-
sinon.assert.calledOnce(readFileTsConfigStub)
9483
const tsLoader = webpackOptions.module.rules[0].use[0]
9584

9685
expect(tsLoader.loader).to.contain('ts-loader')
@@ -100,80 +89,8 @@ describe('webpack-batteries-included-preprocessor', () => {
10089
expect(tsLoader.options.silent).to.be.true
10190
expect(tsLoader.options.transpileOnly).to.be.true
10291

103-
const compilerOptions = tsLoader.options.compilerOptions
104-
105-
expect(compilerOptions.downlevelIteration).to.be.true
106-
expect(compilerOptions.inlineSources).to.be.true
107-
expect(compilerOptions.inlineSources).to.be.true
108-
expect(compilerOptions.sourceMap).to.be.false
109-
})
110-
111-
it('turns inlineSourceMaps on by default even if none are configured', () => {
112-
// make json5 compat schema
113-
const mockTsConfig = `{
114-
"compilerOptions": {
115-
"sourceMap": false,
116-
"someConfigWithTrailingComma": true,
117-
}
118-
}`
119-
120-
readFileTsConfigMock = () => mockTsConfig
121-
122-
const preprocessorCB = preprocessor({
123-
typescript: true,
124-
webpackOptions,
125-
})
126-
127-
preprocessorCB({
128-
filePath: 'foo.ts',
129-
outputPath: '.js',
130-
})
131-
132-
sinon.assert.calledOnce(readFileTsConfigStub)
133-
const tsLoader = webpackOptions.module.rules[0].use[0]
134-
135-
expect(tsLoader.loader).to.contain('ts-loader')
136-
137-
const compilerOptions = tsLoader.options.compilerOptions
138-
139-
expect(compilerOptions.downlevelIteration).to.be.true
140-
expect(compilerOptions.inlineSources).to.be.true
141-
expect(compilerOptions.inlineSources).to.be.true
142-
expect(compilerOptions.sourceMap).to.be.false
143-
})
144-
145-
it('turns on sourceMaps and disables inlineSourceMap and inlineSources if the sourceMap configuration option is set by the user', () => {
146-
// make json5 compat schema
147-
const mockTsConfig = `{
148-
"compilerOptions": {
149-
"sourceMap": true,
150-
"someConfigWithTrailingComma": true,
151-
}
152-
}`
153-
154-
readFileTsConfigMock = () => mockTsConfig
155-
156-
const preprocessorCB = preprocessor({
157-
typescript: true,
158-
webpackOptions,
159-
})
160-
161-
preprocessorCB({
162-
filePath: 'foo.ts',
163-
outputPath: '.js',
164-
})
165-
166-
sinon.assert.calledOnce(readFileTsConfigStub)
167-
const tsLoader = webpackOptions.module.rules[0].use[0]
168-
169-
expect(tsLoader.loader).to.contain('ts-loader')
170-
171-
const compilerOptions = tsLoader.options.compilerOptions
172-
173-
expect(compilerOptions.downlevelIteration).to.be.true
174-
expect(compilerOptions.inlineSources).to.be.false
175-
expect(compilerOptions.inlineSources).to.be.false
176-
expect(compilerOptions.sourceMap).to.be.true
92+
// compilerOptions are set by `@cypress/webpack-preprocessor` if ts-loader is present
93+
expect(tsLoader.options.compilerOptions).to.be.undefined
17794
})
17895
})
17996
})

npm/webpack-preprocessor/index.ts

+97
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,72 @@ import webpack from 'webpack'
77
import utils from './lib/utils'
88
import { overrideSourceMaps } from './lib/typescript-overrides'
99

10+
const getTsLoaderIfExists = (rules) => {
11+
let tsLoaderRule
12+
13+
rules.some((rule) => {
14+
if (!rule.use && !rule.loader) return false
15+
16+
if (Array.isArray(rule.use)) {
17+
const foundRule = rule.use.find((use) => {
18+
return use.loader && use.loader.includes('ts-loader')
19+
})
20+
21+
/**
22+
* If the rule is found, it will look like this:
23+
* rules: [
24+
* {
25+
* test: /\.tsx?$/,
26+
* exclude: [/node_modules/],
27+
* use: [{
28+
* loader: 'ts-loader'
29+
* }]
30+
* }
31+
* ]
32+
*/
33+
tsLoaderRule = foundRule
34+
35+
return tsLoaderRule
36+
}
37+
38+
if (_.isObject(rule.use) && rule.use.loader && rule.use.loader.includes('ts-loader')) {
39+
/**
40+
* If the rule is found, it will look like this:
41+
* rules: [
42+
* {
43+
* test: /\.tsx?$/,
44+
* exclude: [/node_modules/],
45+
* use: {
46+
* loader: 'ts-loader'
47+
* }
48+
* }
49+
* ]
50+
*/
51+
tsLoaderRule = rule.use
52+
53+
return tsLoaderRule
54+
}
55+
56+
tsLoaderRule = rules.find((rule) => {
57+
/**
58+
* If the rule is found, it will look like this:
59+
* rules: [
60+
* {
61+
* test: /\.tsx?$/,
62+
* exclude: [/node_modules/],
63+
* loader: 'ts-loader'
64+
* }
65+
* ]
66+
*/
67+
return rule.loader && rule.loader.includes('ts-loader')
68+
})
69+
70+
return tsLoaderRule
71+
})
72+
73+
return tsLoaderRule
74+
}
75+
1076
const debug = Debug('cypress:webpack')
1177
const debugStats = Debug('cypress:webpack:stats')
1278

@@ -208,6 +274,37 @@ const preprocessor: WebpackPreprocessor = (options: PreprocessorOptions = {}): F
208274
filename: path.basename(outputPath),
209275
},
210276
})
277+
.tap((opts) => {
278+
try {
279+
const tsLoaderRule = getTsLoaderIfExists(opts?.module?.rules)
280+
281+
if (!tsLoaderRule) {
282+
debug('ts-loader not detected')
283+
284+
return
285+
}
286+
287+
// FIXME: To prevent disruption, we are only passing in these 4 options to the ts-loader.
288+
// We will be passing in the entire compilerOptions object from the tsconfig.json in Cypress 15.
289+
// @see https://github.com/cypress-io/cypress/issues/29614#issuecomment-2722071332
290+
// @see https://github.com/cypress-io/cypress/issues/31282
291+
// Cypress ALWAYS wants sourceMap set to true, regardless of the user configuration.
292+
// This is because we want to display a correct code frame in the test runner.
293+
debug(`ts-loader detected: overriding tsconfig to use sourceMap:true, inlineSourceMap:false, inlineSources:false, downlevelIteration:true`)
294+
295+
tsLoaderRule.options = tsLoaderRule?.options || {}
296+
tsLoaderRule.options.compilerOptions = tsLoaderRule.options?.compilerOptions || {}
297+
298+
tsLoaderRule.options.compilerOptions.sourceMap = true
299+
tsLoaderRule.options.compilerOptions.inlineSourceMap = false
300+
tsLoaderRule.options.compilerOptions.inlineSources = false
301+
tsLoaderRule.options.compilerOptions.downlevelIteration = true
302+
} catch (e) {
303+
debug('ts-loader not detected', e)
304+
305+
return
306+
}
307+
})
211308
.tap((opts) => {
212309
if (opts.devtool === false) {
213310
// disable any overrides if we've explicitly turned off sourcemaps

0 commit comments

Comments
 (0)