babel / minify

:scissors: An ES6+ aware minifier based on the Babel toolchain (beta)
https://babeljs.io/repl
MIT License
4.39k stars 225 forks source link

babel-plugin-minify-guarded-expressions removes required parts of core-js #975

Open ehoogeveen-medweb opened 4 years ago

ehoogeveen-medweb commented 4 years ago

I'm building and minifying core-js for a specific set of browser targets using core-js-builder and babel-preset-minify. I noticed that Number.prototype.toFixed() was producing errors in IE11 and narrowed it down to the plugin babel-plugin-minify-guarded-expressions.

Diffing the (prettified) output, I noticed that it's removing a polyfill for String.prototype.repeat() and a guard for String.prototype.matchAll(), neither of which are supported by IE11. It also removes a guard for String.prototype.toFixed() itself, but that should be okay as basic support for this is present in IE11 (even if the implementation isn't compliant).

To Reproduce

I'm producing core-js using a self-contained node.js project. To reproduce, use the files below then simply run

  npm install
  npm run build
  npm run minify

and it should produce a file called core-js.min.js which is missing the required polyfill for String.prototype.repeat() (sorry if this is an unusual set of instructions for a bug report).

My package.json:

{
  "scripts": {
    "build": "node build.js",
    "minify": "babel core-js.js --verbose -s -o core-js.min.js",
    "all": "npm run build && npm run minify"
  },
  "browserslist": [
    "ie 11",
    "last 2 chrome versions",
    "last 2 firefox versions",
    "firefox esr",
    "last 2 edge versions",
    "last 2 safari versions"
  ],
  "devDependencies": {
    "@babel/cli": "^7.8.3",
    "@babel/core": "^7.8.3",
    "babel-preset-minify": "^0.5.1",
    "core-js-builder": "^3.6.4"
  }
}

My babel.config.js (I disabled every plugin except babel-plugin-minify-guarded-expressions):

module.exports = {
  presets: [
    ["minify", {
      booleans: false,
      builtIns: false,
      consecutiveAdds: false,
      deadcode: false,
      evaluate: false,
      flipComparisons: false,
      guards: true,
      infinity: false,
      mangle: false,
      memberExpressions: false,
      mergeVars: false,
      numericLiterals: false,
      propertyLiterals: false,
      regexpConstructors: false,
      removeUndefined: false,
      replace: false,
      simplify: false,
      simplifyComparisons: false,
      typeConstructors: false,
      undefinedToVoid: false,
    }],
  ],
  sourceMaps: true, // Generate source maps.
  sourceType: "script", // Treat the code as non-strict unless specified.
  comments: false, // Remove comments.
};

My build.js file (not sure if this is the correct way to build core-js but it works):

const fs = require('fs');
const json = JSON.parse(fs.readFileSync('./package.json'));

require('core-js-builder')({
  targets: json.browserslist,
  filename: './core-js.js',
}).then(code => {
  console.log("Finished building core-js!");
}).catch(error => {
  console.error("Building core-js failed!");
  console.error(error);
});

The diff between a run with guards: false (which works) and guards: true (which causes the error):

@@ -3959,7 +3959,7 @@
             }
             return n
         };
-        var FORCED = nativeToFixed && (0.00008.toFixed(3) !== "0.000" || 0.9.toFixed(0) !== "1" || 1.255.toFixed(2) !== "1.25" || 1000000000000000100.toFixed(0) !== "1000000000000000128") || !fails(function() {
+        var FORCED = 0.00008.toFixed(3) !== "0.000" || 0.9.toFixed(0) !== "1" || 1.255.toFixed(2) !== "1.25" || 1000000000000000100.toFixed(0) !== "1000000000000000128" || !fails(function() {
             nativeToFixed.call({})
         });
         $({
@@ -4059,16 +4059,7 @@
         "use strict";
         var toInteger = __webpack_require__(40);
         var requireObjectCoercible = __webpack_require__(12);
-        module.exports = "".repeat || function repeat(count) {
-            var str = String(requireObjectCoercible(this));
-            var result = "";
-            var n = toInteger(count);
-            if (n < 0 || n == Infinity) throw RangeError("Wrong number of repetitions");
-            for (; n > 0;
-                (n >>>= 1) && (str += str))
-                if (n & 1) result += str;
-            return result
-        }
+        module.exports = "".repeat
     }, function(module, exports, __webpack_require__) {
         var $ = __webpack_require__(2);
         var assign = __webpack_require__(194);
@@ -6119,7 +6110,7 @@
         var RegExpPrototype = RegExp.prototype;
         var regExpBuiltinExec = RegExpPrototype.exec;
         var nativeMatchAll = "".matchAll;
-        var WORKS_WITH_NON_GLOBAL_REGEX = !!nativeMatchAll && !fails(function() {
+        var WORKS_WITH_NON_GLOBAL_REGEX = !fails(function() {
             "a".matchAll(/./)
         });
         var regExpExec = function(R, S) {

Configuration

How are you using babel-minify?

babel-preset-minify in babel.config.js

babel-minify version: 0.5.1

babel version: 7.8.3

core-js-builder version: 3.6.4

babel-minify config:

{
  booleans: false,
  builtIns: false,
  consecutiveAdds: false,
  deadcode: false,
  evaluate: false,
  flipComparisons: false,
  guards: true,
  infinity: false,
  mangle: false,
  memberExpressions: false,
  mergeVars: false,
  numericLiterals: false,
  propertyLiterals: false,
  regexpConstructors: false,
  removeUndefined: false,
  replace: false,
  simplify: false,
  simplifyComparisons: false,
  typeConstructors: false,
  undefinedToVoid: false
}
ehoogeveen commented 4 years ago

FWIW, minify-dead-code-elimination appears to have the same problem, removing the null check for native String.prototype.startsWith(), so I've now added deadcode: false too.

ehoogeveen-medweb commented 4 years ago

The plugin minify-type-constructors also causes an issue, as it transforms String(Symbol()) (which evaluates to "Symbol()") to '' + Symbol() (which is a type error). core-js uses this as part of its test for native Symbol support.