Skip to content

Commit 7d32f49

Browse files
authored
Switch to @ampproject/remapping to merge source maps (#14209)
1 parent 6b427ce commit 7d32f49

8 files changed

Lines changed: 61 additions & 329 deletions

File tree

packages/babel-cli/test/fixtures/babel/filename-sourcemap --out-file --source-maps inline/out-files/script2.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/babel-core/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"./src/transformation/util/clone-deep.ts": "./src/transformation/util/clone-deep-browser.ts"
4949
},
5050
"dependencies": {
51+
"@ampproject/remapping": "^2.0.0",
5152
"@babel/code-frame": "workspace:^",
5253
"@babel/generator": "workspace:^",
5354
"@babel/helper-compilation-targets": "workspace:^",
@@ -61,8 +62,7 @@
6162
"debug": "^4.1.0",
6263
"gensync": "^1.0.0-beta.2",
6364
"json5": "^2.1.2",
64-
"semver": "condition:BABEL_8_BREAKING ? ^7.3.4 : ^6.3.0",
65-
"source-map": "^0.5.0"
65+
"semver": "condition:BABEL_8_BREAKING ? ^7.3.4 : ^6.3.0"
6666
},
6767
"devDependencies": {
6868
"@babel/helper-transform-fixture-test-runner": "workspace:^",
@@ -71,7 +71,8 @@
7171
"@types/debug": "^4.1.0",
7272
"@types/resolve": "^1.3.2",
7373
"@types/semver": "^5.4.0",
74-
"@types/source-map": "^0.5.0"
74+
"@types/source-map": "^0.5.0",
75+
"source-map": "0.6.1"
7576
},
7677
"conditions": {
7778
"BABEL_8_BREAKING": [
Lines changed: 9 additions & 305 deletions
Original file line numberDiff line numberDiff line change
@@ -1,321 +1,25 @@
11
type SourceMap = any;
2-
import sourceMap from "source-map";
2+
import remapping from "@ampproject/remapping";
33

44
export default function mergeSourceMap(
55
inputMap: SourceMap,
66
map: SourceMap,
77
): SourceMap {
8-
const input = buildMappingData(inputMap);
9-
const output = buildMappingData(map);
8+
const result = remapping([rootless(map), rootless(inputMap)], () => null);
109

11-
const mergedGenerator = new sourceMap.SourceMapGenerator();
12-
for (const { source } of input.sources) {
13-
if (typeof source.content === "string") {
14-
mergedGenerator.setSourceContent(source.path, source.content);
15-
}
16-
}
17-
18-
if (output.sources.length === 1) {
19-
const defaultSource = output.sources[0];
20-
21-
const insertedMappings = new Map();
22-
23-
// Process each generated range in the input map, e.g. each range over the
24-
// code that Babel was originally given.
25-
eachInputGeneratedRange(input, (generated, original, source) => {
26-
// Then pick out each range over Babel's _output_ that corresponds with
27-
// the given range on the code given to Babel.
28-
eachOverlappingGeneratedOutputRange(defaultSource, generated, item => {
29-
// It's possible that multiple input ranges will overlap the same
30-
// generated range. Since sourcemap don't traditionally represent
31-
// generated locations with multiple original locations, we explicitly
32-
// skip generated locations once we've seen them the first time.
33-
const key = makeMappingKey(item);
34-
if (insertedMappings.has(key)) return;
35-
insertedMappings.set(key, item);
36-
37-
mergedGenerator.addMapping({
38-
source: source.path,
39-
original: {
40-
line: original.line,
41-
column: original.columnStart,
42-
},
43-
generated: {
44-
line: item.line,
45-
column: item.columnStart,
46-
},
47-
name: original.name,
48-
});
49-
});
50-
});
51-
52-
// Since mappings are manipulated using single locations, but are interpreted
53-
// as ranges, the insertions above may not actually have their ending
54-
// locations mapped yet. Here be go through each one and ensure that it has
55-
// a well-defined ending location, if one wasn't already created by the start
56-
// of a different range.
57-
for (const item of insertedMappings.values()) {
58-
if (item.columnEnd === Infinity) {
59-
continue;
60-
}
61-
62-
const clearItem = {
63-
line: item.line,
64-
columnStart: item.columnEnd,
65-
};
66-
67-
const key = makeMappingKey(clearItem);
68-
if (insertedMappings.has(key)) {
69-
continue;
70-
}
71-
72-
// Insert mappings with no original position to terminate any mappings
73-
// that were found above, so that they don't expand beyond their correct
74-
// range.
75-
// @ts-expect-error todo(flow->ts) original and source field are missing
76-
mergedGenerator.addMapping({
77-
generated: {
78-
line: clearItem.line,
79-
column: clearItem.columnStart,
80-
},
81-
});
82-
}
83-
}
84-
85-
const result = mergedGenerator.toJSON();
86-
// addMapping expects a relative path, and setSourceContent expects an
87-
// absolute path. To avoid this whole confusion, we leave the root out
88-
// entirely, and add it at the end here.
89-
if (typeof input.sourceRoot === "string") {
90-
result.sourceRoot = input.sourceRoot;
10+
if (typeof inputMap.sourceRoot === "string") {
11+
result.sourceRoot = inputMap.sourceRoot;
9112
}
9213
return result;
9314
}
9415

95-
function makeMappingKey(item: { line: number; columnStart: number }) {
96-
return `${item.line}/${item.columnStart}`;
97-
}
98-
99-
function eachOverlappingGeneratedOutputRange(
100-
outputFile: ResolvedFileMappings,
101-
inputGeneratedRange: ResolvedGeneratedRange,
102-
callback: (range: ResolvedGeneratedRange) => unknown,
103-
) {
104-
// Find the Babel-generated mappings that overlap with this range in the
105-
// input sourcemap. Generated locations within the input sourcemap
106-
// correspond with the original locations in the map Babel generates.
107-
const overlappingOriginal = filterApplicableOriginalRanges(
108-
outputFile,
109-
inputGeneratedRange,
110-
);
111-
112-
for (const { generated } of overlappingOriginal) {
113-
for (const item of generated) {
114-
callback(item);
115-
}
116-
}
117-
}
118-
119-
function filterApplicableOriginalRanges(
120-
{ mappings }: ResolvedFileMappings,
121-
{ line, columnStart, columnEnd }: ResolvedGeneratedRange,
122-
): OriginalMappings {
123-
// The mapping array is sorted by original location, so we can
124-
// binary-search it for the overlapping ranges.
125-
return filterSortedArray(mappings, ({ original: outOriginal }) => {
126-
if (line > outOriginal.line) return -1;
127-
if (line < outOriginal.line) return 1;
128-
129-
if (columnStart >= outOriginal.columnEnd) return -1;
130-
if (columnEnd <= outOriginal.columnStart) return 1;
131-
132-
return 0;
133-
});
134-
}
135-
136-
function eachInputGeneratedRange(
137-
map: ResolvedMappings,
138-
callback: (
139-
c: ResolvedGeneratedRange,
140-
b: ResolvedOriginalRange,
141-
a: ResolvedSource,
142-
) => unknown,
143-
) {
144-
for (const { source, mappings } of map.sources) {
145-
for (const { original, generated } of mappings) {
146-
for (const item of generated) {
147-
callback(item, original, source);
148-
}
149-
}
150-
}
151-
}
152-
153-
type ResolvedMappings = {
154-
file: string | undefined | null;
155-
sourceRoot: string | undefined | null;
156-
sources: Array<ResolvedFileMappings>;
157-
};
158-
159-
type ResolvedFileMappings = {
160-
source: ResolvedSource;
161-
mappings: OriginalMappings;
162-
};
163-
164-
type OriginalMappings = Array<{
165-
original: ResolvedOriginalRange;
166-
generated: Array<ResolvedGeneratedRange>;
167-
}>;
168-
169-
type ResolvedSource = {
170-
path: string;
171-
content: string | null;
172-
};
173-
174-
type ResolvedOriginalRange = {
175-
line: number;
176-
columnStart: number;
177-
columnEnd: number;
178-
name: string | null;
179-
};
180-
181-
type ResolvedGeneratedRange = {
182-
line: number;
183-
columnStart: number;
184-
columnEnd: number;
185-
};
186-
187-
function buildMappingData(map: SourceMap): ResolvedMappings {
188-
const consumer = new sourceMap.SourceMapConsumer({
16+
function rootless(map: SourceMap): SourceMap {
17+
return {
18918
...map,
19019

191-
// This is a bit hack. .addMapping expects source values to be relative,
192-
// but eachMapping returns mappings with absolute paths. To avoid that
193-
// incompatibility, we leave the sourceRoot out here and add it to the
194-
// final map at the end instead.
20+
// This is a bit hack. Remapping will create absolute sources in our
21+
// sourcemap, but we want to maintain sources relative to the sourceRoot.
22+
// We'll re-add the sourceRoot after remapping.
19523
sourceRoot: null,
196-
});
197-
198-
const sources = new Map();
199-
const mappings = new Map();
200-
201-
let last = null;
202-
203-
consumer.computeColumnSpans();
204-
205-
consumer.eachMapping(
206-
m => {
207-
if (m.originalLine === null) return;
208-
209-
let source = sources.get(m.source);
210-
if (!source) {
211-
source = {
212-
path: m.source,
213-
content: consumer.sourceContentFor(m.source, true),
214-
};
215-
sources.set(m.source, source);
216-
}
217-
218-
let sourceData = mappings.get(source);
219-
if (!sourceData) {
220-
sourceData = {
221-
source,
222-
mappings: [],
223-
};
224-
mappings.set(source, sourceData);
225-
}
226-
227-
const obj = {
228-
line: m.originalLine,
229-
columnStart: m.originalColumn,
230-
columnEnd: Infinity,
231-
name: m.name,
232-
};
233-
234-
if (
235-
last &&
236-
last.source === source &&
237-
last.mapping.line === m.originalLine
238-
) {
239-
last.mapping.columnEnd = m.originalColumn;
240-
}
241-
242-
last = {
243-
source,
244-
mapping: obj,
245-
};
246-
247-
sourceData.mappings.push({
248-
original: obj,
249-
generated: consumer
250-
.allGeneratedPositionsFor({
251-
source: m.source,
252-
line: m.originalLine,
253-
column: m.originalColumn,
254-
})
255-
.map(item => ({
256-
line: item.line,
257-
columnStart: item.column,
258-
// source-map's lastColumn is inclusive, not exclusive, so we need
259-
// to add 1 to it.
260-
columnEnd: item.lastColumn + 1,
261-
})),
262-
});
263-
},
264-
null,
265-
sourceMap.SourceMapConsumer.ORIGINAL_ORDER,
266-
);
267-
268-
return {
269-
file: map.file,
270-
sourceRoot: map.sourceRoot,
271-
sources: Array.from(mappings.values()),
27224
};
27325
}
274-
275-
function findInsertionLocation<T>(
276-
array: Array<T>,
277-
callback: (item: T) => number,
278-
): number {
279-
let left = 0;
280-
let right = array.length;
281-
while (left < right) {
282-
const mid = Math.floor((left + right) / 2);
283-
const item = array[mid];
284-
285-
const result = callback(item);
286-
if (result === 0) {
287-
left = mid;
288-
break;
289-
}
290-
if (result >= 0) {
291-
right = mid;
292-
} else {
293-
left = mid + 1;
294-
}
295-
}
296-
297-
// Ensure the value is the start of any set of matches.
298-
let i = left;
299-
if (i < array.length) {
300-
while (i >= 0 && callback(array[i]) >= 0) {
301-
i--;
302-
}
303-
return i + 1;
304-
}
305-
306-
return i;
307-
}
308-
309-
function filterSortedArray<T>(
310-
array: Array<T>,
311-
callback: (item: T) => number,
312-
): Array<T> {
313-
const start = findInsertionLocation(array, callback);
314-
315-
const results = [];
316-
for (let i = start; i < array.length && callback(array[i]) === 0; i++) {
317-
results.push(array[i]);
318-
}
319-
320-
return results;
321-
}

packages/babel-core/test/api.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ describe("api", function () {
540540
column: 4,
541541
}),
542542
).toEqual({
543-
name: null,
543+
name: "Foo",
544544
source: "stdout",
545545
line: 1,
546546
column: 6,

packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-complex/source-map.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
{
22
"version": 3,
3-
"sources": [
4-
"HelloWorld.vue"
5-
],
6-
"names": [],
7-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAsFA;AACA,EAAA,IAAA,EAAA,YADA;;AAEA,EAAA,IAAA,GAAA;AACA,WAAA;AACA,MAAA,GAAA,EAAA;AADA,KAAA;AAGA;;AANA,C",
3+
"sources": ["HelloWorld.vue"],
4+
"names": ["name", "data", "msg"],
5+
"mappings": ";;;;;;AAsFA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAAA;AACAA,oBADA;;AAEAC;AACA;AACAC;AADA;AAGA;;AANA,C",
86
"sourceRoot": "src/components",
97
"sourcesContent": [
108
"<template>\n <div class=\"hello\">\n <h1>{{ msg }}</h1>\n <h2>Essential Links</h2>\n <ul>\n <li>\n <a\n href=\"https://vuejs.org\"\n target=\"_blank\"\n >\n Core Docs\n </a>\n </li>\n <li>\n <a\n href=\"https://forum.vuejs.org\"\n target=\"_blank\"\n >\n Forum\n </a>\n </li>\n <li>\n <a\n href=\"https://chat.vuejs.org\"\n target=\"_blank\"\n >\n Community Chat\n </a>\n </li>\n <li>\n <a\n href=\"https://twitter.com/vuejs\"\n target=\"_blank\"\n >\n Twitter\n </a>\n </li>\n <br>\n <li>\n <a\n href=\"http://vuejs-templates.github.io/webpack/\"\n target=\"_blank\"\n >\n Docs for This Template\n </a>\n </li>\n </ul>\n <h2>Ecosystem</h2>\n <ul>\n <li>\n <a\n href=\"http://router.vuejs.org/\"\n target=\"_blank\"\n >\n vue-router\n </a>\n </li>\n <li>\n <a\n href=\"http://vuex.vuejs.org/\"\n target=\"_blank\"\n >\n vuex\n </a>\n </li>\n <li>\n <a\n href=\"http://vue-loader.vuejs.org/\"\n target=\"_blank\"\n >\n vue-loader\n </a>\n </li>\n <li>\n <a\n href=\"https://github.com/vuejs/awesome-vue\"\n target=\"_blank\"\n >\n awesome-vue\n </a>\n </li>\n </ul>\n </div>\n</template>\n\n<script>\nexport default {\n name: 'HelloWorld',\n data () {\n return {\n msg: 'Welcome to Your Vue.js App'\n }\n }\n}\n</script>\n\n<!-- Add \"scoped\" attribute to limit CSS to this component only -->\n<style scoped>\nh1, h2 {\n font-weight: normal;\n}\nul {\n list-style-type: none;\n padding: 0;\n}\nli {\n display: inline-block;\n margin: 0 10px;\n}\na {\n color: #42b983;\n}\n</style>\n"

packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-external/source-map.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"mappings": "AAAA,IAAA,GAAA,GAAU,Y;SAAM,C;AAAC,CAAjB",
2+
"mappings": "AAAA,UAAU,Y;SAAM,C;AAAC,CAAjB",
33
"names": [],
44
"sources": ["original.js"],
55
"sourcesContent": ["var foo = () => 4;"],

0 commit comments

Comments
 (0)