Babel 6: configuring ES6 standard library and helpers

By Axel Rauschmayer

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

For more information on how to use Babel’s presets and plugins, consult the blog post “Configuring Babel 6”.

babel-polyfill

This package installs two things into global variables:

  • Whatever is missing from the ES6 runtime library (Map, new string methods, etc.), globally. Provided via core-js.
  • The runtime for Regenerator (which is used by Babel to transpile ES6 generators to ES5).

Install this package via npm as a runtime dependency if you find that anything from these two sources 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.

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

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 package babel-runtime (which therefore become a runtime dependency).

Babel helpers

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

    'use strict';
    
    var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
    
    var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
    
    var _createClass2 = require('babel-runtime/helpers/createClass');
    
    var _createClass3 = _interopRequireDefault(_createClass2);
    
    require('babel-core/external-helpers');
    
    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 helper function _interopRequireDefault ensures that either plain CommonJS modules can be used or transpiled ES6 modules.

ES6 runtime library

However, transform-runtime does not do as well for the ES6 runtime library. Consider, for example, the following ES6 code:

    let map = new Map();
    
    console.log('a'.repeat(3));
    console.log(String.prototype.repeat.call('b', 3));

This becomes:

    'use strict';
    
    var _map = require('babel-runtime/core-js/map');
    
    var _map2 = _interopRequireDefault(_map);
    
    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
    
    var map = new _map2.default();
    
    console.log('a'.repeat(3));
    console.log(String.prototype.repeat.call('b', 3));

That is, new Map() is changed successfully, but method invocations are not detected (doing so is difficult to impossible!).

Thus, manually invoking methods is a better approach:

    import * as core from 'core-js/library';
    console.log(core.String.repeat('c', 3));
    
    import repeat from 'core-js/fn/string/repeat.js';
    console.log(repeat('d', 3));

Source:: 2ality