Babel 6: loose mode

[2015-12-12] dev, javascript, jstools, babel, esnext
(Ad, please don’t block)

Babel’s loose mode transpiles ES6 code to ES5 code that is less faithful to ES6 semantics. This blog post explains how that works and what the pros and cons are (spoiler: normally not recommended).

Starting point for this series of posts on Babel 6:Configuring Babel 6” [explains the basics: configuration files, presets, plugins, etc.]

Two modes  

Many plugins in Babel have two modes:

  • A normal mode follows the semantics of ECMAScript 6 as closely as possible.
  • A loose mode produces simpler ES5 code.

Normally, it is recommended to not use loose mode. The pros and cons are:

  • Pros: The generated code is potentially faster and more compatible with older engines. It also tends to be cleaner, more “ES5-style”.
  • Con: You risk getting problems later on, when you switch from transpiled ES6 to native ES6. That is rarely a risk worth taking.

Switching on loose mode  

The preset es2015-loose is the loose version of the standard ES6 preset, es2015. The preset’s code provides a good overview of what plugins have a loose mode and how to switch it on. This is an excerpt:

module.exports = {
  plugins: [
    ···
    [require("babel-plugin-transform-es2015-classes"), {loose: true}],
    require("babel-plugin-transform-es2015-object-super"),
    ···
  ]
};

This is a CommonJS module where you can use all of ECMAScript 5. If you configure Babel via .babelrc or package.json (details), you need to use JSON. You can either include the whole preset:

···
"presets": [
  ···
  "es2015-loose",
  ···
],
···

Or you can include plugins individually:

···
"plugins": [
  ···
  ["transform-es2015-classes", {loose: true}],
  "transform-es2015-object-super",
  ···
],
···

Example: the output of normal mode and loose mode  

Let’s see how the modes affect the transpilation of the following code.

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    toString() {
        return `(${this.x}, ${this.y})`;
    }
}

Normal mode  

In normal mode, the prototype methods of the class are added via Object.defineProperty (line A), to ensure that they are non-enumerable, as required by the ES6 spec.

"use strict";

var _createClass = (function () {
    function defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configurable = true;
            if ("value" in descriptor) descriptor.writable = true;
            Object.defineProperty(target, descriptor.key, descriptor); // (A)
        }
    }
    return function (Constructor, protoProps, staticProps) {
        if (protoProps) defineProperties(Constructor.prototype, protoProps);
        if (staticProps) defineProperties(Constructor, staticProps);
        return Constructor;
    };
})();

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function");
    }
}

var Point = (function () {
    function Point(x, y) {
        _classCallCheck(this, Point);

        this.x = x;
        this.y = y;
    }

    _createClass(Point, [{
        key: "toString",
        value: function toString() {
            return "(" + this.x + ", " + this.y + ")";
        }
    }]);

    return Point;
})();

Loose mode  

In loose mode, normal assignment is used to add methods (line A). This style is more like you’d hand-write code in ES5.

"use strict";

function _classCallCheck(instance, Constructor) { ··· }

var Point = (function () {
    function Point(x, y) {
        _classCallCheck(this, Point);

        this.x = x;
        this.y = y;
    }

    Point.prototype.toString = function toString() { // (A)
        return "(" + this.x + ", " + this.y + ")";
    };

    return Point;
})();