Skip to content

Commit

Permalink
Switch to GenMapping for sourcemap generation (#1190)
Browse files Browse the repository at this point in the history
* Switch to GenMapping for sourcemap generation

[`gen-mapping`](https://github.com/jridgewell/gen-mapping) is faster, smaller, and lighter  sourcemap generation library.

This also exposes a new `decodedMap` property on the result object. Decoded maps are free to create (it's a shallow clone of the `GenMapping` instance), and passing them to `@jridgewell/trace-mapping` is copy-free. With Babel  [recently](babel/babel#14497) adding a `decodedMap` field, a dev could pass from the Babel transpilation to Terser without any added memory use for sourcemaps.

And if there's a multi stage build process, a dev could use `@ampproject/remapping` to remap Babel, Terser, and (eg) a bundler's outputs without having to feed input maps into each stage.

* Apply suggestions from code review

Co-authored-by: Fábio Santos <fabiosantosart@gmail.com>

* Fix dependency

* Switch to source-map compatible @jridgewell/source-map

This preserves API compatibility, so that loading source-map in the browser is still supported.

* Update TS types

* Support source-map's async SourceMapConsumer

* Fix sourcesContent check with source-map's SourceMapGenerator

Co-authored-by: Fábio Santos <fabiosantosart@gmail.com>
  • Loading branch information
jridgewell and fabiosantoscode committed May 10, 2022
1 parent 8627a08 commit a47f29a
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 64 deletions.
33 changes: 20 additions & 13 deletions lib/minify.js
Expand Up @@ -297,18 +297,15 @@ async function minify(files, options, _fs_module) {
}

if (options.sourceMap) {
if (options.sourceMap.includeSources && files instanceof AST_Toplevel) {
throw new Error("original source content unavailable");
}
options.format.source_map = await SourceMap({
file: options.sourceMap.filename,
orig: options.sourceMap.content,
root: options.sourceMap.root
root: options.sourceMap.root,
files: options.sourceMap.includeSources ? files : null,
});
if (options.sourceMap.includeSources) {
if (files instanceof AST_Toplevel) {
throw new Error("original source content unavailable");
} else for (var name in files) if (HOP(files, name)) {
options.format.source_map.get().setSourceContent(name, files[name]);
}
}
}
delete options.format.ast;
delete options.format.code;
Expand All @@ -317,11 +314,21 @@ async function minify(files, options, _fs_module) {
toplevel.print(stream);
result.code = stream.get();
if (options.sourceMap) {
if(options.sourceMap.asObject) {
result.map = options.format.source_map.get().toJSON();
} else {
result.map = options.format.source_map.toString();
}
Object.defineProperty(result, "map", {
configurable: true,
enumerable: true,
get() {
const map = options.format.source_map.getEncoded();
return (result.map = options.sourceMap.asObject ? map : JSON.stringify(map));
},
set(value) {
Object.defineProperty(result, "map", {
value,
writable: true,
});
}
});
result.decoded_map = options.format.source_map.getDecoded();
if (options.sourceMap.url == "inline") {
var sourceMap = typeof result.map === "object" ? JSON.stringify(result.map) : result.map;
result.code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + to_base64(sourceMap);
Expand Down
71 changes: 50 additions & 21 deletions lib/sourcemap.js
Expand Up @@ -43,42 +43,47 @@

"use strict";

import MOZ_SourceMap from "source-map";
import {AnyMap, originalPositionFor} from "@jridgewell/trace-mapping";
import {defaults} from "./utils/index.js";
import {SourceMapConsumer, SourceMapGenerator} from "@jridgewell/source-map";
import {defaults, HOP} from "./utils/index.js";

// a small wrapper around source-map and @jridgewell/trace-mapping
function SourceMap(options) {
// a small wrapper around source-map and @jridgewell/source-map
async function SourceMap(options) {
options = defaults(options, {
file : null,
root : null,
orig : null,

orig_line_diff : 0,
dest_line_diff : 0,
files: {},
});

var orig_map;
var generator = new MOZ_SourceMap.SourceMapGenerator({
var generator = new SourceMapGenerator({
file : options.file,
sourceRoot : options.root
});

let sourcesContent = {__proto__: null};
let files = options.files;
for (var name in files) if (HOP(files, name)) {
sourcesContent[name] = files[name];
}
if (options.orig) {
orig_map = new AnyMap(options.orig);
// We support both @jridgewell/source-map (which has a sync
// SourceMapConsumer) and source-map (which has an async
// SourceMapConsumer).
orig_map = await new SourceMapConsumer(options.orig);
if (orig_map.sourcesContent) {
orig_map.resolvedSources.forEach(function(source, i) {
var sourceContent = orig_map.sourcesContent[i];
if (sourceContent) {
generator.setSourceContent(source, sourceContent);
orig_map.sources.forEach(function(source, i) {
var content = orig_map.sourcesContent[i];
if (content) {
sourcesContent[source] = content;
}
});
}
}

function add(source, gen_line, gen_col, orig_line, orig_col, name) {
if (orig_map) {
var info = originalPositionFor(orig_map, {
var info = orig_map.originalPositionFor({
line: orig_line,
column: orig_col
});
Expand All @@ -91,18 +96,42 @@ function SourceMap(options) {
name = info.name || name;
}
generator.addMapping({
generated : { line: gen_line + options.dest_line_diff, column: gen_col },
original : { line: orig_line + options.orig_line_diff, column: orig_col },
generated : { line: gen_line, column: gen_col },
original : { line: orig_line, column: orig_col },
source : source,
name : name
});
generator.setSourceContent(source, sourcesContent[source]);
}

function clean(map) {
const allNull = map.sourcesContent && map.sourcesContent.every(c => c == null);
if (allNull) delete map.sourcesContent;
if (map.file === undefined) delete map.file;
if (map.sourceRoot === undefined) delete map.sourceRoot;
return map;
}

function getDecoded() {
if (!generator.toDecodedMap) return null;
return clean(generator.toDecodedMap());
}

function getEncoded() {
return clean(generator.toJSON());
}

function destroy() {
// @jridgewell/source-map's SourceMapConsumer does not need to be
// manually freed.
if (orig_map && orig_map.destroy) orig_map.destroy();
}

return {
add : add,
get : function() { return generator; },
toString : function() { return generator.toString(); },
destroy : function() {}
add,
getDecoded,
getEncoded,
destroy,
};
}

Expand Down
48 changes: 39 additions & 9 deletions package-lock.json

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

6 changes: 3 additions & 3 deletions package.json
Expand Up @@ -44,10 +44,9 @@
"main.js"
],
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.5",
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map": "~0.8.0-beta.0",
"source-map-support": "~0.5.20"
},
"devDependencies": {
Expand All @@ -60,7 +59,8 @@
"pre-commit": "^1.2.2",
"rimraf": "^3.0.2",
"rollup": "2.56.3",
"semver": "^7.3.4"
"semver": "^7.3.4",
"source-map": "~0.8.0-beta.0"
},
"scripts": {
"test": "node test/compress.js && mocha test/mocha",
Expand Down
2 changes: 1 addition & 1 deletion rollup.config.js
Expand Up @@ -5,7 +5,7 @@ export default () => {
file: "dist/bundle.min.js",
format: "umd",
globals: {
"source-map": "sourceMap",
"@jridgewell/source-map": "sourceMap",
},
name: "Terser",
sourcemap: false,
Expand Down
2 changes: 1 addition & 1 deletion test/input/issue-505/output.js

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

2 changes: 1 addition & 1 deletion test/input/issue-520/output.js

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

2 changes: 1 addition & 1 deletion test/input/source-maps/expect.js

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

4 changes: 2 additions & 2 deletions test/mocha/cli-2.js
Expand Up @@ -21,7 +21,7 @@ describe("bin/terser (2)", function() {

assert.strictEqual(stdout, [
'"use strict";var foo=function foo(x){return"foo "+x};console.log(foo("bar"));',
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImluZGV4LmpzIl0sIm5hbWVzIjpbImZvbyIsIngiLCJjb25zb2xlIiwibG9nIl0sIm1hcHBpbmdzIjoiYUFBQSxJQUFJQSxJQUFNLFNBQU5BLElBQU1DLEdBQUEsTUFBSyxPQUFTQSxHQUN4QkMsUUFBUUMsSUFBSUgsSUFBSSJ9",
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmb28iLCJ4IiwiY29uc29sZSIsImxvZyJdLCJzb3VyY2VzIjpbImluZGV4LmpzIl0sIm1hcHBpbmdzIjoiYUFBQSxJQUFJQSxJQUFNLFNBQU5BLElBQU1DLEdBQUEsTUFBSyxPQUFTQSxHQUN4QkMsUUFBUUMsSUFBSUgsSUFBSSJ9",
""
].join("\n"));
done();
Expand All @@ -46,7 +46,7 @@ describe("bin/terser (2)", function() {

assert.strictEqual(stdout, [
'function foo(){return function(){console.log("PASS")}}foo()();',
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMjMxMC9pbnB1dC5qcyJdLCJuYW1lcyI6WyJmb28iLCJjb25zb2xlIiwibG9nIiwiZiJdLCJtYXBwaW5ncyI6IkFBQUEsU0FBU0EsTUFDTCxPQUFPLFdBQ0hDLFFBQVFDLElBQUksU0FLUkYsS0FDUkcifQ==",
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmb28iLCJjb25zb2xlIiwibG9nIiwiZiJdLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMjMxMC9pbnB1dC5qcyJdLCJtYXBwaW5ncyI6IkFBQUEsU0FBU0EsTUFDTCxPQUFPLFdBQ0hDLFFBQVFDLElBQUksU0FLUkYsS0FDUkcifQ==",
""
].join("\n"));
done();
Expand Down
2 changes: 1 addition & 1 deletion test/mocha/minify.js
Expand Up @@ -321,7 +321,7 @@ describe("minify", function() {
});
var code = result.code;
assert.strictEqual(code, "var a=function(n){return n};\n" +
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjAiXSwibmFtZXMiOlsiYSIsImZvbyJdLCJtYXBwaW5ncyI6IkFBQUEsSUFBSUEsRUFBSSxTQUFTQyxHQUFPLE9BQU9BIn0=");
"//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJhIiwiZm9vIl0sInNvdXJjZXMiOlsiMCJdLCJtYXBwaW5ncyI6IkFBQUEsSUFBSUEsRUFBSSxTQUFTQyxHQUFPLE9BQU9BIn0=");
});
it("should not append source map to output js when sourceMapInline is not enabled", async function() {
var result = await minify("var a = function(foo) { return foo; };");
Expand Down

0 comments on commit a47f29a

Please sign in to comment.