Skip to content

Commit 35aef3e

Browse files
authored
fix(core): fall back to AST for unusable sourcemaps (#1934)
1 parent f81541a commit 35aef3e

2 files changed

Lines changed: 103 additions & 18 deletions

File tree

packages/core/src/inner-plugins/plugins/sourcemapTool.ts

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,15 @@ export async function collectSourceMaps(
213213
}
214214
}
215215

216+
function markAssetWithoutSourceMap(
217+
assetsWithoutSourceMap: Set<string>,
218+
assetName: unknown,
219+
) {
220+
if (typeof assetName === 'string' && /\.(?:js|bundle|css)$/.test(assetName)) {
221+
assetsWithoutSourceMap.add(assetName);
222+
}
223+
}
224+
216225
/**
217226
* Handles the afterEmit event for assets to collect source map information.
218227
* @param compilation - The current compilation object.
@@ -280,25 +289,29 @@ export async function handleAfterEmitAssets(
280289
});
281290
}
282291

283-
if (sourceMapAsset?.source) {
284-
map = JSON.parse(sourceMapAsset.source.source().toString());
285-
const outputPath = compilation.options.output?.path;
286-
if (outputPath && typeof outputPath === 'string') {
287-
sourceMapPath = resolve(outputPath, sourceMapAsset.name);
292+
try {
293+
const sourceMapContent = sourceMapAsset?.source?.source();
294+
if (sourceMapContent) {
295+
map = JSON.parse(sourceMapContent.toString());
288296
}
297+
} catch (error) {
298+
logger.debug(
299+
`Error parsing source map asset ${sourceMapFile}:`,
300+
error,
301+
);
289302
}
290-
} else {
291-
// Mark asset as having no sourcemap for AST parsing fallback
292-
// Only mark js/css files that don't have sourcemap
293-
if (
294-
assetName &&
295-
typeof assetName === 'string' &&
296-
(assetName.endsWith('.js') ||
297-
assetName.endsWith('.bundle') ||
298-
assetName.endsWith('.css'))
299-
) {
300-
_this.assetsWithoutSourceMap.add(assetName);
303+
304+
if (!map) {
305+
markAssetWithoutSourceMap(_this.assetsWithoutSourceMap, assetName);
306+
continue;
301307
}
308+
309+
const outputPath = compilation.options.output?.path;
310+
if (outputPath && typeof outputPath === 'string' && sourceMapAsset) {
311+
sourceMapPath = resolve(outputPath, sourceMapAsset.name);
312+
}
313+
} else {
314+
markAssetWithoutSourceMap(_this.assetsWithoutSourceMap, assetName);
302315
continue;
303316
}
304317
} else {
@@ -324,6 +337,7 @@ export async function handleAfterEmitAssets(
324337
);
325338
} catch (e) {
326339
logger.debug(e);
340+
markAssetWithoutSourceMap(_this.assetsWithoutSourceMap, assetName);
327341
}
328342
_this.sourceMapSets.forEach((_value: string, key: string) => {
329343
if (!skipSources.has(key)) {

packages/core/tests/plugins/sourcemapTool.test.ts

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ describe('sourcemapTool', () => {
321321
expect(sourceMap).toBe('console.log("test");');
322322
});
323323

324-
it('should skip source map assets without a source', async () => {
324+
it('should fall back to AST parsing when a related source map has no source', async () => {
325325
const plugin = createMockPluginInstance();
326326
const compilation = {
327327
compiler: { rspack: {} },
@@ -331,6 +331,18 @@ describe('sourcemapTool', () => {
331331
},
332332
},
333333
getAssets: () => [
334+
{
335+
name: 'main.js',
336+
source: {
337+
source: () => 'console.log("test");\n',
338+
name: 'main.js',
339+
sourceAndMap: () => ({
340+
source: 'console.log("test");\n',
341+
map: mockJsMap,
342+
}),
343+
},
344+
info: {},
345+
},
334346
{
335347
name: 'worker.abc123.js',
336348
source: {
@@ -355,7 +367,66 @@ describe('sourcemapTool', () => {
355367
} as any;
356368

357369
await handleAfterEmitAssets(compilation, plugin);
358-
expect(plugin.sourceMapSets.size).toBe(0);
370+
expect(plugin.sourceMapSets.size).toBe(1);
371+
expect(plugin.assetsWithoutSourceMap).toEqual(
372+
new Set(['worker.abc123.js']),
373+
);
374+
});
375+
376+
it('should fall back to AST parsing when a related source map is invalid', async () => {
377+
const plugin = createMockPluginInstance();
378+
const compilation = {
379+
compiler: { rspack: {} },
380+
options: {
381+
output: {
382+
filename: '[name].[contenthash].js',
383+
},
384+
},
385+
getAssets: () => [
386+
{
387+
name: 'main.js',
388+
source: {
389+
source: () => 'console.log("test");\n',
390+
name: 'main.js',
391+
sourceAndMap: () => ({
392+
source: 'console.log("test");\n',
393+
map: mockJsMap,
394+
}),
395+
},
396+
info: {},
397+
},
398+
{
399+
name: 'worker.abc123.js',
400+
source: {
401+
source: () => 'console.log("worker");\n',
402+
name: 'worker.abc123.js',
403+
sourceAndMap: () => ({
404+
source: 'console.log("worker");\n',
405+
map: null,
406+
}),
407+
},
408+
info: {
409+
related: {
410+
sourceMap: 'worker.abc123.js.map',
411+
},
412+
},
413+
},
414+
{
415+
name: 'worker.abc123.js.map',
416+
source: {
417+
source: () => '{}',
418+
name: 'worker.abc123.js.map',
419+
},
420+
info: {},
421+
},
422+
],
423+
} as any;
424+
425+
await handleAfterEmitAssets(compilation, plugin);
426+
expect(plugin.sourceMapSets.size).toBe(1);
427+
expect(plugin.assetsWithoutSourceMap).toEqual(
428+
new Set(['worker.abc123.js']),
429+
);
359430
});
360431

361432
it('should find source map by base name without hash when exact match fails', async () => {

0 commit comments

Comments
 (0)