diff --git a/packages/webpack5/__tests__/plugins/FixSourceMapUrlPlugin.spec.ts b/packages/webpack5/__tests__/plugins/FixSourceMapUrlPlugin.spec.ts new file mode 100644 index 0000000000..55b471880b --- /dev/null +++ b/packages/webpack5/__tests__/plugins/FixSourceMapUrlPlugin.spec.ts @@ -0,0 +1,75 @@ +import { resolve } from 'path'; +import { pathToFileURL } from 'url'; + +import FixSourceMapUrlPlugin from '../../src/plugins/FixSourceMapUrlPlugin'; + +function createCompiler() { + let emitHandler: ((compilation: any) => void) | undefined; + + return { + compiler: { + hooks: { + emit: { + tap(_name: string, handler: (compilation: any) => void) { + emitHandler = handler; + }, + }, + }, + } as any, + run(compilation: any) { + if (!emitHandler) { + throw new Error('Expected emit hook to be registered.'); + } + + emitHandler(compilation); + }, + }; +} + +function createCompilation(source: string) { + return { + assets: { + 'bundle.js': { + source: () => source, + size: () => source.length, + }, + }, + }; +} + +describe('FixSourceMapUrlPlugin', () => { + it('encodes spaces in rewritten source map urls', () => { + const outputPath = '/Users/test/my tns app/platforms/android/dist'; + const { compiler, run } = createCompiler(); + + new FixSourceMapUrlPlugin({ outputPath }).apply(compiler); + + const compilation = createCompilation( + 'console.log("test");\n//# sourceMappingURL=bundle.js.map', + ); + run(compilation); + + expect(compilation.assets['bundle.js'].source()).toContain( + `//# sourceMappingURL=${pathToFileURL(resolve(outputPath, 'bundle.js.map')).toString()}`, + ); + expect(compilation.assets['bundle.js'].source()).toContain('%20'); + }); + + it('leaves absolute source map urls unchanged', () => { + const outputPath = '/Users/test/my tns app/platforms/android/dist'; + const existingUrl = + 'file:///Users/test/my%20tns%20app/platforms/android/dist/bundle.js.map'; + const { compiler, run } = createCompiler(); + + new FixSourceMapUrlPlugin({ outputPath }).apply(compiler); + + const compilation = createCompilation( + `console.log("test");\n//# sourceMappingURL=${existingUrl}`, + ); + run(compilation); + + expect(compilation.assets['bundle.js'].source()).toContain( + `//# sourceMappingURL=${existingUrl}`, + ); + }); +}); diff --git a/packages/webpack5/src/plugins/FixSourceMapUrlPlugin.ts b/packages/webpack5/src/plugins/FixSourceMapUrlPlugin.ts index 95052a85a0..1ae51b2d06 100644 --- a/packages/webpack5/src/plugins/FixSourceMapUrlPlugin.ts +++ b/packages/webpack5/src/plugins/FixSourceMapUrlPlugin.ts @@ -1,3 +1,5 @@ +import { resolve } from 'path'; +import { pathToFileURL } from 'url'; import type { Compiler } from 'webpack'; import { sources } from 'webpack'; @@ -18,7 +20,15 @@ export default class FixSourceMapUrlPlugin { !!wp?.Compilation?.PROCESS_ASSETS_STAGE_DEV_TOOLING && !!(compiler as any).hooks?.thisCompilation; - const leadingCharacter = process.platform === 'win32' ? '/' : ''; + const getSourceMapUrl = (sourceMapPath: string): string => { + if (/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(sourceMapPath)) { + return sourceMapPath; + } + + return pathToFileURL( + resolve(this.options.outputPath, sourceMapPath), + ).toString(); + }; const toStringContent = (content: any): string => { if (typeof content === 'string') return content; @@ -65,8 +75,9 @@ export default class FixSourceMapUrlPlugin { let source = toStringContent(rawSource); // Replace sourceMappingURL to use file:// protocol pointing to actual location source = source.replace( - /\/\/\# sourceMappingURL=(.+\.map)/g, - `//# sourceMappingURL=file://${leadingCharacter}${this.options.outputPath}/$1`, + /\/\/\# sourceMappingURL=(.+\.map(?:\?[^\s]*)?)/g, + (_match, sourceMapPath: string) => + `//# sourceMappingURL=${getSourceMapUrl(sourceMapPath)}`, ); // Prefer Webpack 5 updateAsset with RawSource when available