diff --git a/README.md b/README.md index 842cfd3a..f88a1972 100644 --- a/README.md +++ b/README.md @@ -110,6 +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. 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()` @@ -496,6 +498,10 @@ 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. 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 for best compression. @@ -554,6 +560,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, @@ -702,6 +709,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. @@ -823,6 +833,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"]`. diff --git a/bin/uglifyjs b/bin/uglifyjs index 21635bcb..9adc0c1c 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", @@ -195,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) { diff --git a/lib/compress.js b/lib/compress.js index 886a4b5b..2654a077 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, @@ -130,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), diff --git a/lib/minify.js b/lib/minify.js index ee1d478b..e360da01 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, [ "parse", "compress", "mangle" ]); set_shorthand("safari10", options, [ "mangle", "output" ]); set_shorthand("toplevel", options, [ "compress", "mangle" ]); set_shorthand("warnings", options, [ "compress" ]); @@ -87,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/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(); diff --git a/lib/scope.js b/lib/scope.js index 09687aaf..850a1e69 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -514,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 b85214ec..9bad924c 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -1601,3 +1601,52 @@ issue_2874_3: { ] node_version: ">=6" } + +module_enables_strict_mode: { + options = { + module: true, + } + input: { + if (1) { + function xyz() {} + } + } + expect: { + if (1) { + function xyz() {} + } + } +} + +module_mangle_scope: { + mangle = { + module: true + } + input: { + let a = 10; + } + expect: { + 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/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 }; 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) {