diff --git a/npm/vite-dev-server/src/plugins/sourcemap.ts b/npm/vite-dev-server/src/plugins/sourcemap.ts index 7111e2b54376..22b196f4494f 100644 --- a/npm/vite-dev-server/src/plugins/sourcemap.ts +++ b/npm/vite-dev-server/src/plugins/sourcemap.ts @@ -15,7 +15,9 @@ export const CypressSourcemap = ( enforce: 'post', transform (code, id, options?) { try { - if (/\.js$/i.test(id) && !/\/\/# sourceMappingURL=/i.test(code)) { + const queryParameterLessId = id.split('?')[0] + + if (/\.(js|jsx|ts|tsx|vue|mjs|cjs)$/i.test(queryParameterLessId) && !/\/\/# sourceMappingURL=data/i.test(code)) { /* 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 @@ -36,7 +38,11 @@ export const CypressSourcemap = ( const sourcemap = this.getCombinedSourcemap() - code += `\n//# sourceMappingURL=${sourcemap.toUrl()}` + if (/\/\/# sourceMappingURL=/i.test(code)) { + code = code.replace(/\/\/# sourceMappingURL=(.*)$/m, `//# sourceMappingURL=${sourcemap.toUrl()}`) + } else { + code += `\n//# sourceMappingURL=${sourcemap.toUrl()}` + } return { code, diff --git a/npm/vite-dev-server/test/plugins/sourcemap.spec.ts b/npm/vite-dev-server/test/plugins/sourcemap.spec.ts new file mode 100644 index 000000000000..85fbc005c71e --- /dev/null +++ b/npm/vite-dev-server/test/plugins/sourcemap.spec.ts @@ -0,0 +1,62 @@ +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', () => { + ['js', 'jsx', 'ts', 'tsx', 'vue', 'mjs', 'cjs'].forEach((ext) => { + it('should append sourcemap to the code if sourceMappingURL is not present', () => { + const code = 'console.log("hello world")' + const id = `test.${ext}` + 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.include('//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozfQ==') + } else { + throw new Error('transform is not a function') + } + }) + + it('should replace sourceMappingURL with sourcemap', () => { + const code = 'console.log("hello world")\n//# sourceMappingURL=old-url' + const id = `test.${ext}` + 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.include('//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozfQ==') + } else { + throw new Error('transform is not a function') + } + }) + }) +})