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:
ES5 polyfills (whatever is missing from the ES5 standard library):
Object.create()
,Array.prototype.forEach()
, etc.ES6 polyfills:
Map
,String.prototype.repeat()
, etc.A few polyfills of ECMAScript feature proposals:
Object.entries()
,Array.prototype.includes()
, etc.The runtime for Regenerator (which is used by Babel to transpile ES6 generators to ES5).
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 intoglobal.babelHelpers
.babel-external-helpers -t var
prints browser code that puts the helpers into the global variablebabelHelpers
.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.
Comments
Post a Comment