auto resolve input source maps

This commit is contained in:
Connor Clark 2018-07-20 01:23:26 -07:00
parent 7cf72b8d66
commit 2fbf992811
24 changed files with 230 additions and 11 deletions

View File

@ -118,8 +118,11 @@ a double dash to prevent input files being used as option arguments:
`base` Path to compute relative paths from input files.
`content` Input source map, useful if you're compressing
JS that was generated from some other original
code. Specify "inline" if the source map is
included within the sources.
code. Specify "auto" to enable automatic source
map resolution. Specify "inline" if the source map
is included within the sources only as base64 strings.
`contents` Provide overrides for the auto source map resolution
strategy. See "Composed source map" below.
`filename` Filename and/or location of the output source
(sets `file` attribute in source map).
`includeSources` Pass this flag if you want to include
@ -185,9 +188,19 @@ CoffeeScript → compiled JS, UglifyJS can generate a map from CoffeeScript →
compressed JS by mapping every token in the compiled JS to its original
location.
To use this feature pass `--source-map "content='/path/to/input/source.map'"`
or `--source-map "content=inline"` if the source map is included inline with
the sources.
The easiest way to compose source maps is to use the `--source-map "content=auto"`
option. If an input file specifies a sourceMappingURL comment, the source map will be fetched.
If there is no comment, given an input file of `src/file.js`, the map will be looked for
at `src/file.js.map` and `src/file.map`. Maps can fetched via HTTP too.
If easier to specify the link manually, you can use the `--source-map "contents=..."` option.
The format looks like this: `contents=file1.js*file1.js.map|file2.js*path/to/map.js.map|...`.
These source map locations will take precedence over the auto resolution strategy.
Previous versions of UglifyJS provided partial support for this via the
`--source-map "content=inline"` option (only looking for base64 inline maps)
and `--source-map "content='/path/to/input/source.map'"` (only supporting one file).
These options still work, but "auto" is recommended.
## CLI compress options

View File

@ -5,6 +5,7 @@
require("../tools/exit");
var execSync = require("child_process").execSync;
var fs = require("fs");
var info = require("../package.json");
var path = require("path");
@ -179,7 +180,7 @@ function run() {
print_error("WARN: " + msg);
};
var content = program.sourceMap && program.sourceMap.content;
if (content && content != "inline") {
if (content && content != "inline" && content != "auto") {
print_error("INFO: Using input source map: " + content);
options.sourceMap.content = read_file(content, content);
}
@ -206,6 +207,59 @@ function run() {
} catch (ex) {
fatal(ex);
}
if (content == "auto") {
function to_ascii(b64) {
return new Buffer(b64, "base64").toString();
}
function resolve_source_map_content(name, code) {
var sourceMappingURLMatch = /\n\/\/# sourceMappingURL=(.*)/.exec(code);
var sourceMappingURL = sourceMappingURLMatch ? sourceMappingURLMatch[1] : null;
if (!sourceMappingURL) {
// infer
if (fs.existsSync(name + '.map')) {
return read_file(name + '.map');
}
if (name.endsWith('.js') && fs.existsSync(name.slice(0, -2) + 'map')) {
return read_file(name.slice(0, -2) + 'map');
}
return;
}
if (sourceMappingURL.startsWith('data:application')) {
var match = /data:application\/json(;.*?)?;base64,(.*)/.exec(sourceMappingURL);
return to_ascii(match[2]);
}
if (sourceMappingURL.startsWith('http')) {
try {
return execSync('curl -s ' + sourceMappingURL).toString();
} catch (ex) {
fatal(ex);
}
}
return read_file(path.join(path.dirname(name), sourceMappingURL));
}
// read source map content locations given via CLI
// contents=file1.js*file1.js.map|file2.js*path/to/map.js.map|...
var contents = {};
if (options.sourceMap.contents) options.sourceMap.contents.replace(/([^|*]+)=([^|]+)/g, function(_, x, y) {
contents[x] = read_file(y, y);
});
// attempt to resolve the remaining
for (var name in files) if (!contents[name]) {
var content = resolve_source_map_content(name, files[name]);
if (content) {
contents[name] = content;
}
}
options.sourceMap.contents = contents;
}
var result = UglifyJS.minify(files, options);
if (result.error) {
var ex = result.error;

View File

@ -7,7 +7,7 @@ var to_base64 = typeof btoa == "undefined" ? function(str) {
return new Buffer(str).toString("base64");
} : btoa;
function read_source_map(name, code) {
function read_inline_source_map(name, code) {
var match = /\n\/\/# sourceMappingURL=data:application\/json(;.*?)?;base64,(.*)/.exec(code);
if (!match) {
AST_Node.warn("inline source map not found: " + name);
@ -109,6 +109,7 @@ function minify(files, options) {
if (options.sourceMap) {
options.sourceMap = defaults(options.sourceMap, {
content: null,
contents: null,
filename: null,
includeSources: false,
root: null,
@ -132,7 +133,7 @@ function minify(files, options) {
options.parse = options.parse || {};
options.parse.toplevel = null;
var source_map_content = options.sourceMap && options.sourceMap.content;
if (typeof source_map_content == "string" && source_map_content != "inline") {
if (typeof source_map_content == "string" && source_map_content != "inline" && source_map_content != "auto") {
source_map_content = parse_source_map(source_map_content);
}
source_maps = source_map_content && Object.create(null);
@ -141,10 +142,15 @@ function minify(files, options) {
options.parse.toplevel = parse(files[name], options.parse);
if (source_maps) {
if (source_map_content == "inline") {
var inlined_content = read_source_map(name, files[name]);
var inlined_content = read_inline_source_map(name, files[name]);
if (inlined_content) {
source_maps[name] = parse_source_map(inlined_content);
}
} else if (source_map_content == "auto") {
var content = options.sourceMap.contents[name];
if (content) {
source_maps[name] = parse_source_map(content);
}
} else {
source_maps[name] = source_map_content;
}

24
test/input/issue-3219/build.sh Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env sh
tsc --sourceMap --inlineSources file.ts
sed -i '' 's/file/mapping/g' file.js
mv file.js.map mapping.js.map
tsc --sourceMap --inlineSources file.ts --out file2.js
sed -i '' 's/file2/mapping2/g' file2.js
sed -i '' -e '$ d' file2.js
mv file2.js.map mapping2.js.map
tsc --sourceMap --inlineSources file.ts --out file3.js
sed -i '' 's/file3/mapping2/g' file3.js
sed -i '' -e '$ d' file3.js
mv file3.js.map mapping2.js.map
tsc --inlineSourceMap --inlineSources inline.ts
tsc --sourceMap --inlineSources infer1.ts
sed -i '' -e '$ d' infer1.js
tsc --sourceMap --inlineSources infer2.ts
sed -i '' -e '$ d' infer2.js
mv infer2.js.map infer2.map

View File

@ -0,0 +1,12 @@
var Car = /** @class */ (function () {
function Car() {
this.model = 'nice';
this.speed = 100;
this.cost = 1000000;
}
return Car;
}());
var myCar = new Car();
myCar.cost += 1;
var _a = [1, 2], blah = _a[0], blah2 = _a[1];
//# sourceMappingURL=mapping.js.map

View File

@ -0,0 +1,16 @@
interface ICar {
model: string
speed: number
cost: number
}
class Car implements ICar {
model: string = 'nice'
speed: number = 100
cost: number = 1000000
}
var myCar: Car = new Car()
myCar.cost += 1
const [blah, blah2] = [1,2]

View File

@ -0,0 +1,11 @@
var Car = /** @class */ (function () {
function Car() {
this.model = 'nice';
this.speed = 100;
this.cost = 1000000;
}
return Car;
}());
var myCar = new Car();
myCar.cost += 1;
var _a = [1, 2], blah = _a[0], blah2 = _a[1];

View File

@ -0,0 +1,11 @@
var Car = /** @class */ (function () {
function Car() {
this.model = 'nice';
this.speed = 100;
this.cost = 1000000;
}
return Car;
}());
var myCar = new Car();
myCar.cost += 1;
var _a = [1, 2], blah = _a[0], blah2 = _a[1];

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
console.log('infer1.js');

View File

@ -0,0 +1 @@
{"version":3,"file":"infer1.js","sourceRoot":"","sources":["infer1.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC","sourcesContent":["console.log('infer1.js');"]}

View File

@ -0,0 +1 @@
console.log('infer1.js');

View File

@ -0,0 +1 @@
console.log('infer2.js');

View File

@ -0,0 +1 @@
{"version":3,"file":"infer2.js","sourceRoot":"","sources":["infer2.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC","sourcesContent":["console.log('infer2.js');"]}

View File

@ -0,0 +1 @@
console.log('infer2.js');

View File

@ -0,0 +1,12 @@
var Car = /** @class */ (function () {
function Car() {
this.model = 'bad';
this.speed = 10;
this.cost = 10000;
}
return Car;
}());
var myCar = new Car();
myCar.cost += 1;
var _a = [1, 2], blah = _a[0], blah2 = _a[1];
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5saW5lLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiaW5saW5lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQU1BO0lBQUE7UUFDRSxVQUFLLEdBQVcsS0FBSyxDQUFBO1FBQ3JCLFVBQUssR0FBVyxFQUFFLENBQUE7UUFDbEIsU0FBSSxHQUFXLEtBQUssQ0FBQTtJQUN0QixDQUFDO0lBQUQsVUFBQztBQUFELENBQUMsQUFKRCxJQUlDO0FBRUQsSUFBSSxLQUFLLEdBQVEsSUFBSSxHQUFHLEVBQUUsQ0FBQTtBQUMxQixLQUFLLENBQUMsSUFBSSxJQUFJLENBQUMsQ0FBQTtBQUVULElBQUEsV0FBcUIsRUFBcEIsWUFBSSxFQUFFLGFBQUssQ0FBUyIsInNvdXJjZXNDb250ZW50IjpbImludGVyZmFjZSBJQ2FyIHtcbiAgbW9kZWw6IHN0cmluZ1xuICBzcGVlZDogbnVtYmVyXG4gIGNvc3Q6IG51bWJlclxufVxuXG5jbGFzcyBDYXIgaW1wbGVtZW50cyBJQ2FyIHtcbiAgbW9kZWw6IHN0cmluZyA9ICdiYWQnXG4gIHNwZWVkOiBudW1iZXIgPSAxMFxuICBjb3N0OiBudW1iZXIgPSAxMDAwMFxufVxuXG52YXIgbXlDYXI6IENhciA9IG5ldyBDYXIoKVxubXlDYXIuY29zdCArPSAxXG5cbmNvbnN0IFtibGFoLCBibGFoMl0gPSBbMSwyXVxuIl19

View File

@ -0,0 +1,16 @@
interface ICar {
model: string
speed: number
cost: number
}
class Car implements ICar {
model: string = 'bad'
speed: number = 10
cost: number = 10000
}
var myCar: Car = new Car()
myCar.cost += 1
const [blah, blah2] = [1,2]

View File

@ -0,0 +1 @@
{"version":3,"file":"file.js","sourceRoot":"","sources":["file.ts"],"names":[],"mappings":"AAMA;IAAA;QACE,UAAK,GAAW,MAAM,CAAA;QACtB,UAAK,GAAW,GAAG,CAAA;QACnB,SAAI,GAAW,OAAO,CAAA;IACxB,CAAC;IAAD,UAAC;AAAD,CAAC,AAJD,IAIC;AAED,IAAI,KAAK,GAAQ,IAAI,GAAG,EAAE,CAAA;AAC1B,KAAK,CAAC,IAAI,IAAI,CAAC,CAAA;AAET,IAAA,WAAqB,EAApB,YAAI,EAAE,aAAK,CAAS","sourcesContent":["interface ICar {\n model: string\n speed: number\n cost: number\n}\n\nclass Car implements ICar {\n model: string = 'nice'\n speed: number = 100\n cost: number = 1000000\n}\n\nvar myCar: Car = new Car()\nmyCar.cost += 1\n\nconst [blah, blah2] = [1,2]\n"]}

View File

@ -0,0 +1 @@
{"version":3,"file":"file3.js","sourceRoot":"","sources":["file.ts"],"names":[],"mappings":"AAMA;IAAA;QACE,UAAK,GAAW,MAAM,CAAA;QACtB,UAAK,GAAW,GAAG,CAAA;QACnB,SAAI,GAAW,OAAO,CAAA;IACxB,CAAC;IAAD,UAAC;AAAD,CAAC,AAJD,IAIC;AAED,IAAI,KAAK,GAAQ,IAAI,GAAG,EAAE,CAAA;AAC1B,KAAK,CAAC,IAAI,IAAI,CAAC,CAAA;AAET,IAAA,WAAqB,EAApB,YAAI,EAAE,aAAK,CAAS","sourcesContent":["interface ICar {\n model: string\n speed: number\n cost: number\n}\n\nclass Car implements ICar {\n model: string = 'nice'\n speed: number = 100\n cost: number = 1000000\n}\n\nvar myCar: Car = new Car()\nmyCar.cost += 1\n\nconst [blah, blah2] = [1,2]\n"]}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
var Car=function(){function Car(){this.model="nice";this.speed=100;this.cost=1e6}return Car}();var myCar=new Car;myCar.cost+=1;var _a=[1,2],blah=_a[0],blah2=_a[1];var Car=function(){function Car(){this.model="nice";this.speed=100;this.cost=1e6}return Car}();var myCar=new Car;myCar.cost+=1;var _a=[1,2],blah=_a[0],blah2=_a[1];
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMzIxOS9maWxlMi5qcyIsInRlc3QvaW5wdXQvaXNzdWUtMzIxOS9maWxlMy5qcyJdLCJuYW1lcyI6WyJDYXIiLCJ0aGlzIiwibW9kZWwiLCJzcGVlZCIsImNvc3QiLCJteUNhciIsIl9hIiwiYmxhaCIsImJsYWgyIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxJQUFxQixXQUNyQixTQUFTQSxNQUNMQyxLQUFLQyxNQUFRLE9BQ2JELEtBQUtFLE1BQVEsSUFDYkYsS0FBS0csS0FBTyxJQUVoQixPQUFPSixJQU5hLEdBUXhCLElBQUlLLE1BQVEsSUFBSUwsSUFDaEJLLE1BQU1ELE1BQVEsRUFDZCxJQUFJRSxHQUFLLENBQUMsRUFBRyxHQUFJQyxLQUFPRCxHQUFHLEdBQUlFLE1BQVFGLEdBQUcsR0NWMUMsSUFBSU4sSUFBcUIsV0FDckIsU0FBU0EsTUFDTEMsS0FBS0MsTUFBUSxPQUNiRCxLQUFLRSxNQUFRLElBQ2JGLEtBQUtHLEtBQU8sSUFFaEIsT0FBT0osSUFOYSxHQVF4QixJQUFJSyxNQUFRLElBQUlMLElBQ2hCSyxNQUFNRCxNQUFRLEVBQ2QsSUFBSUUsR0FBSyxDQUFDLEVBQUcsR0FBSUMsS0FBT0QsR0FBRyxHQUFJRSxNQUFRRixHQUFHIiwic291cmNlc0NvbnRlbnQiOlsidmFyIENhciA9IC8qKiBAY2xhc3MgKi8gKGZ1bmN0aW9uICgpIHtcbiAgICBmdW5jdGlvbiBDYXIoKSB7XG4gICAgICAgIHRoaXMubW9kZWwgPSAnbmljZSc7XG4gICAgICAgIHRoaXMuc3BlZWQgPSAxMDA7XG4gICAgICAgIHRoaXMuY29zdCA9IDEwMDAwMDA7XG4gICAgfVxuICAgIHJldHVybiBDYXI7XG59KCkpO1xudmFyIG15Q2FyID0gbmV3IENhcigpO1xubXlDYXIuY29zdCArPSAxO1xudmFyIF9hID0gWzEsIDJdLCBibGFoID0gX2FbMF0sIGJsYWgyID0gX2FbMV07XG4iLCJ2YXIgQ2FyID0gLyoqIEBjbGFzcyAqLyAoZnVuY3Rpb24gKCkge1xuICAgIGZ1bmN0aW9uIENhcigpIHtcbiAgICAgICAgdGhpcy5tb2RlbCA9ICduaWNlJztcbiAgICAgICAgdGhpcy5zcGVlZCA9IDEwMDtcbiAgICAgICAgdGhpcy5jb3N0ID0gMTAwMDAwMDtcbiAgICB9XG4gICAgcmV0dXJuIENhcjtcbn0oKSk7XG52YXIgbXlDYXIgPSBuZXcgQ2FyKCk7XG5teUNhci5jb3N0ICs9IDE7XG52YXIgX2EgPSBbMSwgMl0sIGJsYWggPSBfYVswXSwgYmxhaDIgPSBfYVsxXTtcbiJdfQ==

View File

@ -19,7 +19,7 @@ it = function(title, fn) {
fn.limit = config.limit;
fn.titles = titles.slice();
fn.titles.push(title);
tasks.push(fn);
if (!process.env.MOCHA_GREP || new RegExp(process.env.MOCHA_GREP).test(title)) tasks.push(fn);
};
fs.readdirSync("test/mocha").filter(function(file) {

View File

@ -249,6 +249,35 @@ describe("bin/uglifyjs", function() {
done();
});
});
it("Should infer source maps in auto mode", function(done) {
var command = [
uglifyjscmd,
"test/input/issue-3219/file.js",
"test/input/issue-3219/http.js",
"test/input/issue-3219/infer1.js",
"test/input/issue-3219/infer2.js",
"test/input/issue-3219/inline.js",
"--source-map", "content=auto,includeSources=true,url=inline",
].join(" ");
exec(command, {maxBuffer: 1024 * 300}, function(err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, read("test/input/issue-3219/output1.js"));
done();
});
});
it("Should prefer CLI source map locations over auto resolution strategy", function(done) {
var command = [
uglifyjscmd,
"test/input/issue-3219/file2.js",
"test/input/issue-3219/file3.js",
"--source-map", "'content=auto,contents=test/input/issue-3219/file2.js*test/input/issue-3219/mapping2.js.map|test/input/issue-3219/file3.js*test/input/issue-3219/mapping3.js.map,includeSources=true,url=inline'",
].join(" ");
exec(command, function(err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, read("test/input/issue-3219/output2.js"));
done();
});
});
it("Should fail with invalid syntax", function(done) {
var command = uglifyjscmd + ' test/input/invalid/simple.js';
exec(command, function(err, stdout, stderr) {

View File

@ -12,7 +12,7 @@ var failures = 0;
var failed_files = {};
var minify_options = require("./ufuzz.json").map(JSON.stringify);
run_compress_tests();
if (!process.env.SKIP_COMPRESS_TESTS) run_compress_tests();
if (failures) {
console.error("\n!!! Failed " + failures + " test cases.");
console.error("!!! " + Object.keys(failed_files).join(", "));