Skip to content

Transpile ES6 features to ES5

BabelJS - Transpiling ES2015 (ES6) Features to ES5

Section titled “BabelJS - Transpiling ES2015 (ES6) Features to ES5”

ECMAScript 2015 (ES6) was a major update to JavaScript, introducing a wealth of new syntax and features that significantly improve developer productivity and code organization. BabelJS, primarily through @babel/preset-env, plays a crucial role in allowing developers to use these features while maintaining compatibility with older environments that only support ES5.

This chapter will cover some key ES2015 features and how Babel transpiles them. Assume you have a basic Babel setup with @babel/preset-env configured in babel.config.json:

{
"presets": ["@babel/preset-env"]
}

And you use a command like npx babel input.js --out-file output.js for transpilation.

let allows block-scoped variable declarations, while const declares block-scoped variables whose values cannot be reassigned.

let a = 1;
const b = 10;
if (true) {
let a = 2; // Different 'a' (block-scoped)
// b = 20; // This would cause an error: Assignment to constant variable.
console.log('Inside if, a:', a); // 2
}
console.log('Outside if, a:', a); // 1
console.log('Constant b:', b); // 10
"use strict";
var a = 1;
var b = 10;
if (true) {
var _a = 2; // Babel renames to avoid conflict, simulating block scope
// b = 20;
console.log('Inside if, a:', _a);
}
console.log('Outside if, a:', a);
console.log('Constant b:', b);

Babel converts let and const to var. For let within blocks, it often renames variables to mimic block scoping. The reassignment protection of const is a compile-time check by Babel in strict mode or by linters; in ES5, it’s just a var.

Arrow functions provide a concise syntax for writing functions and lexically bind the this value (they don’t have their own this).

const add = (x, y) => x + y;
console.log(add(5, 3)); // 8
const numbers = [1, 2, 3];
const squares = numbers.map(n => n * n);
console.log(squares); // [1, 4, 9]
"use strict";
var add = function add(x, y) {
return x + y;
};
console.log(add(5, 3));
var numbers = [1, 2, 3];
var squares = numbers.map(function (n) {
return n * n;
});
console.log(squares);

Babel converts arrow functions to traditional function expressions. The lexical this behavior is preserved by Babel ensuring the correct this is captured if used within the arrow function.

ES2015 classes are syntactic sugar over JavaScript’s existing prototype-based inheritance.

class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
}
const person1 = new Person("Alice");
console.log(person1.greet()); // Hello, Alice!
"use strict";
function _classCallCheck(instance, Constructor) { /* ... */ }
function _defineProperties(target, props) { /* ... */ }
function _createClass(Constructor, protoProps, staticProps) { /* ... */ }
var Person = /*#__PURE__*/function () {
function Person(name) {
_classCallCheck(this, Person);
this.name = name;
}
_createClass(Person, [{
key: "greet",
value: function greet() {
return "Hello, ".concat(this.name, "!");
}
}]);
return Person;
}();
var person1 = new Person("Alice");
console.log(person1.greet());

Babel transforms class syntax into constructor functions and methods attached to the prototype, along with helper functions to ensure correct behavior.

Promises provide a way to handle asynchronous operations. Babel itself doesn’t transpile the Promise object’s existence or its methods; it only transpiles syntax that might use Promises (like async/await). To use Promises in environments that don’t support them, you need a polyfill.

const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Promise resolved!");
}, 100);
});
myPromise.then(message => console.log(message));

Transpiled ES5 Code (Syntax remains similar)

Section titled “Transpiled ES5 Code (Syntax remains similar)”
"use strict";
var myPromise = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("Promise resolved!");
}, 100);
});
myPromise.then(function (message) {
return console.log(message);
});

To make this work in an old browser: Configure @babel/preset-env with "useBuiltIns": "usage" and "corejs": 3, and install core-js. Babel will then automatically include the Promise polyfill if needed for your target environments. See the Polyfills chapter.

Generator functions (function*) can be paused and resumed. Transpiling generators requires a runtime helper, typically regenerator-runtime.

function* idMaker() {
let index = 0;
while (true) {
yield index++;
}
}
const gen = idMaker();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1

Transpiled ES5 Code (Simplified, with regeneratorRuntime)

Section titled “Transpiled ES5 Code (Simplified, with regeneratorRuntime)”
"use strict";
var _marked = /*#__PURE__*/regeneratorRuntime.mark(idMaker);
function idMaker() {
var index;
return regeneratorRuntime.wrap(function idMaker$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
index = 0;
case 1:
if (!true) {
_context.next = 7;
break;
}
_context.next = 4;
return index++;
case 4:
_context.next = 1;
break;
case 7:
case "end":
return _context.stop();
}
}
}, _marked);
}
var gen = idMaker();
console.log(gen.next().value);
console.log(gen.next().value);

Babel transforms generator functions into state machines. For this to work, @babel/preset-env (when configured appropriately with core-js and useBuiltIns, or if you explicitly import regenerator-runtime/runtime) will ensure regeneratorRuntime is available.

Destructuring allows unpacking values from arrays or properties from objects into distinct variables.

// Array destructuring
const [first, second] = [1, 2, 3];
console.log(first, second); // 1 2
// Object destructuring
const { name, age } = { name: "Bob", age: 42 };
console.log(name, age); // Bob 42
"use strict";
// Array destructuring
var _ref = [1, 2, 3],
first = _ref[0],
second = _ref[1];
console.log(first, second);
// Object destructuring
var _ref2 = {
name: "Bob",
age: 42
},
name = _ref2.name,
age = _ref2.age;
console.log(name, age);

The for...of loop iterates over iterable objects. Iterators themselves use the Symbol.iterator protocol.

const arr = ['a', 'b', 'c'];
for (const val of arr) {
console.log(val);
}
// Output: a, b, c (each on a new line)

Transpiled ES5 Code (Simplified for array)

Section titled “Transpiled ES5 Code (Simplified for array)”
"use strict";
var arr = ['a', 'b', 'c'];
for (var _i = 0; _i < arr.length; _i++) {
var val = arr[_i];
console.log(val);
}
// Note: For generic iterables, the transform is more complex and involves iterator protocol checks.

Using Symbol.iterator directly or for...of on non-array iterables may require polyfills for Symbol and the specific iterator methods if targeting very old environments. core-js (via @babel/preset-env) handles this.

Testing Symbol.iterator (needs polyfill for old browsers)

Section titled “Testing Symbol.iterator (needs polyfill for old browsers)”

Original Code:

let numbers = [4, 7, 3, 10];
let iterator = numbers[Symbol.iterator]();
console.log(iterator.next()); // {value: 4, done: false}

Transpiled code will keep Symbol.iterator. You’d need to ensure Symbol is polyfilled by core-js if your target environment doesn’t support it. HTML example to test polyfills:

<!-- Assuming bundle.js is created by a bundler after Babel and core-js processing -->
<html>
<head>
<title>Iterator Test</title>
</head>
<body>
<h1>Iterators</h1>
<script src="bundle.js"></script>
</body>
</html>

The console in the browser (even older ones, with polyfills) would show {value: 4, done: false}.

Template literals allow embedded expressions and multi-line strings.

const personName = "Charlie";
const greeting = `Hello, ${personName}!`;
console.log(greeting); // Hello, Charlie!
"use strict";
var personName = "Charlie";
var greeting = "Hello, " + personName + "!"; // or .concat()
console.log(greeting);

Shorthand for property names and method definitions, and computed property names.

const x = 1, y = 2;
const propName = "dynamicProp";
const obj = {
x, // Shorthand property
y,
greet() { // Shorthand method
return "Hi!";
},
[propName]: "value" // Computed property name
};
console.log(obj.x, obj.greet(), obj.dynamicProp); // 1 Hi! value
"use strict";
function _defineProperty(obj, key, value) { /* ... */ return obj; }
var x = 1,
y = 2;
var propName = "dynamicProp";
var obj = _defineProperty({
x: x,
y: y,
greet: function greet() {
return "Hi!";
}
}, propName, "value");
console.log(obj.x, obj.greet(), obj.dynamicProp);

10. Default, Rest & Spread Parameters/Properties

Section titled “10. Default, Rest & Spread Parameters/Properties”
function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(5, 2)); // 10
console.log(multiply(5)); // 5

Transpiled:

"use strict";
function multiply(a) {
var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
return a * b;
}
console.log(multiply(5, 2));
console.log(multiply(5));
function sum(...numbers) {
return numbers.reduce((acc, current) => acc + current, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15

Transpiled (Simplified):

"use strict";
function sum() {
for (var _len = arguments.length, numbers = new Array(_len), _key = 0; _key < _len; _key++) {
numbers[_key] = arguments[_key];
}
return numbers.reduce(function (acc, current) {
return acc + current;
}, 0);
}
console.log(sum(1, 2, 3));
console.log(sum(1, 2, 3, 4, 5));

Spread Syntax (ES2015 for arrays, ES2018 for objects)

Section titled “Spread Syntax (ES2015 for arrays, ES2018 for objects)”

For arrays:

const arr1 = [1, 2];
const arr2 = [...arr1, 3, 4];
console.log(arr2); // [1, 2, 3, 4]

Transpiled:

"use strict";
function _toConsumableArray(arr) { /* ... helper ... */ }
var arr1 = [1, 2];
var arr2 = [].concat(_toConsumableArray(arr1), [3, 4]);
console.log(arr2);

Proxies (ES2015) allow creating an object that can be used in place of another object but which may redefine fundamental Object operations like getting, setting, and defining properties. Transpiling full Proxy behavior is complex and has limitations.

const target = { message1: "hello", message2: "everyone" };
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : "property does not exist";
}
};
const p = new Proxy(target, handler);
console.log(p.message1); // hello
console.log(p.nonExistent); // property does not exist

Transpiled ES5 Code (Syntax remains, requires Polyfill)

Section titled “Transpiled ES5 Code (Syntax remains, requires Polyfill)”
"use strict";
var target = { message1: "hello", message2: "everyone" };
var handler = {
get: function get(obj, prop) {
return prop in obj ? obj[prop] : "property does not exist";
}
};
var p = new Proxy(target, handler);
console.log(p.message1);
console.log(p.nonExistent);

Babel doesn’t (and generally can’t) transpile the Proxy object itself into fully equivalent ES5 code due to the reflective nature of Proxies. To use Proxies in environments that don’t support them, a polyfill (like one from core-js or a standalone Proxy polyfill) is necessary. However, Proxy polyfills often have limitations and cannot perfectly replicate all Proxy behaviors, especially for certain traps. It’s often advised to use Proxies only when targeting environments that support them natively or with a clear understanding of polyfill limitations.

This overview shows how Babel handles many common ES2015 features, transforming syntax to be ES5-compatible. For features that involve new built-in objects or methods (like Promises, Symbols, new Array methods, or Proxies), polyfills are essential for runtime support in older environments, typically managed by configuring @babel/preset-env with core-js.