diff --git a/packages/core/src/inner-plugins/plugins/sourcemapTool.ts b/packages/core/src/inner-plugins/plugins/sourcemapTool.ts index 457816d01..5d96b9884 100644 --- a/packages/core/src/inner-plugins/plugins/sourcemapTool.ts +++ b/packages/core/src/inner-plugins/plugins/sourcemapTool.ts @@ -213,6 +213,15 @@ export async function collectSourceMaps( } } +function markAssetWithoutSourceMap( + assetsWithoutSourceMap: Set, + assetName: unknown, +) { + if (typeof assetName === 'string' && /\.(?:js|bundle|css)$/.test(assetName)) { + assetsWithoutSourceMap.add(assetName); + } +} + /** * Handles the afterEmit event for assets to collect source map information. * @param compilation - The current compilation object. @@ -280,25 +289,29 @@ export async function handleAfterEmitAssets( }); } - if (sourceMapAsset?.source) { - map = JSON.parse(sourceMapAsset.source.source().toString()); - const outputPath = compilation.options.output?.path; - if (outputPath && typeof outputPath === 'string') { - sourceMapPath = resolve(outputPath, sourceMapAsset.name); + try { + const sourceMapContent = sourceMapAsset?.source?.source(); + if (sourceMapContent) { + map = JSON.parse(sourceMapContent.toString()); } + } catch (error) { + logger.debug( + `Error parsing source map asset ${sourceMapFile}:`, + error, + ); } - } else { - // Mark asset as having no sourcemap for AST parsing fallback - // Only mark js/css files that don't have sourcemap - if ( - assetName && - typeof assetName === 'string' && - (assetName.endsWith('.js') || - assetName.endsWith('.bundle') || - assetName.endsWith('.css')) - ) { - _this.assetsWithoutSourceMap.add(assetName); + + if (!map) { + markAssetWithoutSourceMap(_this.assetsWithoutSourceMap, assetName); + continue; } + + const outputPath = compilation.options.output?.path; + if (outputPath && typeof outputPath === 'string' && sourceMapAsset) { + sourceMapPath = resolve(outputPath, sourceMapAsset.name); + } + } else { + markAssetWithoutSourceMap(_this.assetsWithoutSourceMap, assetName); continue; } } else { @@ -324,6 +337,7 @@ export async function handleAfterEmitAssets( ); } catch (e) { logger.debug(e); + markAssetWithoutSourceMap(_this.assetsWithoutSourceMap, assetName); } _this.sourceMapSets.forEach((_value: string, key: string) => { if (!skipSources.has(key)) { diff --git a/packages/core/tests/plugins/sourcemapTool.test.ts b/packages/core/tests/plugins/sourcemapTool.test.ts index 91c20c3cc..c31e56f96 100644 --- a/packages/core/tests/plugins/sourcemapTool.test.ts +++ b/packages/core/tests/plugins/sourcemapTool.test.ts @@ -321,7 +321,7 @@ describe('sourcemapTool', () => { expect(sourceMap).toBe('console.log("test");'); }); - it('should skip source map assets without a source', async () => { + it('should fall back to AST parsing when a related source map has no source', async () => { const plugin = createMockPluginInstance(); const compilation = { compiler: { rspack: {} }, @@ -331,6 +331,18 @@ describe('sourcemapTool', () => { }, }, getAssets: () => [ + { + name: 'main.js', + source: { + source: () => 'console.log("test");\n', + name: 'main.js', + sourceAndMap: () => ({ + source: 'console.log("test");\n', + map: mockJsMap, + }), + }, + info: {}, + }, { name: 'worker.abc123.js', source: { @@ -355,7 +367,66 @@ describe('sourcemapTool', () => { } as any; await handleAfterEmitAssets(compilation, plugin); - expect(plugin.sourceMapSets.size).toBe(0); + expect(plugin.sourceMapSets.size).toBe(1); + expect(plugin.assetsWithoutSourceMap).toEqual( + new Set(['worker.abc123.js']), + ); + }); + + it('should fall back to AST parsing when a related source map is invalid', async () => { + const plugin = createMockPluginInstance(); + const compilation = { + compiler: { rspack: {} }, + options: { + output: { + filename: '[name].[contenthash].js', + }, + }, + getAssets: () => [ + { + name: 'main.js', + source: { + source: () => 'console.log("test");\n', + name: 'main.js', + sourceAndMap: () => ({ + source: 'console.log("test");\n', + map: mockJsMap, + }), + }, + info: {}, + }, + { + name: 'worker.abc123.js', + source: { + source: () => 'console.log("worker");\n', + name: 'worker.abc123.js', + sourceAndMap: () => ({ + source: 'console.log("worker");\n', + map: null, + }), + }, + info: { + related: { + sourceMap: 'worker.abc123.js.map', + }, + }, + }, + { + name: 'worker.abc123.js.map', + source: { + source: () => '{}', + name: 'worker.abc123.js.map', + }, + info: {}, + }, + ], + } as any; + + await handleAfterEmitAssets(compilation, plugin); + expect(plugin.sourceMapSets.size).toBe(1); + expect(plugin.assetsWithoutSourceMap).toEqual( + new Set(['worker.abc123.js']), + ); }); it('should find source map by base name without hash when exact match fails', async () => {