diff --git a/lib/ast.js b/lib/ast.js index 234616f7..7668dabf 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -629,11 +629,11 @@ var AST_Const = DEFNODE("Const", null, { $documentation: "A `const` statement" }, AST_Definitions); -var AST_NameImport = DEFNODE("NameImport", "foreign_name name", { - $documentation: "The part of the import statement that imports names from a module.", +var AST_NameMapping = DEFNODE("NameMapping", "foreign_name name", { + $documentation: "The part of the export/import statement that declare names from a module.", $propdoc: { - foreign_name: "[AST_SymbolImportForeign] The name being imported (as specified in the module)", - name: "[AST_SymbolImport] The name as it becomes available to this module." + foreign_name: "[AST_SymbolExportForeign|AST_SymbolImportForeign] The name being exported/imported (as specified in the module)", + name: "[AST_SymbolExport|AST_SymbolImport] The name as it is visible to this module." }, _walk: function (visitor) { return visitor._visit(this, function() { @@ -647,7 +647,7 @@ var AST_Import = DEFNODE("Import", "imported_name imported_names module_name", { $documentation: "An `import` statement", $propdoc: { imported_name: "[AST_SymbolImport] The name of the variable holding the module's default export.", - imported_names: "[AST_NameImport*] The names of non-default imported variables", + imported_names: "[AST_NameMapping*] The names of non-default imported variables", module_name: "[AST_String] String literal describing where this module came from", }, _walk: function(visitor) { @@ -656,7 +656,7 @@ var AST_Import = DEFNODE("Import", "imported_name imported_names module_name", { this.imported_name._walk(visitor); } if (this.imported_names) { - this.imported_names.forEach(function (name_import) { + this.imported_names.forEach(function(name_import) { name_import._walk(visitor); }); } @@ -670,7 +670,7 @@ var AST_Export = DEFNODE("Export", "exported_definition exported_value is_defaul $propdoc: { exported_definition: "[AST_Defun|AST_Definitions|AST_DefClass?] An exported definition", exported_value: "[AST_Node?] An exported value", - exported_names: "[AST_NameImport*?] List of exported names", + exported_names: "[AST_NameMapping*?] List of exported names", module_name: "[AST_String?] Name of the file to load exports from", is_default: "[Boolean] Whether this is the default exported value of this module" }, @@ -682,6 +682,14 @@ var AST_Export = DEFNODE("Export", "exported_definition exported_value is_defaul if (this.exported_value) { this.exported_value._walk(visitor); } + if (this.exported_names) { + this.exported_names.forEach(function(name_export) { + name_export._walk(visitor); + }); + } + if (this.module_name) { + this.module_name._walk(visitor); + } }); } }, AST_Statement); @@ -996,7 +1004,7 @@ var AST_SymbolCatch = DEFNODE("SymbolCatch", null, { }, AST_SymbolBlockDeclaration); var AST_SymbolImport = DEFNODE("SymbolImport", null, { - $documentation: "Symbol refering to an imported name", + $documentation: "Symbol referring to an imported name", }, AST_SymbolBlockDeclaration); var AST_SymbolImportForeign = DEFNODE("SymbolImportForeign", null, { @@ -1018,6 +1026,14 @@ var AST_SymbolRef = DEFNODE("SymbolRef", null, { $documentation: "Reference to some symbol (not definition/declaration)", }, AST_Symbol); +var AST_SymbolExport = DEFNODE("SymbolExport", null, { + $documentation: "Symbol referring to a name to export", +}, AST_SymbolRef); + +var AST_SymbolExportForeign = DEFNODE("SymbolExportForeign", null, { + $documentation: "A symbol exported from this module, but it is used in the other module, and its real name is irrelevant for this module's purposes", +}, AST_Symbol); + var AST_LabelRef = DEFNODE("LabelRef", null, { $documentation: "Reference to a label symbol", }, AST_Symbol); diff --git a/lib/compress.js b/lib/compress.js index d6a1830a..c67a3f40 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -672,7 +672,9 @@ merge(Compressor.prototype, { node instanceof AST_DefClass || node instanceof AST_Defun || node instanceof AST_Let || - node instanceof AST_Const + node instanceof AST_Const || + node instanceof AST_Export || + node instanceof AST_Import ); } @@ -2099,15 +2101,16 @@ merge(Compressor.prototype, { return true; // don't go in nested scopes } if (node instanceof AST_Definitions && scope === self) { + var in_export = tw.parent() instanceof AST_Export; node.definitions.forEach(function(def){ if (def.name instanceof AST_SymbolVar) { var_defs_by_id.add(def.name.definition().id, def); } - if (!drop_vars) { + if (in_export || !drop_vars) { def.name.walk(new TreeWalker(function(node) { if (node instanceof AST_SymbolDeclaration) { var def = node.definition(); - if (def.global && !(def.id in in_use_ids)) { + if ((in_export || def.global) && !(def.id in in_use_ids)) { in_use_ids[def.id] = true; in_use.push(def); } @@ -4037,6 +4040,10 @@ merge(Compressor.prototype, { return self; }); + OPT(AST_SymbolExport, function(self, compressor){ + return self; + }); + OPT(AST_SymbolRef, function(self, compressor){ var def = self.resolve_defines(compressor); if (def) { diff --git a/lib/output.js b/lib/output.js index a30800c1..1a95dd14 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1280,7 +1280,6 @@ function OutputStream(options) { name_import.print(output); if (i < self.imported_names.length - 1) { output.print(","); - output.space(); } }); output.space(); @@ -1296,17 +1295,26 @@ function OutputStream(options) { output.semicolon(); }); - DEFPRINT(AST_NameImport, function(self, output) { + DEFPRINT(AST_NameMapping, function(self, output) { + var is_import = output.parent() instanceof AST_Import; var definition = self.name.definition(); var names_are_different = (definition && definition.mangled_name || self.name.name) !== self.foreign_name.name; if (names_are_different) { - output.print(self.foreign_name.name); + if (is_import) { + output.print(self.foreign_name.name); + } else { + self.name.print(output); + } output.space(); output.print("as"); output.space(); - self.name.print(output); + if (is_import) { + self.name.print(output); + } else { + output.print(self.foreign_name.name); + } } else { self.name.print(output); } @@ -1320,24 +1328,20 @@ function OutputStream(options) { output.space(); } if (self.exported_names) { - output.space(); - if (self.exported_names.length === 1 && self.exported_names[0].name.name === "*") { self.exported_names[0].print(output); } else { output.print("{"); - self.exported_names.forEach(function (name_import, i) { + self.exported_names.forEach(function(name_export, i) { output.space(); - name_import.print(output); + name_export.print(output); if (i < self.exported_names.length - 1) { output.print(","); - output.space(); } }); output.space(); output.print("}"); } - output.space(); } else if (self.exported_value) { self.exported_value.print(output); diff --git a/lib/parse.js b/lib/parse.js index 9a5e75cb..27ecba59 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -2302,7 +2302,7 @@ function parse($TEXT, options) { next(); } - imported_names = import_names(true); + imported_names = map_names(true); if (imported_names || imported_name) { expect_token("name", "from"); @@ -2326,26 +2326,32 @@ function parse($TEXT, options) { }); } - function import_name() { + function map_name(is_import) { + var foreign_type = is_import ? AST_SymbolImportForeign : AST_SymbolExportForeign; + var type = is_import ? AST_SymbolImport : AST_SymbolExport; var start = S.token; var foreign_name; var name; - if (peek().value === "as" && peek().type === "name") { - foreign_name = as_symbol(AST_SymbolImportForeign); + if (is_import) { + foreign_name = as_symbol(foreign_type); + } else { + name = as_symbol(type); + } + if (is("name", "as")) { next(); // The "as" word - } - name = as_symbol(AST_SymbolImport); - - if (foreign_name === undefined) { - foreign_name = new AST_SymbolImportForeign({ - name: name.name, - start: name.start, - end: name.end, - }); + if (is_import) { + name = as_symbol(type); + } else { + foreign_name = as_symbol(foreign_type); + } + } else if (is_import) { + name = new type(foreign_name); + } else { + foreign_name = new foreign_type(name); } - return new AST_NameImport({ + return new AST_NameMapping({ start: start, foreign_name: foreign_name, name: name, @@ -2353,26 +2359,26 @@ function parse($TEXT, options) { }) } - function import_nameAsterisk(name) { + function map_nameAsterisk(is_import, name) { + var foreign_type = is_import ? AST_SymbolImportForeign : AST_SymbolExportForeign; + var type = is_import ? AST_SymbolImport : AST_SymbolExport; var start = S.token; var foreign_name; - - var end = prev(); - name = name || new AST_SymbolImport({ + name = name || new type({ name: '*', start: start, end: end, }); - foreign_name = new AST_SymbolImportForeign({ + foreign_name = new foreign_type({ name: '*', start: start, end: end, }); - return new AST_NameImport({ + return new AST_NameMapping({ start: start, foreign_name: foreign_name, name: name, @@ -2380,13 +2386,13 @@ function parse($TEXT, options) { }) } - function import_names(allow_as) { + function map_names(is_import) { var names; if (is("punc", "{")) { next(); names = []; while (!is("punc", "}")) { - names.push(import_name()); + names.push(map_name(is_import)); if (is("punc", ",")) { next(); } @@ -2395,11 +2401,11 @@ function parse($TEXT, options) { } else if (is("operator", "*")) { var name; next(); - if (allow_as && is("name", "as")) { + if (is_import && is("name", "as")) { next(); // The "as" word name = as_symbol(AST_SymbolImportForeign); } - names = [import_nameAsterisk(name)]; + names = [map_nameAsterisk(is_import, name)]; } return names; } @@ -2415,7 +2421,7 @@ function parse($TEXT, options) { is_default = true; next(); } else { - exported_names = import_names(false); + exported_names = map_names(false); if (exported_names) { if (is("name", "from")) { diff --git a/lib/transform.js b/lib/transform.js index 1f10e274..0d52199e 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -243,9 +243,22 @@ TreeTransformer.prototype = new TreeWalker; self.expression = self.expression.transform(tw); }); - _(AST_Export, function(self, tw){ + _(AST_NameMapping, function(self, tw) { + self.foreign_name = self.foreign_name.transform(tw); + self.name = self.name.transform(tw); + }); + + _(AST_Import, function(self, tw) { + if (self.imported_name) self.imported_name = self.imported_name.transform(tw); + if (self.imported_names) do_list(self.imported_names, tw); + self.module_name = self.module_name.transform(tw); + }); + + _(AST_Export, function(self, tw) { if (self.exported_definition) self.exported_definition = self.exported_definition.transform(tw); if (self.exported_value) self.exported_value = self.exported_value.transform(tw); + if (self.exported_names) do_list(self.exported_names, tw); + if (self.module_name) self.module_name = self.module_name.transform(tw); }); _(AST_TemplateString, function(self, tw) { diff --git a/test/compress/export.js b/test/compress/export.js new file mode 100644 index 00000000..b37bbfdf --- /dev/null +++ b/test/compress/export.js @@ -0,0 +1,97 @@ +issue_2038_1: { + options = { + toplevel: true, + unused: true, + } + mangle = { + toplevel: true, + } + input: { + export var V = 1; + export let L = 2; + export const C = 3; + } + expect: { + export var V = 1; + export let L = 2; + export const C = 3; + } +} + +issue_2038_2: { + options = { + toplevel: true, + unused: true, + } + mangle = { + toplevel: true, + } + input: { + let LET = 1; + const CONST = 2; + var VAR = 3; + export { LET, CONST, VAR }; + } + expect: { + let a = 1; + const c = 2; + var n = 3; + export { a as LET, c as CONST, n as VAR }; + } +} + +issue_2124: { + options = { + unused: true, + } + input: { + { + export var V = 1; + } + { + export let L = 2; + } + { + export const C = 3; + } + } + expect: { + { + export var V = 1; + } + { + export let L = 2; + } + { + export const C = 3; + } + } +} + +issue_2126: { + mangle = { + toplevel: true, + } + input: { + import { foo as bar, cat as dog } from "stuff"; + console.log(bar, dog); + export { bar as qux }; + export { dog }; + } + expect: { + import { foo as o, cat as f } from "stuff"; + console.log(o, f); + export { o as qux }; + export { f as dog }; + } +} + +beautify: { + beautify = { + beautify: true, + } + input: { + export { A as B, C as D }; + } + expect_exact: "export { A as B, C as D };" +} diff --git a/test/mocha/export.js b/test/mocha/export.js index 906aa68c..e9bb8503 100644 --- a/test/mocha/export.js +++ b/test/mocha/export.js @@ -2,8 +2,7 @@ var assert = require("assert"); var uglify = require("../node"); describe("Export", function() { - it ("Should parse export directives", function() { - + it("Should parse export directives", function() { var inputs = [ ['export * from "a.js"', ['*'], "a.js"], ['export {A} from "a.js"', ['A'], "a.js"], @@ -12,17 +11,17 @@ describe("Export", function() { ['export {A, B} from "a.js"', ['A', 'B'], "a.js"], ]; - var test = function(code) { + function test(code) { return uglify.parse(code); - }; + } - var extractNames = function(symbols) { + function extractNames(symbols) { var ret = []; for (var i = 0; i < symbols.length; i++) { - ret.push(symbols[i].name.name) + ret.push(symbols[i].foreign_name.name); } return ret; - }; + } for (var i = 0; i < inputs.length; i++) { var ast = test(inputs[i][0]); @@ -34,7 +33,7 @@ describe("Export", function() { assert(st instanceof uglify.AST_Export); var actualNames = extractNames(st.exported_names); assert.deepEqual(actualNames, names); - assert.equal(st.module_name.value, filename) + assert.equal(st.module_name.value, filename); } - }) -}); \ No newline at end of file + }); +});