2 cases where Babel fixes your code but it shouldn't

How turning off transpilations messes up the codebase

An Aha! moment, delivered to your inbox every week. Check out the JS Tips & Tricks Newsletter!

Switching to babel-preset-env

Did you know that Babel inadvertently fixes some errors? Me neither. In one of my projects, I switched from babel-preset-latest to babel-preset-env without expecting any problems.

But this is what happened. Since I don’t care about legacy browsers, it immediately switched off all plugins and introduced two bugs in the codebase.

After some investigation, it turned out that some transpilations “fix” some edge cases. And in turn, switching them off surfaced the errors.

const initialization

When you use block-scoped values, like a const, Babel rewrites them to traditional vars. It takes care of block-scoping, so most of the times they behave as they should.

But it does not handle a specific scenario. For this input code (Try it):

const c = (() => {
	return c;
})();

Babel outputs this:

"use strict";

var c = function () {
	return c;
}();

It’s almost identical; only the const has been rewritten to var. But if you run it, the babelified code sets c to undefined, while the original one throws an Error.

The difference between const and var – apart from the scoping – is how they are hoisted. The var declaration is moved to the top of the scope and set undefined. Therefore, c is accessible inside the function call.

On the other hand, const is moved to the top but left uninitialized. Inside the function call, c is still uninitialized, it is in the so-called temporal dead zone, so accessing it throws an Error.

Convert Array-like to iterable

The other edge case is how iterables are handled.

Consider the following code (Try it):

const o = {
	length: 0
};

console.log([...o]);

o is a so-called Array-like structure. It has a length property, and the numerical keys return the appropriate elements. In this case, it is an empty array (length: 0).

Running this code results in an error:

Uncaught TypeError: o is not iterable

The array destructuring operator works only on iterables. Array-likes are supported by some built-in functions, but they are not enough in this case.

Since iterables are required to have a [Symbol.iterator] function and o clearly does not have it, the error is expected.

Babel produces the following code for the above input:

"use strict";

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

var o = {
	length: 0
};

console.log([].concat(_toConsumableArray(o)));

The key part is the _toConsumableArray function. It makes a full-fledged Array out of its parameter. Since Array.isArray(o) is false, it returns Array.from(o). That function accepts an Array-like and returns an Array. Since then, it behaves exactly like []. Therefore, the code returns an empty Array, instead of throwing an Error.

Conclusion

There might be other edge cases that get “fixed” by transpilation, and cases like these might surface errors in an update.

This might cause problems bumping even minor versions. For example, babel-preset-env supports time-dependent queries, like last x versions. With a new browser version, a plugin might get disabled, surfacing an error.

Fortunately, these errors are rare, and they are both known issues. But don’t expect them to just go away. They are hard to implement right, and therefore, it’s more likely you’ll update sooner than the fix is released.

By knowing errors like these exist, give attention to plugins that get enabled/disabled during an update.

31 October 2017

Interesting article?

Get hand-crafted emails on new content!