From c82fc1ef717309059b24eda00b236c7b662251c3 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 23 May 2022 22:45:47 +0100 Subject: [PATCH] implement `--module` (#5462) --- README.md | 7 +++++++ bin/uglifyjs | 2 ++ lib/compress.js | 5 ++++- lib/minify.js | 4 +++- lib/parse.js | 17 +++++++---------- test/input/module/expect.js | 1 + test/input/module/input.js | 13 +++++++++++++ test/mocha/cli.js | 8 ++++++++ 8 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 test/input/module/expect.js create mode 100644 test/input/module/input.js diff --git a/README.md b/README.md index 750b940e..effb8220 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ a double dash to prevent input files being used as option arguments: --keep-fargs Do not mangle/drop function arguments. --keep-fnames Do not mangle/drop function names. Useful for code relying on Function.prototype.name. + --module Process input as ES module (implies --toplevel) --name-cache File to hold mangled name mappings. --self Build UglifyJS as a library (implies --wrap UglifyJS) --source-map [options] Enable source map/specify source map options: @@ -517,6 +518,9 @@ if (result.error) throw result.error; - `mangle.properties` (default: `false`) — a subcategory of the mangle option. Pass an object to specify custom [mangle property options](#mangle-properties-options). +- `module` (default: `false`) — set to `true` if you wish to process input as + ES module, i.e. implicit `"use strict";` alongside with `toplevel` enabled. + - `nameCache` (default: `null`) — pass an empty object `{}` or a previously used `nameCache` object if you wish to cache mangled variable and property names across multiple invocations of `minify()`. Note: this is @@ -728,6 +732,9 @@ to be `false` and all symbol names will be omitted. - `merge_vars` (default: `true`) — combine and reuse variables. +- `module` (default: `false`) — set to `true` if you wish to process input as + ES module, i.e. implicit `"use strict";` alongside with `toplevel` enabled. + - `negate_iife` (default: `true`) — negate "Immediately-Called Function Expressions" where the return value is discarded, to avoid the parens that the code generator would insert. diff --git a/bin/uglifyjs b/bin/uglifyjs index d7a6498e..304d84de 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -107,6 +107,7 @@ function process_option(name, no_value) { " --ie Support non-standard Internet Explorer.", " --keep-fargs Do not mangle/drop function arguments.", " --keep-fnames Do not mangle/drop function names. Useful for code relying on Function.prototype.name.", + " --module Process input as ES module (implies --toplevel)", " --name-cache File to hold mangled name mappings.", " --rename Force symbol expansion.", " --no-rename Disable symbol expansion.", @@ -152,6 +153,7 @@ function process_option(name, no_value) { case "annotations": case "ie": case "ie8": + case "module": case "timings": case "toplevel": case "v8": diff --git a/lib/compress.js b/lib/compress.js index 1b6ffcb7..db6967d7 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -80,6 +80,7 @@ function Compressor(options, false_by_default) { keep_infinity : false, loops : !false_by_default, merge_vars : !false_by_default, + module : false, negate_iife : !false_by_default, objects : !false_by_default, optional_chains : !false_by_default, @@ -97,7 +98,7 @@ function Compressor(options, false_by_default) { switches : !false_by_default, templates : !false_by_default, top_retain : null, - toplevel : !!(options && options["top_retain"]), + toplevel : !!(options && (options["module"] || options["top_retain"])), typeofs : !false_by_default, unsafe : false, unsafe_comps : false, @@ -130,6 +131,7 @@ function Compressor(options, false_by_default) { var escaped = def.escaped; return escaped && escaped.depth != 1; }; + if (this.options["module"]) this.directives["use strict"] = true; var pure_funcs = this.options["pure_funcs"]; if (typeof pure_funcs == "function") { this.pure_funcs = pure_funcs; @@ -7360,6 +7362,7 @@ Compressor.prototype.compress = function(node) { } }); tt.push(compressor.parent()); + tt.directives = Object.create(compressor.directives); self.transform(tt); if (self instanceof AST_Lambda && self.body.length == 1 diff --git a/lib/minify.js b/lib/minify.js index c6560473..2e9c3325 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -81,13 +81,14 @@ function minify(files, options) { keep_fargs: false, keep_fnames: false, mangle: {}, + module: false, nameCache: null, output: {}, parse: {}, rename: undefined, sourceMap: false, timings: false, - toplevel: false, + toplevel: !!(options && options["module"]), v8: false, validate: false, warnings: false, @@ -101,6 +102,7 @@ function minify(files, options) { if (options.ie) set_shorthand("ie", options, [ "compress", "mangle", "output", "rename" ]); if (options.keep_fargs) set_shorthand("keep_fargs", options, [ "compress", "mangle", "rename" ]); if (options.keep_fnames) set_shorthand("keep_fnames", options, [ "compress", "mangle", "rename" ]); + if (options.module) set_shorthand("module", options, [ "compress", "parse" ]); if (options.toplevel) set_shorthand("toplevel", options, [ "compress", "mangle", "rename" ]); if (options.v8) set_shorthand("v8", options, [ "mangle", "output", "rename" ]); if (options.webkit) set_shorthand("webkit", options, [ "compress", "mangle", "output", "rename" ]); diff --git a/lib/parse.js b/lib/parse.js index a03300e9..9b2ceb78 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -237,8 +237,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { newline_before : false, regex_allowed : false, comments_before : [], - directives : {}, - directive_stack : [], + directives : Object.create(null), read_template : with_eof_error("Unterminated template literal", function(strings) { var s = ""; for (;;) { @@ -635,23 +634,19 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { }; next_token.add_directive = function(directive) { - S.directive_stack[S.directive_stack.length - 1].push(directive); - S.directives[directive] = (S.directives[directive] || 0) + 1; + S.directives[directive] = true; } next_token.push_directives_stack = function() { - S.directive_stack.push([]); + S.directives = Object.create(S.directives); } next_token.pop_directives_stack = function() { - var directives = S.directive_stack.pop(); - for (var i = directives.length; --i >= 0;) { - S.directives[directives[i]]--; - } + S.directives = Object.getPrototypeOf(S.directives); } next_token.has_directive = function(directive) { - return S.directives[directive] > 0; + return !!S.directives[directive]; } return next_token; @@ -698,6 +693,7 @@ function parse($TEXT, options) { expression : false, filename : null, html5_comments : true, + module : false, shebang : true, strict : false, toplevel : null, @@ -2545,6 +2541,7 @@ function parse($TEXT, options) { return function() { var start = S.token; var body = []; + if (options.module) S.input.add_directive("use strict"); S.input.push_directives_stack(); while (!is("eof")) body.push(statement()); diff --git a/test/input/module/expect.js b/test/input/module/expect.js new file mode 100644 index 00000000..01f3aeed --- /dev/null +++ b/test/input/module/expect.js @@ -0,0 +1 @@ +function n(){return this||arguments[0]+arguments[1]}function o(){return this||arguments[0]+arguments[1]}console.log(n(n(1,3),5)),console.log(o(o(2,4),6)); diff --git a/test/input/module/input.js b/test/input/module/input.js new file mode 100644 index 00000000..9ee80225 --- /dev/null +++ b/test/input/module/input.js @@ -0,0 +1,13 @@ +console.log(function() { + function sum(...params) { + return this || arguments[0] + arguments[1]; + } + return sum(sum(1, 3), 5); +}()); +console.log(function() { + "use strict"; + function sum(...params) { + return this || arguments[0] + arguments[1]; + } + return sum(sum(2, 4), 6); +}()); \ No newline at end of file diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 6e9f124a..34b97101 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -928,6 +928,14 @@ describe("bin/uglifyjs", function() { done(); }); }); + it("Should work with --module", function(done) { + var command = uglifyjscmd + " test/input/module/input.js --module -mc"; + exec(command, function(err, stdout, stderr) { + if (err) throw err; + assert.strictEqual(stdout, read("test/input/module/expect.js")); + done(); + }); + }); it("Should compress swarm of unused variables with reasonable performance", function(done) { var code = [ "console.log(function() {",