diff --git a/packages/babel-plugin-minify-dead-code-elimination/README.md b/packages/babel-plugin-minify-dead-code-elimination/README.md index c9dd14a64..1f3697c73 100644 --- a/packages/babel-plugin-minify-dead-code-elimination/README.md +++ b/packages/babel-plugin-minify-dead-code-elimination/README.md @@ -69,3 +69,4 @@ require("babel-core").transform("code", { + `keepFnName` - prevent plugin from removing function name. Useful for code depending on `fn.name` + `keepFnArgs` - prevent plugin from removing function args. Useful for code depending on `fn.length` ++ `keepClassName` - prevent mangler from altering class names. diff --git a/packages/babel-plugin-minify-dead-code-elimination/__tests__/dead-code-elimination-test.js b/packages/babel-plugin-minify-dead-code-elimination/__tests__/dead-code-elimination-test.js index b709dca1e..4cc160fb5 100644 --- a/packages/babel-plugin-minify-dead-code-elimination/__tests__/dead-code-elimination-test.js +++ b/packages/babel-plugin-minify-dead-code-elimination/__tests__/dead-code-elimination-test.js @@ -1292,12 +1292,12 @@ describe("dce-plugin", () => { expect(transform(source)).toBe(expected); }); - it("should preserve fn/class names when keepFnName is true", () => { + it("should preserve fn names when keepFnName is true", () => { const source = unpad(` (function () { function A() {} exports.A = A; - var B = class B {}; + var B = function B() {}; exports.B = B; onClick(function C() {}); })(); @@ -1306,13 +1306,34 @@ describe("dce-plugin", () => { (function () { exports.A = function A() {}; - exports.B = class B {}; + exports.B = function B() {}; onClick(function C() {}); })(); `); expect(transform(source, { keepFnName: true })).toBe(expected); }); + it("should preserve class names when keepClassName is true", () => { + const source = unpad(` + (function () { + class A {} + exports.A = A; + var B = class B {}; + exports.B = B; + class AA {} new AA() + })(); + `); + const expected = unpad(` + (function () { + exports.A = class A {}; + + exports.B = class B {}; + new class AA {}(); + })(); + `); + expect(transform(source, { keepClassName: true })).toBe(expected); + }); + // NCE = Named Class Expressions it("should remove name from NCE", () => { const source = unpad(` diff --git a/packages/babel-plugin-minify-dead-code-elimination/src/index.js b/packages/babel-plugin-minify-dead-code-elimination/src/index.js index e7394181e..e193d924e 100644 --- a/packages/babel-plugin-minify-dead-code-elimination/src/index.js +++ b/packages/babel-plugin-minify-dead-code-elimination/src/index.js @@ -636,12 +636,19 @@ module.exports = ({ types: t, traverse }) => { // Remove named function expression name. While this is dangerous as it changes // `function.name` all minifiers do it and hence became a standard. - "FunctionExpression|ClassExpression"(path) { + "FunctionExpression"(path) { if (!this.keepFnName) { removeUnreferencedId(path); } }, + // remove class names + "ClassExpression"(path) { + if (!this.keepClassName) { + removeUnreferencedId(path); + } + }, + // Put the `var` in the left if feasible. ForInStatement(path) { const left = path.get("left"); @@ -696,6 +703,7 @@ module.exports = ({ types: t, traverse }) => { // set defaults optimizeRawSize = false, keepFnName = false, + keepClassName = false, keepFnArgs = false, } = {} } = {}) { @@ -707,6 +715,7 @@ module.exports = ({ types: t, traverse }) => { functionToBindings: new Map(), optimizeRawSize, keepFnName, + keepClassName, keepFnArgs, }); } diff --git a/packages/babel-plugin-minify-mangle-names/README.md b/packages/babel-plugin-minify-mangle-names/README.md index eed72ae85..8daa0775f 100644 --- a/packages/babel-plugin-minify-mangle-names/README.md +++ b/packages/babel-plugin-minify-mangle-names/README.md @@ -71,3 +71,4 @@ require("babel-core").transform("code", { + `blacklist` - A plain JS Object with keys as identifier names and values indicating whether to exclude + `eval` - mangle identifiers in scopes accessible by eval + `keepFnName` - prevent mangler from altering function names. Useful for code depending on `fn.name` ++ `keepClassName` - prevent mangler from altering class names. diff --git a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js index a2bf60af1..7ad879552 100644 --- a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js +++ b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js @@ -756,11 +756,9 @@ describe("mangle-names", () => { expect(actual).toBe(expected); }); - it("should NOT mangle functions & classes when keepFnName is true", () => { + it("should NOT mangle functions when keepFnName is true", () => { const source = unpad(` (function() { - class Foo {} - const Bar = class Bar extends Foo {} var foo = function foo() { foo(); } @@ -774,22 +772,46 @@ describe("mangle-names", () => { `); const expected = unpad(` (function () { - class Foo {} - const a = class Bar extends Foo {}; - var b = function foo() { + var a = function foo() { foo(); }; function bar() { - b(); + a(); } bar(); - var c = b; - c(); + var b = a; + b(); })(); `); expect(transform(source, {keepFnName: true})).toBe(expected); }); + it("should NOT mangle classes when keepClassName is true", () => { + const source = unpad(` + (function() { + class Foo {} + const Bar = class Bar extends Foo {} + var foo = class Baz {} + function bar() { + new foo(); + } + bar(); + })(); + `); + const expected = unpad(` + (function () { + class Foo {} + const b = class Bar extends Foo {}; + var c = class Baz {}; + function a() { + new c(); + } + a(); + })(); + `); + expect(transform(source, {keepClassName: true})).toBe(expected); + }); + it("should mangle variable re-declaration / K violations", () => { const source = unpad(` !function () { diff --git a/packages/babel-plugin-minify-mangle-names/src/index.js b/packages/babel-plugin-minify-mangle-names/src/index.js index 1dda889af..da0fe106b 100644 --- a/packages/babel-plugin-minify-mangle-names/src/index.js +++ b/packages/babel-plugin-minify-mangle-names/src/index.js @@ -5,12 +5,14 @@ module.exports = ({ types: t }) => { constructor(charset, program, { blacklist = {}, keepFnName = false, + keepClassName = false, eval: _eval = false } = {}) { this.charset = charset; this.program = program; this.blacklist = blacklist; this.keepFnName = keepFnName; + this.keepClassName = keepClassName; this.eval = _eval; this.unsafeScopes = new Set; @@ -127,6 +129,8 @@ module.exports = ({ types: t }) => { || mangler.isBlacklist(oldName) // function names || (mangler.keepFnName ? isFunction(binding.path) : false) + // class names + || (mangler.keepClassName ? isClass(binding.path) : false) ) { continue; } @@ -280,8 +284,12 @@ class Charset { // for keepFnName function isFunction(path) { return path.isFunctionExpression() - || path.isFunctionDeclaration() - || path.isClassExpression() + || path.isFunctionDeclaration(); +} + +// for keepClassName +function isClass(path) { + return path.isClassExpression() || path.isClassDeclaration(); } diff --git a/packages/babel-preset-babili/__tests__/__snapshots__/options-tests.js.snap b/packages/babel-preset-babili/__tests__/__snapshots__/options-tests.js.snap index 72301e75c..3dad35702 100644 --- a/packages/babel-preset-babili/__tests__/__snapshots__/options-tests.js.snap +++ b/packages/babel-preset-babili/__tests__/__snapshots__/options-tests.js.snap @@ -56,6 +56,7 @@ Object { exports[`preset-options should handle options that are delegated to multiple other options 1`] = ` Object { "input": Object { + "keepClassName": false, "keepFnName": false, }, "output": Array [ @@ -63,6 +64,7 @@ Object { Array [ "babel-plugin-minify-dead-code-elimination", Object { + "keepClassName": false, "keepFnName": false, }, ], @@ -74,6 +76,7 @@ Object { Array [ "babel-plugin-minify-mangle-names", Object { + "keepClassName": false, "keepFnName": false, }, ], @@ -95,6 +98,7 @@ Object { exports[`preset-options should handle options that are delegated to multiple other options 2`] = ` Object { "input": Object { + "keepClassName": true, "keepFnName": true, "mangle": Object { "blacklist": Array [ @@ -108,6 +112,7 @@ Object { Array [ "babel-plugin-minify-dead-code-elimination", Object { + "keepClassName": true, "keepFnName": true, }, ], @@ -123,6 +128,7 @@ Object { "foo", "bar", ], + "keepClassName": true, "keepFnName": true, }, ], @@ -144,11 +150,13 @@ Object { exports[`preset-options should handle options that are delegated to multiple other options 3`] = ` Object { "input": Object { + "keepClassName": true, "keepFnName": true, "mangle": Object { "blacklist": Array [ "baz", ], + "keepClassName": false, "keepFnName": false, }, }, @@ -157,6 +165,7 @@ Object { Array [ "babel-plugin-minify-dead-code-elimination", Object { + "keepClassName": true, "keepFnName": true, }, ], @@ -171,6 +180,7 @@ Object { "blacklist": Array [ "baz", ], + "keepClassName": false, "keepFnName": false, }, ], diff --git a/packages/babel-preset-babili/__tests__/options-tests.js b/packages/babel-preset-babili/__tests__/options-tests.js index 696597ebd..2c83098e0 100644 --- a/packages/babel-preset-babili/__tests__/options-tests.js +++ b/packages/babel-preset-babili/__tests__/options-tests.js @@ -85,19 +85,23 @@ describe("preset-options", () => { it("should handle options that are delegated to multiple other options", () => { testOpts({ - keepFnName: false + keepFnName: false, + keepClassName: false }); testOpts({ keepFnName: true, + keepClassName: true, mangle: { blacklist: ["foo", "bar"] } }); testOpts({ keepFnName: true, + keepClassName: true, mangle: { blacklist: ["baz"], - keepFnName: false + keepFnName: false, + keepClassName: false, } }); }); diff --git a/packages/babel-preset-babili/src/index.js b/packages/babel-preset-babili/src/index.js index ae26a457b..d9aef47d8 100644 --- a/packages/babel-preset-babili/src/index.js +++ b/packages/babel-preset-babili/src/index.js @@ -85,6 +85,11 @@ function preset(context, _opts = {}) { proxy("keepFnName", [ optionsMap.mangle, optionsMap.deadcode + ]), + + proxy("keepClassName", [ + optionsMap.mangle, + optionsMap.deadcode ]) ], "some"