Skip to content
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

fix: ensure that each js file served up by vite dev server has an inline sourcemap #30606

8 changes: 8 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
<!-- See the ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
## 13.15.3

_Released 11/19/2024 (PENDING)_

**Bugfixes:**

- Fixed an issue where some JS assets were not properly getting sourcemaps included with the vite dev server if they had a cache busting query parameter in the URL. Fixed some scenarios to ensure that the sourcemaps that were included by the vite dev server were inlined. Addressed in [#30606](https://github.com/cypress-io/cypress/pull/30606).

## 13.15.2

_Released 11/5/2024_
Expand Down
13 changes: 11 additions & 2 deletions npm/vite-dev-server/src/plugins/sourcemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ export const CypressSourcemap = (
enforce: 'post',
transform (code, id, options?) {
try {
if (/\.js$/i.test(id) && !/\/\/# sourceMappingURL=/i.test(code)) {
// Remove query parameters from the id. This is necessary because some files
// have a cache buster query parameter (e.g. `?v=12345`)
const queryParameterLessId = id.split('?')[0]

if (/\.js$/i.test(queryParameterLessId) && !/\/\/# sourceMappingURL=data/i.test(code)) {
mschile marked this conversation as resolved.
Show resolved Hide resolved
/*
The Vite dev server and plugins automatically generate sourcemaps for most files, but they are
only included in the served files if any transpilation actually occurred (JSX, TS, etc). This
Expand All @@ -35,8 +39,13 @@ export const CypressSourcemap = (
*/

const sourcemap = this.getCombinedSourcemap()
const sourcemapUrl = sourcemap.toUrl()

code += `\n//# sourceMappingURL=${sourcemap.toUrl()}`
if (/\/\/# sourceMappingURL=/i.test(code)) {
mschile marked this conversation as resolved.
Show resolved Hide resolved
code = code.replace(/\/\/# sourceMappingURL=(.*)$/m, `//# sourceMappingURL=${sourcemapUrl}`)
} else {
code += `\n//# sourceMappingURL=${sourcemapUrl}`
}

return {
code,
Expand Down
60 changes: 60 additions & 0 deletions npm/vite-dev-server/test/plugins/sourcemap.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Plugin } from 'vite-5'
import { ViteDevServerConfig } from '../../src/devServer'
import { Vite } from '../../src/getVite'
import { CypressSourcemap } from '../../src/plugins'
import Chai, { expect } from 'chai'
import SinonChai from 'sinon-chai'

Chai.use(SinonChai)

describe('sourcemap plugin', () => {
mschile marked this conversation as resolved.
Show resolved Hide resolved
it('should append sourcemap to the code if sourceMappingURL is not present', () => {
const code = 'console.log("hello world")'
const id = `test.js`
const options = {} as ViteDevServerConfig
const vite = {} as Vite
const plugin = CypressSourcemap(options, vite) as Plugin & { getCombinedSourcemap: () => { toUrl: () => string } }

plugin.getCombinedSourcemap = () => {
return {
toUrl: () => 'data:application/json;base64,eyJ2ZXJzaW9uIjozfQ==',
}
}

expect(plugin.name).to.equal('cypress:sourcemap')
expect(plugin.enforce).to.equal('post')

if (plugin.transform instanceof Function) {
const result = plugin.transform.call(plugin, code, id)

expect(result.code).to.eq('console.log("hello world")\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozfQ==')
} else {
throw new Error('transform is not a function')
}
})

it('should replace sourceMappingURL with sourcemap and handle query parameters', () => {
const code = 'console.log("hello world")\n//# sourceMappingURL=old-url'
const id = `test.js?v=12345`
const options = {} as ViteDevServerConfig
const vite = {} as Vite
const plugin = CypressSourcemap(options, vite) as Plugin & { getCombinedSourcemap: () => { toUrl: () => string } }

plugin.getCombinedSourcemap = () => {
return {
toUrl: () => 'data:application/json;base64,eyJ2ZXJzaW9uIjozfQ==',
}
}

expect(plugin.name).to.equal('cypress:sourcemap')
expect(plugin.enforce).to.equal('post')

if (plugin.transform instanceof Function) {
const result = plugin.transform.call(plugin, code, id)

expect(result.code).to.eq('console.log("hello world")\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozfQ==')
} else {
throw new Error('transform is not a function')
}
})
})
Loading