5. Babel: configuring standard library and helpers
This chapter 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
5.1 Overview
The code produced by Babel usually has two kinds of external dependencies:
- Helper functions (e.g. for subclassing)
- Standard library functionality (e.g.
Mapor ES6 string methods)
There are two ways of fulfilling these dependencies: by installing functionality globally or via modules. In both cases the functionality is delivered as npm packages.
5.1.1 External dependencies via global variables
The following npm packages install their functionality globally and let you access it via global variables:
- (P)
babel-plugin-external-helpersand (I) generated file: global helpers - (I)
babel-polyfill: global standard library- ES5, ES6+, Regenerator runtime
- (I)
core-js: global standard library-
core-js/shim: ES5, ES6+ -
core-js/es6: ES6
-
Installation:
- (P) Plugin: install the npm package as a dev dependency and switch on the plugin in the Babel configuration data (see Chap. “Configuring Babel 6”).
- (I) Import: install the npm package as a runtime dependency and import it when the program starts.
5.1.2 External dependencies via module imports
The following npm packages enable dependencies via modules:
- (P)
babel-plugin-transform-runtime, (M)babel-runtime: helpers and standard library via imports (plugin generates imports).- Babel helpers (mandatory)
- Standard library (can be switched off)
- Regenerator API (can be switched off)
- (M)
core-js: standard library via modules.- Single entities:
import_repeatfrom'core-js/library/fn/string/repeat'; - Namespace object (ES5, ES6+):
import*ascorefrom'core-js/library';constmyStr=core.String.repeat('*',10); - Namespace object (ES6):
import*ascorefrom'core-js/library/es6';constmyStr=core.String.repeat('*',10);
- Single entities:
Installation:
- (P) Plugin: install the npm package as a dev dependency and switch on the plugin in the Babel configuration data (see Chap. “Configuring Babel 6”).
- (M) Module: install the npm package as a runtime dependency and import entities at runtime (as needed).
5.2 External dependencies of transpiled code
The code generated by Babel usually has two kinds of external dependencies that need to be fulfilled.
First, most code invokes functionality of the ES6 standard library. By default, you access this functionality via global variables:
let m = new Map();
if (str.startsWith('/')) ···
Second, Babel has helper functions (e.g. for subclassing) that are called from the transpiled code. By default, the code of helper functions is inlined, inserted into each file. For example (_classCallCheck is a helper):
//---------- Input: ES6 code
class Person {}
//---------- Output: ES5 code that uses helpers
"use strict";
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Person = function Person() {
_classCallCheck(this, Person);
};
Inlined helpers lead to code duplication whenever the same helper is used in several files.
There are two ways in which you can get the standard library and non-inlined helpers: via global variables and via module imports. How is explained in the next sections.
5.3 External dependencies via global variables
5.3.1 Helpers via a global variable: babel-plugin-external-helpers
There are two things you need to install:
- Plugin
babel-plugin-external-helpers:- Install as development dependency:
npm install --save-dev babel-plugin-external-helpers - Switch on in Babel configuration data:
"plugins":["external-helpers"]
- Install as development dependency:
- File that sets up global variable
babelHelpers:- Must be loaded or imported at runtime, as early as possible. E.g.:
import'babelHelpers'; - Generated via command line tool
babel-external-helpers(how is explained in the next section). - Install
babel-external-helpersas a development dependency:npm install --save-dev babel-cli
- Must be loaded or imported at runtime, as early as possible. E.g.:
The plugin ensures that all helpers are invoked via methods of the global object babelHelpers. In this section, I’ll explain how that works. In the next section, I’ll explain how to set up babelHelpers.
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, you get:
"use strict";
var _createClass = (function () {
···
})();
function _classCallCheck(instance, Constructor) {
···
}
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 switch on the plugin external-helpers, 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;
})();
5.3.1.1 Installing the global Babel helpers
How do you install the object with the helpers into the global variable babelHelpers? Via a file generated by the command line tool babel-external-helpers. The tool is part of the npm package babel-cli. The file is available in three formats:
-
babel-external-helpers -t global
prints a Node.js module that puts the helpers intoglobal.babelHelpers. -
babel-external-helpers -t var
prints a script file (browser code) thatvar-declaresbabelHelpersin global scope and assigns it an object with the helpers. -
babel-external-helpers -t umd
prints a Universal Module Definition (UMD) that works as CommonJS module, AMD module and as a script (via a global variable).
This invocation prints usage information:
babel-external-helpers --help
5.3.2 Standard library and more via global variables: babel-polyfill
The package babel-polyfill contains a module that 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, which you can see if you look at the two import statements making up this module:
import "core-js/shim";
import "babel-regenerator-runtime";
5.3.2.1 Installation
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 current Node.js versions, you may be able to get by without using it, because those versions come with much of the ES6 standard library and native generators.
The module is installed via:
npm install --save babel-polyfill
You must import it before you use the standard library:
import 'babel-polyfill';
The module will check global variables and only install missing functionality. The downside of the polyfill is that you always deliver and load all of the functionality, independently of how much you use.
5.3.3 Just the standard library via global variables: core-js
If you don’t need the Regenerator runtime, you can use the core-js polyfill directly.
You install it like this:
npm install --save
There are several ways in which you can install the polyfill functionality. Two common ones are:
-
import 'core-js/shim';
Polyfills ES5, ES6 and some post-ES6 functionality.
-
import 'core-js/es6';
Polyfills just ES6 functionality.
The import statement needs to happen before you access the runtime library. It installs the polyfills globally.
5.4 External dependencies via module imports
5.4.1 Helpers and standard library via imports: babel-plugin-transform-runtime
Two pieces are needed for this approach:
- A module that contains helpers and standard library. Install as a runtime dependency:
npm install --save babel-runtime - A plugin that changes Babel output so that helpers and standard library are imported from
babel-runtime.- Install as a development dependency:
npm install --save-dev babel-plugin-transform-runtime - Switch on in Babel configuration data:
"plugins":["transform-runtime"]
- Install as a development dependency:
The plugin redirects three kinds of operations to imports from babel-runtime:
- Babel helpers (mandatory)
- Standard library (can be switched off)
- Regenerator API (can be switched off): used by
babel-plugin-transform-regeneratorto transpile ES6 generators to ES5 code.
babel-runtime becomes a runtime dependency, but the import statements are created by the plugin – you do not need to import anything.
The plugin lets you switch off #2 and #3 as follows:
"plugins": [
["transform-runtime",
{ "polyfill": false, "regenerator": false }],
]
The next two sections explore how transform-runtime works for helpers and for the standard library.
5.4.2 transform-runtime and Babel helpers
transform-runtime works well for the helpers. Consider the following ES6 code:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
With the plugin, this 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. Note that each function sits in its own module, which ensures that only functions you actually use end up in bundled code.
The helper function _interopRequireDefault ensures that either plain CommonJS modules or transpiled ES6 modules can be used.
5.4.3 transform-runtime and the standard library
The plugin transform-runtime handles helpers automatically, but for much of the standard library, extra work is required. Let’s examine the support for the following ways of accessing the standard library:
- Accessing functions (supported)
- Accessing methods normally (not supported)
- Accessing methods via custom utility functions (supported)
5.4.3.1 Accessing functions (supported)
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.
5.4.3.2 Accessing methods normally (not supported)
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.
5.4.3.3 Accessing methods via custom utility functions (supported)
transform-runtime provides a work-around for method calls – utility functions attached to constructors. You can use them to replace method calls such as this one:
'c'.repeat(3);
With an invocation of a utility function:
String.repeat('c', 3);
Transpiled, the 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 };
}
(0, _repeat2.default)('c', 3);
5.4.3.4 Babel’s polyfilling is based on core-js
If you look back, all imports generated by transform-runtime use module IDs that start with babel-runtime/core-js. That’s because Babel’s polyfilling is based on the library core-js. You can look up how transform-runtime maps identifiers and chained property accesses to core-js modules in the repository file definitions.js. 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(), String.repeat() and more.
5.4.4 core-js: standard library via modules
Given that transform-runtime requires you to access properties of built-in constructors if you want to make a method call, using core-js directly is a useful alternative to that plugin.
Install it as a runtime dependency:
npm install --save core-js
One way of accessing standard library functionality non-globally is via a single library object:
import * as core from 'core-js/library';
const mySet = new core.Set([1, 2, 3, 2, 1]);
const myArr = core.Array.from(mySet);
const myStr = core.String.repeat('*', 10);
As for the content of the library object, you have a choice between:
-
core-js/library: polyfills ES5, ES6 and a few proposed features. -
core-js/library/es6: polyfills just ES6.
Another way of accessing standard library functionality non-globally is to import entities individually:
import _Set from 'core-js/library/fn/set';
import _from from 'core-js/library/fn/array/from';
import _repeat from 'core-js/library/fn/string/repeat';
const mySet = new _Set([1, 2, 3, 2, 1]);
const myArr = _from(mySet);
const myStr = _repeat('*', 10);
5.5 What should I use when?
For libraries, you must not touch global variables, which is why everything must come from imports:
- Babel helpers:
babel-plugin-transform-runtimeandbabel-runtimework well. - Regenerator runtime: The previous combo also provides the regenerator runtime via imports. If you don’t use or transpile generators then you can switch off this part of
babel-plugin-transform-runtime. - Standard library: I don’t like that
babel-plugin-transform-runtimeenables ES6 methods (e.g.Array.prototype.repeat()) via non-standard global methods (e.g.Array.repeat()). It’d switch this part of the plugin off and use core-js directly.
For apps, you can use a mixed approach:
- Use Babel helpers and – if necessary – the Regenerator runtime via module imports, as explained for libraries.
- Install the ES6 standard library globally. Modules have the advantage that a bundler will only deploy the functionality that is actually used by the app. But a global polyfill lets you write code that will eventually run natively without any changes. I find that more important.
Given that Regenerator is already taken care of, you don’t need
babel-polyfill; core-js is enough.
Non-redundant Babel helpers are always just an optimization, but especially for large code bases, it is worth it because of space savings.
Acknowledgements. Thanks to Denis Pushkarev (@zloirock) and Paul Klimashkin for their feedback on this content.