2015-12-11

Babel 6: configuring ES6 standard library and helpers

This blog post is outdated. Please read Chap. “Babel: configuring standard library and helpers” in “Setting up ES6”.


This blog post explains how to configure how Babel 6 accesses its own helper functions and the ES6 standard library.

The following GitHub repo lets you play with what’s explained here: babel-config-demo

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

External dependencies of transpiled code

There are two external dependencies of the code produced by Babel that you’ll probably want to configure:

  • On one hand, your code will usually invoke functionality of the ES6 standard library. For example:

        let m = new Map();
        if (str.startsWith('/')) ···
    

    The default is to assume that this functionality is available via global variables.

  • On the other hand, Babel has helper functions that are called from the transpiled code. The default is to inline all invoked functions, which can result in redundancies, because it is done for each file, separately.

There are two ways in which you can get the standard library and non-inlined (and non-redundant) helpers: via global variables and via a module. How is explained next.

Standard library and helpers via global variables

The standard library via global variables: babel-polyfill

The package babel-polyfill installs several things into global variables:

The polyfills are provided by core-js.

Install babel-polyfill via npm as a runtime dependency if you find that any of the aforementioned functionality is missing in your transpiled code. In Node.js 5, you may be able to get by without using it, because that version comes with much of the ES6 standard library and native generators.

The helpers via a global variable: babel-plugin-external-helpers-2

This package transform the Babel output so that its helpers come from an object in a global variable and are not inserted into each file (possibly redundantly).

Alas, the helpers can only be accessed via a global variable. If you want to access them via a module, you need babel-plugin-transform-runtime. But that plugin also affects how you access the standard library, which may not be what you want.

As an example, consider the following ES6 code, before transpilation:

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

If you transpile it with the es2015 preset and without external-helpers-2, you get:

    "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);
            }
        }
        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;
    })();

Note the two helper functions _createClass and _classCallCheck.

If you additionally switch on the plugin external-helpers-2, you get this output:

    "use strict";
    
    var Point = (function () {
        function Point(x, y) {
            babelHelpers.classCallCheck(this, Point);
    
            this.x = x;
            this.y = y;
        }
    
        babelHelpers.createClass(Point, [{
            key: "toString",
            value: function toString() {
                return "(" + this.x + ", " + this.y + ")";
            }
        }]);
        return Point;
    })();
Creating a file with the Babel helpers

In order to create a file that sets up the global variable babelHelpers, you need to call the shell command babel-external-helpers (which is installed via the package babel-cli). This command supports three output formats:

  • babel-external-helpers -t global
    prints a Node.js module that puts the helpers into global.babelHelpers.

  • babel-external-helpers -t var
    prints browser code that puts the helpers into the global variable babelHelpers.

  • babel-external-helpers -t umd
    prints a Universal Module Definition (UMD) that works as CommonJS module, AMD module and via a global variable.

This invocation prints usage information:

    babel-external-helpers --help

Standard library and helpers via a module: babel-plugin-transform-runtime

If you install this plugin and switch it on, both helpers and uses of the ES6 standard library are redirected to imports from the package babel-runtime (which therefore becomes a runtime dependency).

Babel helpers and babel-plugin-transform-runtime

transform-runtime works well for the helpers. The previous ES6 example is transpiled to:

    "use strict";
    
    var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); // (A)
    
    var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
    
    var _createClass2 = require("babel-runtime/helpers/createClass"); // (B)
    
    var _createClass3 = _interopRequireDefault(_createClass2);
    
    function _interopRequireDefault(obj) {
        return obj && obj.__esModule ? obj : { default: obj };
    }
    
    var Point = (function () {
        function Point(x, y) {
            (0, _classCallCheck3.default)(this, Point);
    
            this.x = x;
            this.y = y;
        }
    
        (0, _createClass3.default)(Point, [{
            key: "toString",
            value: function toString() {
                return "(" + this.x + ", " + this.y + ")";
            }
        }]);
        return Point;
    })();

The helpers classCallCheck (line A) and createClass (line B) are now imported from babel-runtime.

The helper function _interopRequireDefault ensures that either plain CommonJS modules or transpiled ES6 modules can be used.

Runtime library and babel-plugin-transform-runtime

However, transform-runtime does not do as well for the ES6 runtime library.

Accessing functions

transform-runtime does properly detect function invocations: namespaced functions (such as Object.assign and Math.sign) and constructors (such as Map and Promise). Take, for example, the following ES6 code:

    let m = new Map();
    Math.sign(-1);

This code is transpiled to:

    "use strict";
    
    var _sign = require("babel-runtime/core-js/math/sign"); // (A)
    
    var _sign2 = _interopRequireDefault(_sign);
    
    var _map = require("babel-runtime/core-js/map"); // (B)
    
    var _map2 = _interopRequireDefault(_map);
    
    function _interopRequireDefault(obj) {
        return obj && obj.__esModule ? obj : { default: obj };
    }
    
    var m = new _map2.default();
    (0, _sign2.default)(-1);

Note the imports in line A and line B.

Accessing methods normally

However, transform-runtime does not detect method calls like those in the following ES6 code:

    console.log('a'.repeat(3));
    console.log(String.prototype.repeat.call('b', 3));

This is transpiled to:

    'use strict';
    
    console.log('a'.repeat(3));
    console.log(String.prototype.repeat.call('b', 3));

There are no imports – the input code in basically untouched. The first method call is dynamically dispatched, so it’s not surprising that transform-runtime doesn’t catch it. However, the second method call is direct and ignored, too.

Accessing methods via utility functions

Babel’s polyfilling is based on the library core-js, which lets you access its functionality without global data being changed:

    // Single library object
    var core = require('core-js/library');
    var mySet = new core.Set([1, 2, 3, 2, 1]);
    var myArr = core.Array.from(mySet);
    var myStr = core.String.repeat('*', 10);
    
    // Individual imports
    var Set       = require('core-js/library/fn/set');
    var from      = require('core-js/library/fn/array/from');
    var repeat = require('core-js/library/fn/string/repeat');

These utility functions are made available by transform-runtime. How can be looked up in the file definitions.js in its repository (roughly: the standard library of ES6 and later). This is an excerpt:

    module.exports = {
      builtins: {
        ···
        Set: "set",
        ···
      },
    
      methods: {
        Array: {
          ···
          from: "array/from",
          ···
        },
        ···
        String: {
          ···
          repeat: "string/repeat",
          ···
        },
        ···
      }
    };

That means that transform-runtime provides Set, Array.from() and String.repeat(). The last function is a version of String.prototype.repeat where this is “uncurried” (provided via the first parameter). This is how you use it:

    console.log(String.repeat('c', 3));

If you transpile, this code looks like this (note the import in line A):

    'use strict';
    
    var _repeat = require('babel-runtime/core-js/string/repeat'); // (A)
    
    var _repeat2 = _interopRequireDefault(_repeat);
    
    function _interopRequireDefault(obj) {
        return obj && obj.__esModule ? obj : { default: obj };
    }
    
    console.log((0, _repeat2.default)('c', 3));

Acknowledgement. Thanks to Denis Pushkarev (@zloirock) for his feedback on this blog post.

No comments: