3 JavaScript features that bloat your ES5 bundle
Tags: javascript, typescript, webdev, optimization
Published: 2021-02-01 16:39:14

Every web developer likes cool ES6+ features: generators, iterables, async-await and others. What may be wrong using them?

# Bad old browsers
Sad, but people still use old browsers. And I'm not talking specifically of IE here — some people just turn off auto-update on their mobile phones and don't care anymore. Indeed it's sad 😥

# Should I care?
If you just develop some app — it depends. You know your users better; perhaps they are technically advanced and simply don't use old browsers. Or perhaps IE users fraction is small and non-paying, so you can disregard it completely.

But if you are authoring a JS lib — you definitely should. For the moment, libs are usually distributed transpiled to ES5 so they can work in any environment (however, it's assumed it's ok to require polyfills).

So, let's see what JS features turn your nice-looking ES6+ code into large and bloated ES5!

# 1. Generators
Perhaps the most famous ES5-hostile construct. It's so prominent that Airbnb [has a separate note](https://github.com/airbnb/javascript#generators--nope) on it.

#### input
```javascript
function* gen() {
    yield 1
    yield 2
}
```

#### TypeScript output
```javascript
var __generator = /* Somewhat long helper function */

function gen() {
    return __generator(this, function (_a) {
        switch (_a.label) {
            case 0: return [4 /*yield*/, 1];
            case 1:
                _a.sent();
                return [4 /*yield*/, 2];
            case 2:
                _a.sent();
                return [2 /*return*/];
        }
    });
}
```

Good news about TypeScript: [there is a way](https://dev.to/alekseiberezkin/optimizing-bundle-size-with-tslib-helper-functions-415o) to define helper functions like `__generator` once per bundle. However, the generator definition is always translated to a finite automata that doesn't look as nice as the source 😕

#### Babel output
```javascript
require("regenerator-runtime/runtime.js");

var _marked = /*#__PURE__*/regeneratorRuntime.mark(gen);

function gen() {
  return regeneratorRuntime.wrap(function gen$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return 1;

        case 2:
          _context.next = 4;
          return 2;

        case 4:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}
```

Babel goes even further — it moves all generators runtime to a different module. Which is, unfortunately, quite large 🐘

### What to do?
Use iterables. But be cautious — there's a way to bloat your code with them as well 😉

# 2. async-await
What? Isn't it just a syntax sugar over Promises? Let's see!

#### input
```javascript
export async function fetchExample() {
    const r = await fetch('https://example.com')
    return await r.text();
}
```

#### TypeScript output
```javascript
var __awaiter = /* Some convoluted JS code */

var __generator = /* We saw it already! */

function fetchExample() {
    return __awaiter(this, void 0, void 0, function () {
        var r;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, fetch('https://example.com')];
                case 1:
                    r = _a.sent();
                    return [4 /*yield*/, r.text()];
                case 2: return [2 /*return*/, _a.sent()];
            }
        });
    });
}
```

It's even worse than generators! `async-await` is in fact a generator which additionally suspends on Promises.

#### Babel output

```javascript
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
require("regenerator-runtime/runtime.js");

function asyncGeneratorStep/* Like __awaiter */
function _asyncToGenerator/* Yet another converter */

function fetchExample() {
  return _fetchExample.apply(this, arguments);
}

function _fetchExample() {
  _fetchExample = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    var r;
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return fetch('https://example.com');

          case 2:
            r = _context.sent;
            _context.next = 5;
            return r.text();

          case 5:
            return _context.abrupt("return", _context.sent);

          case 6:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));
  return _fetchExample.apply(this, arguments);
}
```

Babel thinks of `async-await` just like TypeScript does: they are generators with some additional stuff, so it produces not only it imports, but some helper functions as well.

### What to do?
Use simple Promises chains. While they may look too “traditional”, they transpile well to anything.

# 3. Iterables iteration
Multiple JS constructs cause iterators iteration: `for-of` loop, iterables spread, and iterables destructuring.

However, there are some good news about this feature:

* **TypeScript:** without [`downlevelIteration`](https://www.typescriptlang.org/tsconfig#downlevelIteration) the compiler 1) allows only arrays iteration, and 2) transpiles iteration to simple indexed access
* **Babel:** if the compiler infers array it uses simple indexed access

However, if these news do not apply to your code, it's getting bloated 😐

#### input
```javascript
const iterable = (() => [1, 2])()
for (const i of iterable) {
    console.log(i)
}
```

#### TypeScript output
```javascript
var __values = /* ... */
var e_1, _a;
var iterable = (function () { return [1, 2]; })();
try {
    for (var iterable_1 = __values(iterable), iterable_1_1 = iterable_1.next(); !iterable_1_1.done; iterable_1_1 = iterable_1.next()) {
        var i = iterable_1_1.value;
        console.log(i);
    }
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
    try {
        if (iterable_1_1 && !iterable_1_1.done && (_a = iterable_1.return)) _a.call(iterable_1);
    }
    finally { if (e_1) throw e_1.error; }
}
```
There's a [special handling](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/return) for the case if `iterable` is a generator. It's not needed for our example but the compiler can't be sure.


#### Babel output
```javascript
function _createForOfIteratorHelper/* ... */
function _unsupportedIterableToArray/* ... */
function _arrayLikeToArray/* ... */

var iterable = function () {
  return [1, 2];
}();

var _iterator = _createForOfIteratorHelper(iterable),
    _step;

try {
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
    var i = _step.value;
    console.log(i);
  }
} catch (err) {
  _iterator.e(err);
} finally {
  _iterator.f();
}
```
Just like TS, Babel handles exception case which is, in fact, not needed in this example.

### What to do
* Don't iterate anything but arrays
* Otherwise, write a simple function:
```javascript
function forEach(iterable, effect) {
    const itr = iterable[Symbol.iterator]()
    for ( ; ; ) {
        const n = itr.next()
        if (n.done) {
            return n.value
        }
        effect(n.value)
    }
}
```

# Are there other bloaters?
Honestly, any ES6+ feature produces some additional code; however, as far as I know, the produced code is not as large as in examples above.

### What to do?
Just read whatever your compiler produces and think if you can do something about it 🙂

When I looked into a `dist` of [my project](https://github.com/aleksei-berezkin/fluent-streams) for the first time I got shocked: almost every file had `__read` or `__whatever`, and all neat `for-of`s were turned into large and ugly structures. However, having applied techniques I described here and [there](https://dev.to/alekseiberezkin/optimizing-bundle-size-with-tslib-helper-functions-415o), I reduced the bundle size for about 15%. So can you! 😉

______

*Thanks for reading this. Can you name some other bundle bloaters?*