From 041482fd44390fe618673b93dc06b1cc51dcd2da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Thu, 15 Mar 2018 00:27:11 +0000 Subject: [PATCH 1/8] --module option, enables strict mode and toplevel options --- README.md | 1 + bin/uglifyjs | 2 ++ lib/compress.js | 4 ++++ lib/minify.js | 2 ++ test/compress/harmony.js | 16 ++++++++++++++++ 5 files changed, 25 insertions(+) diff --git a/README.md b/README.md index 842cfd3a..11f09b7f 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ a double dash to prevent input files being used as option arguments: --keep-classnames Do not mangle/drop class names. --keep-fnames Do not mangle/drop function names. Useful for code relying on Function.prototype.name. + --module Input is an ES6 module --name-cache File to hold mangled name mappings. --safari10 Support non-standard Safari 10/11. Equivalent to setting `safari10: true` in `minify()` diff --git a/bin/uglifyjs b/bin/uglifyjs index 21635bcb..41bdef2b 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -44,6 +44,7 @@ program.option("--ecma ", "Specify ECMAScript release: 5, 6, 7 or 8."); program.option("--ie8", "Support non-standard Internet Explorer 8."); program.option("--keep-classnames", "Do not mangle/drop class names."); program.option("--keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name."); +program.option("--module", "Input is an ES6 module"); program.option("--name-cache ", "File to hold mangled name mappings."); program.option("--rename", "Force symbol expansion."); program.option("--no-rename", "Disable symbol expansion."); @@ -66,6 +67,7 @@ if (!program.output && program.sourceMap && program.sourceMap.url != "inline") { "compress", "ie8", "mangle", + "module", "safari10", "sourceMap", "toplevel", diff --git a/lib/compress.js b/lib/compress.js index 886a4b5b..37de2b43 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -73,6 +73,7 @@ function Compressor(options, false_by_default) { keep_fnames : false, keep_infinity : false, loops : !false_by_default, + module : false, negate_iife : !false_by_default, passes : 1, properties : !false_by_default, @@ -138,6 +139,9 @@ function Compressor(options, false_by_default) { funcs: toplevel, vars: toplevel }; + if (this.options['module']) { + this.directives['use strict'] = true; + } var sequences = this.options["sequences"]; this.sequences_limit = sequences == 1 ? 800 : sequences | 0; this.warnings_produced = {}; diff --git a/lib/minify.js b/lib/minify.js index ee1d478b..fdc48961 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -52,6 +52,7 @@ function minify(files, options) { keep_classnames: undefined, keep_fnames: false, mangle: {}, + module: false, nameCache: null, output: {}, parse: {}, @@ -76,6 +77,7 @@ function minify(files, options) { set_shorthand("ie8", options, [ "compress", "mangle", "output" ]); set_shorthand("keep_classnames", options, [ "compress", "mangle" ]); set_shorthand("keep_fnames", options, [ "compress", "mangle" ]); + set_shorthand("module", options, [ "compress" ]); set_shorthand("safari10", options, [ "mangle", "output" ]); set_shorthand("toplevel", options, [ "compress", "mangle" ]); set_shorthand("warnings", options, [ "compress" ]); diff --git a/test/compress/harmony.js b/test/compress/harmony.js index b85214ec..7d2fdb93 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -1601,3 +1601,19 @@ issue_2874_3: { ] node_version: ">=6" } + +module_enables_strict_mode: { + options = { + module: true, + } + input: { + if (1) { + function xyz() {} + } + } + expect: { + if (1) { + function xyz() {} + } + } +} From 924f7bc3a97c1473cb6885da32c84b9fe6f28d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Thu, 15 Mar 2018 15:17:45 +0000 Subject: [PATCH 2/8] module-level scope --- lib/minify.js | 2 +- lib/scope.js | 6 +++++- test/compress/harmony.js | 12 ++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/minify.js b/lib/minify.js index fdc48961..73f0e456 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -77,7 +77,7 @@ function minify(files, options) { set_shorthand("ie8", options, [ "compress", "mangle", "output" ]); set_shorthand("keep_classnames", options, [ "compress", "mangle" ]); set_shorthand("keep_fnames", options, [ "compress", "mangle" ]); - set_shorthand("module", options, [ "compress" ]); + set_shorthand("module", options, [ "compress", "mangle" ]); set_shorthand("safari10", options, [ "mangle", "output" ]); set_shorthand("toplevel", options, [ "compress", "mangle" ]); set_shorthand("warnings", options, [ "compress" ]); diff --git a/lib/scope.js b/lib/scope.js index 09687aaf..de572f3e 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -110,7 +110,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ // pass 1: setup scope chaining and handle definitions var self = this; - var scope = self.parent_scope = null; + var scope = null; + if (options.module) { + scope = new AST_Scope(self); + } + self.parent_scope = scope; var labels = new Dictionary(); var defun = null; var in_destructuring = null; diff --git a/test/compress/harmony.js b/test/compress/harmony.js index 7d2fdb93..c0eaab44 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -1617,3 +1617,15 @@ module_enables_strict_mode: { } } } + +module_mangle_scope: { + mangle = { + module: true + } + input: { + let a = 10; + } + expect: { + let e = 10; + } +} From 6dacae769d36c35a3e6d5824ba5a12e16a3331d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Fri, 16 Mar 2018 15:28:23 +0000 Subject: [PATCH 3/8] suggestions from @kzc --- lib/compress.js | 7 ++++--- lib/minify.js | 1 + lib/scope.js | 10 +++++----- test/compress/harmony.js | 21 +++++++++++++++++++++ test/mocha/cli.js | 9 +++++++++ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 37de2b43..2654a077 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -131,6 +131,10 @@ function Compressor(options, false_by_default) { return top_retain.indexOf(def.name) >= 0; }; } + if (this.options["module"]) { + this.directives["use strict"] = true; + this.options["toplevel"] = true; + } var toplevel = this.options["toplevel"]; this.toplevel = typeof toplevel == "string" ? { funcs: /funcs/.test(toplevel), @@ -139,9 +143,6 @@ function Compressor(options, false_by_default) { funcs: toplevel, vars: toplevel }; - if (this.options['module']) { - this.directives['use strict'] = true; - } var sequences = this.options["sequences"]; this.sequences_limit = sequences == 1 ? 800 : sequences | 0; this.warnings_produced = {}; diff --git a/lib/minify.js b/lib/minify.js index 73f0e456..a4b74f1c 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -89,6 +89,7 @@ function minify(files, options) { ie8: false, keep_classnames: false, keep_fnames: false, + module: false, properties: false, reserved: [], safari10: false, diff --git a/lib/scope.js b/lib/scope.js index de572f3e..850a1e69 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -110,11 +110,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ // pass 1: setup scope chaining and handle definitions var self = this; - var scope = null; - if (options.module) { - scope = new AST_Scope(self); - } - self.parent_scope = scope; + var scope = self.parent_scope = null; var labels = new Dictionary(); var defun = null; var in_destructuring = null; @@ -518,9 +514,13 @@ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options) { ie8 : false, keep_classnames: false, keep_fnames : false, + module : false, reserved : [], toplevel : false, }); + if (options["module"]) { + options.toplevel = true; + } if (!Array.isArray(options.reserved)) options.reserved = []; // Never mangle arguments push_uniq(options.reserved, "arguments"); diff --git a/test/compress/harmony.js b/test/compress/harmony.js index c0eaab44..9bad924c 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -1629,3 +1629,24 @@ module_mangle_scope: { let e = 10; } } + +module_enabled: { + options = { + module: true, + reduce_vars: true, + unused: true, + } + mangle = { + module: true, + } + input: { + let apple = 10, b = 20; + console.log(apple++, b, apple++); + export { apple }; + } + expect: { + let o = 10; + console.log(o++, 20, o++); + export { o as apple }; + } +} diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 3804d823..a02de977 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -734,6 +734,15 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("Should mangle toplevel names with the --module option", 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, "let e=1;export{e as foo};\n") + done(); + }); + }); it("Should fail with --define a-b", function(done) { var command = uglifyjscmd + " test/input/issue-505/input.js --define a-b"; exec(command, function (err, stdout, stderr) { From 929d21e0b349f174780dbdb37d656a5894499650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Fri, 16 Mar 2018 15:29:41 +0000 Subject: [PATCH 4/8] add missing file --- test/input/module/input.js | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test/input/module/input.js diff --git a/test/input/module/input.js b/test/input/module/input.js new file mode 100644 index 00000000..5f6943cd --- /dev/null +++ b/test/input/module/input.js @@ -0,0 +1,2 @@ +let foo = 1, bar = 2; +export { foo }; From a436b8a7ecf4112ce0602b0dab5599a3a17c6078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Fri, 16 Mar 2018 15:37:17 +0000 Subject: [PATCH 5/8] add `module` option documentation --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 11f09b7f..9e00a27d 100644 --- a/README.md +++ b/README.md @@ -497,6 +497,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`) — Use when minifying an ES6 module. "use strict" + is implied and names can be mangled on the top scope. + - `output` (default `null`) — pass an object if you wish to specify additional [output options](#output-options). The defaults are optimized for best compression. @@ -555,6 +558,7 @@ if (result.error) throw result.error; keep_classnames: false, keep_fnames: false, ie8: false, + module: false, nameCache: null, // or specify a name cache object safari10: false, toplevel: false, @@ -703,6 +707,9 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `loops` (default: `true`) -- optimizations for `do`, `while` and `for` loops when we can statically determine the condition. +- `module` (default `false`) -- Pass `true` when compressing an ES6 module. Strict + mode is implied and the `toplevel` option as well. + - `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. @@ -824,6 +831,9 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u Useful for code relying on `Function.prototype.name`. See also: the `keep_fnames` [compress option](#compress-options). +- `module` (default `false`) -- Pass `true` an ES6 modules, where the toplevel + scope is not the global scope. Implies `toplevel`. + - `reserved` (default `[]`) -- Pass an array of identifiers that should be excluded from mangling. Example: `["foo", "bar"]`. From 5da9c304bd24b3284efe858cef940646302f025e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Sun, 18 Mar 2018 17:17:35 +0000 Subject: [PATCH 6/8] documentation suggestions from @kzc --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e00a27d..f88a1972 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,8 @@ a double dash to prevent input files being used as option arguments: --keep-classnames Do not mangle/drop class names. --keep-fnames Do not mangle/drop function names. Useful for code relying on Function.prototype.name. - --module Input is an ES6 module + --module Input is an ES6 module. If `compress` or `mangle` is + enabled then the `toplevel` option will be enabled. --name-cache File to hold mangled name mappings. --safari10 Support non-standard Safari 10/11. Equivalent to setting `safari10: true` in `minify()` @@ -498,7 +499,8 @@ if (result.error) throw result.error; Pass an object to specify custom [mangle property options](#mangle-properties-options). - `module` (default `false`) — Use when minifying an ES6 module. "use strict" - is implied and names can be mangled on the top scope. + is implied and names can be mangled on the top scope. If `compress` or + `mangle` is enabled then the `toplevel` option will be enabled. - `output` (default `null`) — pass an object if you wish to specify additional [output options](#output-options). The defaults are optimized From 6e9be5486cd9e2060f2b179fdeaa7d21bc52bc10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Mon, 19 Mar 2018 16:43:10 +0000 Subject: [PATCH 7/8] pass module option to parse as well. --- lib/minify.js | 2 +- lib/parse.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/minify.js b/lib/minify.js index a4b74f1c..e360da01 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -77,7 +77,7 @@ function minify(files, options) { set_shorthand("ie8", options, [ "compress", "mangle", "output" ]); set_shorthand("keep_classnames", options, [ "compress", "mangle" ]); set_shorthand("keep_fnames", options, [ "compress", "mangle" ]); - set_shorthand("module", options, [ "compress", "mangle" ]); + set_shorthand("module", options, [ "parse", "compress", "mangle" ]); set_shorthand("safari10", options, [ "mangle", "output" ]); set_shorthand("toplevel", options, [ "compress", "mangle" ]); set_shorthand("warnings", options, [ "compress" ]); diff --git a/lib/parse.js b/lib/parse.js index d8cc9ac4..bd2434ad 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -859,6 +859,7 @@ function parse($TEXT, options) { expression : false, filename : null, html5_comments : true, + module : false, shebang : true, strict : false, toplevel : null, @@ -2913,6 +2914,7 @@ function parse($TEXT, options) { var start = S.token; var body = []; S.input.push_directives_stack(); + if (options.module) S.input.add_directive("use strict"); while (!is("eof")) body.push(statement()); S.input.pop_directives_stack(); From bc307c4559081c16ca43979ce8b6f10f762c0852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Mon, 19 Mar 2018 16:46:18 +0000 Subject: [PATCH 8/8] enable sourceType:module in acorn when necessary --- bin/uglifyjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 41bdef2b..9adc0c1c 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -197,7 +197,8 @@ function run() { return require("acorn").parse(files[name], { locations: true, program: toplevel, - sourceFile: name + sourceFile: name, + sourceType: options.module || program.parse.module ? "module" : "script" }); }); } else if (program.parse.spidermonkey) {