Skip to content

Commit e3f6ae1

Browse files
authored
fix(sourcemap): combine sourcemaps with multiple sources without matched source (#18971)
1 parent c9e086d commit e3f6ae1

File tree

2 files changed

+160
-8
lines changed

2 files changed

+160
-8
lines changed

packages/vite/src/node/__tests__/utils.spec.ts

+150
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import fs from 'node:fs'
22
import path from 'node:path'
33
import crypto from 'node:crypto'
44
import { describe, expect, test } from 'vitest'
5+
import { fileURLToPath } from 'mlly'
56
import {
67
asyncFlatten,
78
bareImportRE,
9+
combineSourcemaps,
810
extractHostnamesFromSubjectAltName,
911
flattenId,
1012
generateCodeFrame,
@@ -13,6 +15,7 @@ import {
1315
injectQuery,
1416
isFileReadable,
1517
mergeWithDefaults,
18+
normalizePath,
1619
posToNumber,
1720
processSrcSetSync,
1821
resolveHostname,
@@ -555,3 +558,150 @@ describe('mergeWithDefaults', () => {
555558
expect(actual2.array).not.toBe(defaults.array)
556559
})
557560
})
561+
562+
describe('combineSourcemaps', () => {
563+
const _dirname = path.dirname(fileURLToPath(import.meta.url))
564+
const resolveFile = (file: string) => {
565+
return normalizePath(path.resolve(_dirname, file))
566+
}
567+
568+
test('should combine sourcemaps with single sources', () => {
569+
const sourcemaps = [
570+
// processed with magic-string
571+
// https://evanw.github.io/source-map-visualization/#MzQALyogY29tbWVudCAqLwpjb25zb2xlLmxvZygiZm9vIik7CjE1NgB7InZlcnNpb24iOjMsInNvdXJjZXMiOlsiL3NyYy9mb28uanMiXSwic291cmNlc0NvbnRlbnQiOlsiY29uc29sZS5sb2coXCJmb29cIik7XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQzsifQo=
572+
{
573+
version: 3 as const,
574+
mappings: ';AAAA,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;',
575+
names: [],
576+
sources: [resolveFile('./src/foo.js')],
577+
},
578+
// processed with esbuild
579+
// https://evanw.github.io/source-map-visualization/#MjAAY29uc29sZS5sb2coImZvbyIpOwoxNDgAewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiL3NyYy9mb28uanMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnNvbGUubG9nKFwiZm9vXCIpIl0sCiAgIm1hcHBpbmdzIjogIkFBQUEsUUFBUSxJQUFJLEtBQUs7IiwKICAibmFtZXMiOiBbXQp9Cg==
580+
{
581+
version: 3 as const,
582+
mappings: 'AAAA,QAAQ,IAAI,KAAK;',
583+
names: [],
584+
sources: [resolveFile('./src/foo.js')],
585+
},
586+
]
587+
const combined = combineSourcemaps(resolveFile('./src/foo.js'), sourcemaps)
588+
expect(combined).toStrictEqual(
589+
expect.objectContaining({
590+
version: 3,
591+
file: resolveFile('./src/foo.js'),
592+
mappings: ';AAAA,QAAQ,IAAI,KAAK',
593+
sources: [resolveFile('./src/foo.js')],
594+
}),
595+
)
596+
})
597+
598+
test('should combine sourcemaps with multiple sources', () => {
599+
const sourcemaps = [
600+
// processed with magic-string
601+
// https://evanw.github.io/source-map-visualization/#NzcALyogY29tbWVudCAqLwovLyBiLmpzCmNvbnNvbGUubG9nKCIuL2IuanMiKTsKCi8vIGEuanMKY29uc29sZS5sb2coIi4vYS5qcyIpOwozMzkAeyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9hLmpzIl0sInNvdXJjZXNDb250ZW50IjpbIi8vIGIuanNcbmNvbnNvbGUubG9nKFwiLi9iLmpzXCIpO1xuXG4vLyBhLmpzXG5jb25zb2xlLmxvZyhcIi4vYS5qc1wiKTtcblxuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7T0FDRSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7O0FBRXJCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztPQUNFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQzs7In0K
602+
{
603+
version: 3 as const,
604+
mappings:
605+
';AAAA,CAAC,CAAC,CAAC,CAAC,CAAC;OACE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;;AAErB,CAAC,CAAC,CAAC,CAAC,CAAC;OACE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;;',
606+
names: [],
607+
sources: [resolveFile('./src/a.js')],
608+
},
609+
// processed with esbuild
610+
// https://evanw.github.io/source-map-visualization/#NjMALy8gYi5qcwpjb25zb2xlLmxvZygiLi9iLmpzIik7CgovLyBhLmpzCmNvbnNvbGUubG9nKCIuL2EuanMiKTsKMjIwAHsKICAidmVyc2lvbiI6IDMsCiAgInNvdXJjZXMiOiBbImIuanMiLCAiYS5qcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiY29uc29sZS5sb2coJy4vYi5qcycpXG4iLCAiaW1wb3J0ICcuL2IuanMnXG5jb25zb2xlLmxvZygnLi9hLmpzJylcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBQSxRQUFRLElBQUksUUFBUTs7O0FDQ3BCLFFBQVEsSUFBSSxRQUFROyIsCiAgIm5hbWVzIjogW10KfQo=
611+
{
612+
version: 3 as const,
613+
mappings: ';AAAA,QAAQ,IAAI,QAAQ;;;ACCpB,QAAQ,IAAI,QAAQ;',
614+
names: [],
615+
sources: [resolveFile('./src/b.js'), resolveFile('./src/a.js')],
616+
},
617+
]
618+
const combined = combineSourcemaps(resolveFile('./src/a.js'), sourcemaps)
619+
expect(combined).toStrictEqual(
620+
expect.objectContaining({
621+
version: 3,
622+
file: resolveFile('./src/a.js'),
623+
mappings: ';;OAAA,CAAQ,IAAI,QAAQ;;;OCCpB,CAAQ,IAAI,QAAQ',
624+
sources: [resolveFile('./src/b.js'), resolveFile('./src/a.js')],
625+
}),
626+
)
627+
})
628+
629+
test('should combine sourcemaps with multiple sources 2', () => {
630+
const sourcemaps = [
631+
// processed with sass
632+
// https://evanw.github.io/source-map-visualization/#NTYALmltcG9ydGVkMiB7CiAgY29sb3I6IHJlZAp9CgouaW1wb3J0ZWQgewogIGNvbG9yOiByZWQKfQoyNzAAewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsKICAgICIvaW1wb3J0ZWQyLnNhc3MiLAogICAgIi9Gb28udnVlIgogIF0sCiAgIm5hbWVzIjogW10sCiAgIm1hcHBpbmdzIjogIkFBQUE7RUFDRTs7O0FDRUY7RUFDRSIsCiAgImZpbGUiOiAiL0Zvby52dWUiLAogICJzb3VyY2VzQ29udGVudCI6IFsKICAgICIuaW1wb3J0ZWQyXG4gIGNvbG9yOiByZWRcbiIsCiAgICAiXG5AdXNlICcuL2ltcG9ydGVkMidcblxuLmltcG9ydGVkXG4gIGNvbG9yOiByZWRcbiIKICBdCn0K
633+
{
634+
version: 3 as const,
635+
file: resolveFile('./src/Foo.vue'),
636+
mappings: 'AAAA;EACE;;;ACEF;EACE',
637+
names: [],
638+
sources: [
639+
resolveFile('./src/imported2.sass'),
640+
resolveFile('./src/Foo.vue'),
641+
],
642+
},
643+
// processed with vue
644+
// https://evanw.github.io/source-map-visualization/#NDQACkB1c2UgJy4vaW1wb3J0ZWQyJwoKLmltcG9ydGVkCiAgY29sb3I6IHJlZAo0OTQAewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsKICAgICIvRm9vLnZ1ZSIKICBdLAogICJuYW1lcyI6IFtdLAogICJtYXBwaW5ncyI6ICI7QUFNQSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDOztBQUVqQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7RUFDTixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMiLAogICJmaWxlIjogIi9Gb28udnVlIiwKICAic291cmNlc0NvbnRlbnQiOiBbCiAgICAiPHRlbXBsYXRlPlxuICA8cCBjbGFzcz1cImltcG9ydGVkXCI+Zm9vPC9wPlxuICA8cCBjbGFzcz1cImltcG9ydGVkMlwiPmJhcjwvcD5cbjwvdGVtcGxhdGU+XG5cbjxzdHlsZSBsYW5nPVwic2Fzc1wiPlxuQHVzZSAnLi9pbXBvcnRlZDInXG5cbi5pbXBvcnRlZFxuICBjb2xvcjogcmVkXG48L3N0eWxlPlxuIgogIF0KfQo=
645+
{
646+
version: 3 as const,
647+
file: resolveFile('./src/Foo.vue'),
648+
sources: [resolveFile('./src/Foo.vue')],
649+
names: [],
650+
mappings:
651+
';AAMA,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;;AAEjB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;EACN,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC',
652+
},
653+
]
654+
const combined = combineSourcemaps(resolveFile('./src/Foo.vue'), sourcemaps)
655+
expect(combined).toStrictEqual(
656+
expect.objectContaining({
657+
version: 3,
658+
file: resolveFile('./src/Foo.vue'),
659+
mappings: 'AAAA;EACE;;;ACOF;EACE',
660+
sources: [
661+
resolveFile('./src/imported2.sass'),
662+
resolveFile('./src/Foo.vue'),
663+
],
664+
}),
665+
)
666+
})
667+
668+
test('should combine sourcemaps with multiple sources without matched source', () => {
669+
const sourcemaps = [
670+
// processed with postcss
671+
// https://evanw.github.io/source-map-visualization/#NjYALmZvbyB7CiAgb3ZlcmZsb3c6IHNjcm9sbDsKICAtd2Via2l0LW92ZXJmbG93LXNjcm9sbGluZzogdG91Y2g7Cn0KMjU1AHsKICAiZmlsZSI6ICIvc3JjL3Nhc3Mvc3R5bGUuc2FzcyIsCiAgIm1hcHBpbmdzIjogIkFBQUE7RUFDRSxnQkFBZ0I7RUNEbEIsa0NBQUE7QURFQSIsCiAgIm5hbWVzIjogW10sCiAgInNvdXJjZXMiOiBbCiAgICAiL3NyYy9zYXNzL3N0eWxlLnNhc3MiLAogICAgIlx1MDAwMDxubyBzb3VyY2U+IgogIF0sCiAgInNvdXJjZXNDb250ZW50IjogWyAiLmZvbyB7XG4gIG92ZXJmbG93OiBzY3JvbGw7XG59IiwgbnVsbCBdLAogICJ2ZXJzaW9uIjogMwp9Cg==
672+
{
673+
version: 3 as const,
674+
file: resolveFile('./src/sass/style.sass'),
675+
mappings: 'AAAA;EACE,gBAAgB;ECDlB,kCAAA;ADEA',
676+
names: [],
677+
sources: [
678+
resolveFile('./src/sass/style.sass'),
679+
'\x00<no source>', // postcss virtual file
680+
],
681+
},
682+
// processed with sass
683+
// https://evanw.github.io/source-map-visualization/#MjkALmZvbyB7CiAgb3ZlcmZsb3c6IHNjcm9sbDsKfQoyMDcAewogICJmaWxlIjogIi9zcmMvc2Fzcy9zdHlsZS5zYXNzIiwKICAibWFwcGluZ3MiOiAiQUFBQTtFQUNFIiwKICAibmFtZXMiOiBbXSwKICAic291cmNlcyI6IFsKICAgICIvc3JjL3Nhc3MvdmVuZG9yL2luZGV4LnNjc3MiCiAgXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi5mb28ge1xuICBvdmVyZmxvdzogc2Nyb2xsO1xufVxuIl0sCiAgInZlcnNpb24iOiAzCn0K
684+
{
685+
version: 3 as const,
686+
sources: [resolveFile('./src/sass/vendor/index.scss')],
687+
names: [],
688+
mappings: 'AAAA;EACE',
689+
},
690+
]
691+
const combined = combineSourcemaps(
692+
resolveFile('./src/sass/style.sass'),
693+
sourcemaps,
694+
)
695+
expect(combined).toStrictEqual(
696+
expect.objectContaining({
697+
version: 3,
698+
file: resolveFile('./src/sass/style.sass'),
699+
mappings: 'AAAA;EACE;ECDF',
700+
sources: [
701+
resolveFile('./src/sass/vendor/index.scss'),
702+
'\x00<no source>',
703+
],
704+
}),
705+
)
706+
})
707+
})

packages/vite/src/node/utils.ts

+10-8
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,10 @@ const nullSourceMap: RawSourceMap = {
851851
mappings: '',
852852
version: 3,
853853
}
854+
/**
855+
* Combines multiple sourcemaps into a single sourcemap.
856+
* Note that the length of sourcemapList must be 2.
857+
*/
854858
export function combineSourcemaps(
855859
filename: string,
856860
sourcemapList: Array<DecodedSourceMap | RawSourceMap>,
@@ -875,6 +879,7 @@ export function combineSourcemaps(
875879
}
876880
return newSourcemaps
877881
})
882+
const escapedFilename = escapeToLinuxLikePath(filename)
878883

879884
// We don't declare type here so we can convert/fake/map as RawSourceMap
880885
let map //: SourceMap
@@ -885,15 +890,12 @@ export function combineSourcemaps(
885890
map = remapping(sourcemapList, () => null)
886891
} else {
887892
map = remapping(sourcemapList[0], function loader(sourcefile) {
888-
const mapForSources = sourcemapList
889-
.slice(mapIndex)
890-
.find((s) => s.sources.includes(sourcefile))
891-
892-
if (mapForSources) {
893-
mapIndex++
894-
return mapForSources
893+
// this line assumes that the length of the sourcemapList is 2
894+
if (sourcefile === escapedFilename && sourcemapList[mapIndex]) {
895+
return sourcemapList[mapIndex++]
896+
} else {
897+
return null
895898
}
896-
return null
897899
})
898900
}
899901
if (!map.file) {

0 commit comments

Comments
 (0)