diff --git a/packages/babel-core/src/transformation/file/generate.ts b/packages/babel-core/src/transformation/file/generate.ts
index 7248d2509961..1b60aa9b1a2f 100644
--- a/packages/babel-core/src/transformation/file/generate.ts
+++ b/packages/babel-core/src/transformation/file/generate.ts
@@ -14,18 +14,14 @@ export default function generateCode(
outputMap: SourceMap | null;
} {
const { opts, ast, code, inputMap } = file;
+ const { generatorOpts } = opts;
const results = [];
for (const plugins of pluginPasses) {
for (const plugin of plugins) {
const { generatorOverride } = plugin;
if (generatorOverride) {
- const result = generatorOverride(
- ast,
- opts.generatorOpts,
- code,
- generate,
- );
+ const result = generatorOverride(ast, generatorOpts, code, generate);
if (result !== undefined) results.push(result);
}
@@ -34,7 +30,7 @@ export default function generateCode(
let result;
if (results.length === 0) {
- result = generate(ast, opts.generatorOpts, code);
+ result = generate(ast, generatorOpts, code);
} else if (results.length === 1) {
result = results[0];
@@ -53,7 +49,11 @@ export default function generateCode(
let { code: outputCode, map: outputMap } = result;
if (outputMap && inputMap) {
- outputMap = mergeSourceMap(inputMap.toObject(), outputMap);
+ outputMap = mergeSourceMap(
+ inputMap.toObject(),
+ outputMap,
+ generatorOpts.sourceFileName,
+ );
}
if (opts.sourceMaps === "inline" || opts.sourceMaps === "both") {
diff --git a/packages/babel-core/src/transformation/file/merge-map.ts b/packages/babel-core/src/transformation/file/merge-map.ts
index 7abcd957064f..614c743f9e58 100644
--- a/packages/babel-core/src/transformation/file/merge-map.ts
+++ b/packages/babel-core/src/transformation/file/merge-map.ts
@@ -4,8 +4,26 @@ import remapping from "@ampproject/remapping";
export default function mergeSourceMap(
inputMap: SourceMap,
map: SourceMap,
+ source: string,
): SourceMap {
- const result = remapping([rootless(map), rootless(inputMap)], () => null);
+ const outputSources = map.sources;
+
+ let result;
+ if (outputSources.length > 1) {
+ // When there are multiple output sources, we can't always be certain which
+ // source represents the file we just transformed.
+ const index = outputSources.indexOf(source);
+
+ // If we can't find the source, we fall back to the legacy behavior of
+ // outputting an empty sourcemap.
+ if (index === -1) {
+ result = emptyMap(inputMap);
+ } else {
+ result = mergeMultiSource(inputMap, map, index);
+ }
+ } else {
+ result = mergeSingleSource(inputMap, map);
+ }
if (typeof inputMap.sourceRoot === "string") {
result.sourceRoot = inputMap.sourceRoot;
@@ -13,6 +31,52 @@ export default function mergeSourceMap(
return result;
}
+// A single source transformation is the default, and easiest to handle.
+function mergeSingleSource(inputMap: SourceMap, map: SourceMap): SourceMap {
+ return remapping([rootless(map), rootless(inputMap)], () => null);
+}
+
+// Transformation generated an output from multiple source files. When this
+// happens, it's ambiguous which source was the transformed file, and which
+// source is from the transformation process. We use remapping's multisource
+// behavior, returning the input map when we encounter the transformed file.
+function mergeMultiSource(inputMap: SourceMap, map: SourceMap, index: number) {
+ // We empty the source index, which will prevent the sourcemap from becoming
+ // relative the the input's location. Eg, if we're transforming a file
+ // 'foo/bar.js', and it is a transformation of a `baz.js` file in the same
+ // directory, the expected output is just `baz.js`. Without this step, it
+ // would become `foo/baz.js`.
+ map.sources[index] = "";
+
+ let count = 0;
+ return remapping(rootless(map), () => {
+ if (count++ === index) return rootless(inputMap);
+ return null;
+ });
+}
+
+// Legacy behavior of the old merger was to output a sourcemap without any
+// mappings but with copied sourcesContent. This only happens if there are
+// multiple output files and it's ambiguous which one is the transformed file.
+function emptyMap(inputMap: SourceMap) {
+ const inputSources = inputMap.sources;
+
+ const sources = [];
+ const sourcesContent = inputMap.sourcesContent?.filter((content, i) => {
+ if (typeof content !== "string") return false;
+
+ sources.push(inputSources[i]);
+ return true;
+ });
+
+ return {
+ ...inputMap,
+ sources,
+ sourcesContent,
+ mappings: "",
+ };
+}
+
function rootless(map: SourceMap): SourceMap {
return {
...map,
diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/input.js b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/input.js
new file mode 100644
index 000000000000..298b7dfa0cb8
--- /dev/null
+++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/input.js
@@ -0,0 +1,5 @@
+foo(1);
+function foo(bar) {
+ throw new Error('Intentional.');
+}
+//# sourceMappingURL=input.js.map
diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/input.js.map b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/input.js.map
new file mode 100644
index 000000000000..4f1791f12c23
--- /dev/null
+++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/input.js.map
@@ -0,0 +1,9 @@
+{
+ "version": 3,
+ "sources": ["input.tsx"],
+ "names": [],
+ "mappings": "AAAA,GAAG,CAAC,CAAC,CAAC,CAAC;AACP,SAAS,GAAG,CAAC,GAAW;IACpB,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;AACpC,CAAC",
+ "sourcesContent": [
+ "foo(1);\nfunction foo(bar: number): never {\n throw new Error('Intentional.');\n}"
+ ]
+}
diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/options.json b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/options.json
new file mode 100644
index 000000000000..6466aed13cd6
--- /dev/null
+++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/options.json
@@ -0,0 +1,4 @@
+{
+ "inputSourceMap": true,
+ "plugins": ["./plugin.js"]
+}
diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/output.js b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/output.js
new file mode 100644
index 000000000000..4d0b018046c9
--- /dev/null
+++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/output.js
@@ -0,0 +1,5 @@
+"bar";
+
+function foo(bar) {
+ throw new Error('Intentional.');
+}
diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/plugin.js b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/plugin.js
new file mode 100644
index 000000000000..b9fd011c05bf
--- /dev/null
+++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/plugin.js
@@ -0,0 +1,32 @@
+module.exports = function (babel) {
+ const { types: t } = babel;
+
+ return {
+ visitor: {
+ CallExpression(path) {
+ const { file } = this;
+ const { sourceFileName } = file.opts.generatorOpts;
+ const callee = path.node;
+ const { loc } = callee;
+
+ // This filename will cause a second source file to be generated in the
+ // output sourcemap.
+ loc.filename = "test.js";
+ loc.start.column = 1;
+ loc.end.column = 4;
+
+ const node = t.stringLiteral('bar');
+ node.loc = loc;
+ path.replaceWith(node);
+
+ // This injects the sourcesContent, though I don't imagine anyone's
+ // doing it.
+ file.code = {
+ [sourceFileName]: file.code,
+ 'test.js': '',
+ };
+ path.stop();
+ },
+ },
+ };
+};
diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/source-map.json b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/source-map.json
new file mode 100644
index 000000000000..c896cd9f05a6
--- /dev/null
+++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/source-map.json
@@ -0,0 +1,17 @@
+{
+ "mappings": "AAAC;;ACCD,SAASA,GAAT,CAAaC,GAAb,EAAwB;AACpB,QAAM,IAAIC,KAAJ,CAAU,cAAV,CAAN;AACH",
+ "names": [
+ "foo",
+ "bar",
+ "Error"
+ ],
+ "sources": [
+ "test.js",
+ "input.tsx"
+ ],
+ "sourcesContent": [
+ "",
+ "foo(1);\nfunction foo(bar: number): never {\n throw new Error('Intentional.');\n}"
+ ],
+ "version": 3
+}