developit / microbundle

📦 Zero-configuration bundler for tiny modules.
https://npm.im/microbundle
MIT License
8.05k stars 362 forks source link

Using for of loop adds ~300 bytes of polyfill code #1053

Closed everdimension closed 1 year ago

everdimension commented 1 year ago

Hi! I noticed in my project that if I use a for of loop instead of a regular for loop, microbundle adds approx. 300 bytes of polyfill code:

t=function(r,e){var t="undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(t)return(t=t.call(r)).next.bind(t);if(Array.isArray(r)||(t=function(r,e){if(r){if("string"==typeof r)return n(r,e);var t=Object.prototype.toString.call(r).slice(8,-1);return"Object"===t&&r.constructor&&(t=r.constructor.name),"Map"===t||"Set"===t?Array.from(r):"Arguments"===t||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)?n(r,e):void 0}}(r))){t&&(r=t);var o=0;return function(){return o>=r.length?{done:!0}:{done:!1,value:r[o++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}(arguments);

My compilation target is esnext (defined in tsconfig.json)

Is there a way to tell microbundle to omit this polyfill? I thought all browsers already support it: https://kangax.github.io/compat-table/es6/

rschristian commented 1 year ago

That's not for...of being polyfilled, but your non-iterable (in the target format, anyway) being polyfilled.

For example,

input


const arr = [1, 2, 3];

for (const el of arr) { console.log(el); }

> CJS output
```js
var arr = [1, 2, 3];
for (var _i = 0, _arr = arr; _i < _arr.length; _i++) {
  var el = _arr[_i];
  console.log(el);
}

input


const arr = 'foo';

for (const el of arr) { console.log(el); }


> CJS output
```js
function _unsupportedIterableToArray(o, minLen) {
  if (!o) return;
  if (typeof o === "string") return _arrayLikeToArray(o, minLen);
  var n = Object.prototype.toString.call(o).slice(8, -1);
  if (n === "Object" && o.constructor) n = o.constructor.name;
  if (n === "Map" || n === "Set") return Array.from(o);
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
  if (len == null || len > arr.length) len = arr.length;
  for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
  return arr2;
}
function _createForOfIteratorHelperLoose(o, allowArrayLike) {
  var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
  if (it) return (it = it.call(o)).next.bind(it);
  if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
    if (it) o = it;
    var i = 0;
    return function () {
      if (i >= o.length) return {
        done: true
      };
      return {
        done: false,
        value: o[i++]
      };
    };
  }
  throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}

var arr = 'foo';
for (var _iterator = _createForOfIteratorHelperLoose(arr), _step; !(_step = _iterator()).done;) {
  var el = _step.value;
  console.log(el);
}

This is clearer if you disable compression with the --no-compress flag, or --target node.

My compilation target is esnext (defined in tsconfig.json)

Your tsconfig.json only controls TS-specific compilation, and even then, it's mostly done through Microbundle's flags instead (like JSX pragma). cjs, umd, and esm are ES5 targetting, while modern is ES2017. What you set as your tsconfig.json compilation target doesn't affect anything.

I thought all browsers already support it

Just to add the earlier examples, this isn't so much for...of not being able to be transpiled in a byte-concious way, but you're iterating over something that needs a helper to be iterable in ES5. Options include changing data structures (if possible) or pointing users at the more modern output, and calling the verbose ES5 as simply legacy cost.

developit commented 1 year ago

I believe using --format modern avoids this downleveling. The esm+cjs+umd bundles are ES5.

rschristian commented 1 year ago

Yes, the modern output format is just as terse as the source.

rschristian commented 1 year ago

Going to close this out as everything seems to be working as intended from microbundle's side.

Feel free to respond if you have any further issues.

everdimension commented 1 year ago

Yes, this answers my question, thanks @rschristian @developit!

Small comment, the value I'm iteraring over is the arguments object, which seemingly doesn't require conversion if you fallback to a classic for-loop

But I suppose the complier can't make that decision