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.
1. let and const
Section titled “1. let and const”let allows block-scoped variable declarations, while const declares block-scoped variables whose values cannot be reassigned.
ES2015 Code
Section titled “ES2015 Code”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); // 1console.log('Constant b:', b); // 10Transpiled ES5 Code (Simplified)
Section titled “Transpiled ES5 Code (Simplified)”"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.
2. Arrow Functions
Section titled “2. Arrow Functions”Arrow functions provide a concise syntax for writing functions and lexically bind the this value (they don’t have their own this).
ES2015 Code
Section titled “ES2015 Code”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]Transpiled ES5 Code
Section titled “Transpiled ES5 Code”"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.
3. Classes
Section titled “3. Classes”ES2015 classes are syntactic sugar over JavaScript’s existing prototype-based inheritance.
ES2015 Code
Section titled “ES2015 Code”class Person { constructor(name) { this.name = name; } greet() { return `Hello, ${this.name}!`; }}const person1 = new Person("Alice");console.log(person1.greet()); // Hello, Alice!Transpiled ES5 Code (Simplified)
Section titled “Transpiled ES5 Code (Simplified)”"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.
4. Promises
Section titled “4. Promises”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.
ES2015 Code
Section titled “ES2015 Code”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.
5. Generators
Section titled “5. Generators”Generator functions (function*) can be paused and resumed. Transpiling generators requires a runtime helper, typically regenerator-runtime.
ES2015 Code
Section titled “ES2015 Code”function* idMaker() { let index = 0; while (true) { yield index++; }}const gen = idMaker();console.log(gen.next().value); // 0console.log(gen.next().value); // 1Transpiled 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.
6. Destructuring Assignment
Section titled “6. Destructuring Assignment”Destructuring allows unpacking values from arrays or properties from objects into distinct variables.
ES2015 Code
Section titled “ES2015 Code”// Array destructuringconst [first, second] = [1, 2, 3];console.log(first, second); // 1 2
// Object destructuringconst { name, age } = { name: "Bob", age: 42 };console.log(name, age); // Bob 42Transpiled ES5 Code
Section titled “Transpiled ES5 Code”"use strict";
// Array destructuringvar _ref = [1, 2, 3], first = _ref[0], second = _ref[1];console.log(first, second);
// Object destructuringvar _ref2 = { name: "Bob", age: 42}, name = _ref2.name, age = _ref2.age;console.log(name, age);7. Iterators and For…of Loops
Section titled “7. Iterators and For…of Loops”The for...of loop iterates over iterable objects. Iterators themselves use the Symbol.iterator protocol.
ES2015 Code
Section titled “ES2015 Code”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}.
8. Template Literals
Section titled “8. Template Literals”Template literals allow embedded expressions and multi-line strings.
ES2015 Code
Section titled “ES2015 Code”const personName = "Charlie";const greeting = `Hello, ${personName}!`;console.log(greeting); // Hello, Charlie!Transpiled ES5 Code
Section titled “Transpiled ES5 Code”"use strict";
var personName = "Charlie";var greeting = "Hello, " + personName + "!"; // or .concat()console.log(greeting);9. Enhanced Object Literals
Section titled “9. Enhanced Object Literals”Shorthand for property names and method definitions, and computed property names.
ES2015 Code
Section titled “ES2015 Code”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! valueTranspiled ES5 Code (Simplified)
Section titled “Transpiled ES5 Code (Simplified)”"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”Default Parameters (ES2015)
Section titled “Default Parameters (ES2015)”function multiply(a, b = 1) { return a * b;}console.log(multiply(5, 2)); // 10console.log(multiply(5)); // 5Transpiled:
"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));Rest Parameters (ES2015)
Section titled “Rest Parameters (ES2015)”function sum(...numbers) { return numbers.reduce((acc, current) => acc + current, 0);}console.log(sum(1, 2, 3)); // 6console.log(sum(1, 2, 3, 4, 5)); // 15Transpiled (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);11. Proxies
Section titled “11. Proxies”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.
ES2015 Code
Section titled “ES2015 Code”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); // helloconsole.log(p.nonExistent); // property does not existTranspiled 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.