Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support merging sourcemaps when transformed file is fully replaced #14247

Merged
merged 2 commits into from Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/babel-core/package.json
Expand Up @@ -48,7 +48,7 @@
"./src/transformation/util/clone-deep.ts": "./src/transformation/util/clone-deep-browser.ts"
},
"dependencies": {
"@ampproject/remapping": "^2.0.0",
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "workspace:^",
"@babel/generator": "workspace:^",
"@babel/helper-compilation-targets": "workspace:^",
Expand Down
76 changes: 13 additions & 63 deletions packages/babel-core/src/transformation/file/merge-map.ts
Expand Up @@ -6,77 +6,27 @@ export default function mergeSourceMap(
map: SourceMap,
source: string,
): SourceMap {
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);
const result = remapping(rootless(map), (s, ctx) => {
if (s === source) {
// We empty the source location, which will prevent the sourcemap from
// becoming relative to 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`.
ctx.source = "";

return rootless(inputMap);
}
} else {
result = mergeSingleSource(inputMap, map);
}

return null;
});

if (typeof inputMap.sourceRoot === "string") {
result.sourceRoot = inputMap.sourceRoot;
}
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,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -0,0 +1,4 @@
{
"inputSourceMap": true,
"plugins": ["./plugin.js"]
}
@@ -0,0 +1,2 @@
"bar";
"baz";
@@ -0,0 +1,52 @@
module.exports = function (babel) {
const { types: t } = babel;

return {
visitor: {
Program(path) {
const { file } = this;
const { sourceFileName } = file.opts.generatorOpts;

// This injects the sourcesContent, though I don't imagine anyone's
// doing it.
file.code = {
[sourceFileName]: file.code,
'bar.js': '<bar />',
'baz.js': 'baz();',
};
},

CallExpression(path) {
const callee = path.node;
const { loc } = callee;

// This filename will cause a second source file to be generated in the
// output sourcemap.
loc.filename = "bar.js";
loc.start.column = 1;
loc.end.column = 4;

const node = t.stringLiteral('bar');
node.loc = loc;
path.replaceWith(node);
},

Function(path) {
const callee = path.node;
const { loc } = callee;

// This filename will cause a second source file to be generated in the
// output sourcemap.
loc.filename = "baz.js";
loc.start.column = 0;
loc.start.line = 1;
loc.end.column = 3;
loc.end.line = 1;

const node = t.stringLiteral('baz');
node.loc = loc;
path.replaceWith(node);
},
},
};
};
@@ -0,0 +1,13 @@
{
"mappings": "AAAC;ACAD,K",
"names": [],
"sources": [
"bar.js",
"baz.js"
],
"sourcesContent": [
"<bar />",
"baz();"
],
"version": 3
}
Expand Up @@ -20,7 +20,7 @@ module.exports = function (babel) {
path.replaceWith(node);

// This injects the sourcesContent, though I don't imagine anyone's
// doing it.
// doing it.
file.code = {
[sourceFileName]: file.code,
'test.js': '<bar />',
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -0,0 +1,4 @@
{
"inputSourceMap": true,
"plugins": ["./plugin.js"]
}
@@ -0,0 +1 @@
"bar";
@@ -0,0 +1,38 @@
module.exports = function (babel) {
const { types: t } = babel;

return {
visitor: {
Program(path) {
const { file } = this;
const { sourceFileName } = file.opts.generatorOpts;

// This injects the sourcesContent, though I don't imagine anyone's
// doing it.
file.code = {
[sourceFileName]: file.code,
'test.js': '<bar />',
};
},

CallExpression(path) {
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);
},

Function(path) {
path.remove();
},
},
};
};
@@ -0,0 +1,7 @@
{
"mappings": "AAAC",
"names": [],
"sources": ["test.js"],
"sourcesContent": ["<bar />"],
"version": 3
}
32 changes: 19 additions & 13 deletions yarn.lock
Expand Up @@ -5,13 +5,12 @@ __metadata:
version: 5
cacheKey: 8

"@ampproject/remapping@npm:^2.0.0":
version: 2.0.2
resolution: "@ampproject/remapping@npm:2.0.2"
"@ampproject/remapping@npm:^2.0.0, @ampproject/remapping@npm:^2.1.0":
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get yarn, but we need v2.1.0 to ensure the new ctx param is passed to the remapping loader. Will just the change in the package.json be enough?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's enough

version: 2.1.0
resolution: "@ampproject/remapping@npm:2.1.0"
dependencies:
"@jridgewell/trace-mapping": ^0.2.2
sourcemap-codec: 1.4.8
checksum: 5759df3715f0291cbf97099a9bb7202201a1a267e232ee1505418c768b9ae7281cd550b1da563a12808a06529eb1298744a6cabde21ac354fc8450044c7f2213
"@jridgewell/trace-mapping": ^0.3.0
checksum: 10ff0d4a559f930082f1a4c1b68dc521d5b1a75e0b8ab4829e9eedf6621386893e4a008f0db6c716f64db5d8eed49c0abcfbf3bd6ff11d5a00312454a9351ed4
languageName: node
linkType: hard

Expand Down Expand Up @@ -323,7 +322,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@babel/core@workspace:packages/babel-core"
dependencies:
"@ampproject/remapping": ^2.0.0
"@ampproject/remapping": ^2.1.0
"@babel/code-frame": "workspace:^"
"@babel/generator": "workspace:^"
"@babel/helper-compilation-targets": "workspace:^"
Expand Down Expand Up @@ -4091,13 +4090,20 @@ __metadata:
languageName: node
linkType: hard

"@jridgewell/trace-mapping@npm:^0.2.2":
version: 0.2.5
resolution: "@jridgewell/trace-mapping@npm:0.2.5"
"@jridgewell/sourcemap-codec@npm:^1.4.10":
version: 1.4.10
resolution: "@jridgewell/sourcemap-codec@npm:1.4.10"
checksum: 247229218edbe165dcf0a5ae0c4b81bff1b5438818bb09221f756681fe158597fdf25c2a803f9260453b299c98c7e01ddebeb1555cda3157d987cd22c08605ef
languageName: node
linkType: hard

"@jridgewell/trace-mapping@npm:^0.3.0":
version: 0.3.2
resolution: "@jridgewell/trace-mapping@npm:0.3.2"
dependencies:
"@jridgewell/resolve-uri": ^3.0.3
sourcemap-codec: 1.4.8
checksum: 7ac0a2992f4a8d16c1cbe03bccbcfbd1e96bf5071b0a794dd97904a7588cc248b73e8091fabcb13dac8f40bc51297ee4ef98a6a870ca5a4dfb8e2dcbf6f33956
"@jridgewell/sourcemap-codec": ^1.4.10
checksum: b58be6b4133cbcb20bfd28c9ca843b8db9efa0bf1d7e0e9e26b2228dace94ad53161c996ab1d762d7c3955dfc398a7734e7b84a2493ae36b451f232234fbb257
languageName: node
linkType: hard

Expand Down Expand Up @@ -14168,7 +14174,7 @@ fsevents@^1.2.7:
languageName: node
linkType: hard

"sourcemap-codec@npm:1.4.8, sourcemap-codec@npm:^1.4.4":
"sourcemap-codec@npm:^1.4.4":
version: 1.4.8
resolution: "sourcemap-codec@npm:1.4.8"
checksum: b57981c05611afef31605732b598ccf65124a9fcb03b833532659ac4d29ac0f7bfacbc0d6c5a28a03e84c7510e7e556d758d0bb57786e214660016fb94279316
Expand Down