Skip to content

BabelJs - Babel Plugins

BabelJS is a powerful JavaScript compiler. Its primary role is to transform modern JavaScript code (ES2015+ and beyond) into a backward-compatible version that can run in older browsers or environments. The Babel compilation process generally involves three main stages:

  • Parsing: Babel takes your JavaScript code and converts it into an Abstract Syntax Tree (AST), which is a structured representation of the code.
  • Transforming: Babel traverses this AST and applies transformations based on the plugins and presets you’ve configured. This is where new syntax is converted to older syntax.
  • Printing (Code Generation): Babel takes the transformed AST and generates new JavaScript code from it.

Babel itself doesn’t change your code’s syntax by default. It relies on plugins to do the actual work. A plugin is a small piece of JavaScript code that tells Babel how to transform a specific new language feature. For convenience, Babel also offers presets, which are pre-defined collections of plugins. For instance, @babel/preset-env is a smart preset that automatically includes plugins for ECMAScript features that have reached Stage 4 (finished) in the TC39 process and are part of the ECMAScript standard, based on your target environments.

While @babel/preset-env handles most common transformations, sometimes you might need specific plugins for:

  • Experimental JavaScript features (proposals not yet standardized).
  • Specific optimizations or transformations not covered by general presets.
  • Non-standard JavaScript syntax extensions (like JSX used in React).

Let’s explore how some common JavaScript features are handled by Babel, often through plugins bundled in @babel/preset-env or available individually for more control:

  • Class Properties (Fields): e.g., @babel/plugin-proposal-class-properties (Note: Public and private class fields are now standard ES2022 and typically handled by @babel/preset-env).
  • Exponentiation Operator (**): e.g., @babel/plugin-transform-exponentiation-operator (ES2016 standard, handled by @babel/preset-env).
  • For…of Loops: e.g., @babel/plugin-transform-for-of (ES2015 standard, handled by @babel/preset-env).
  • Object Rest and Spread Properties (...): e.g., @babel/plugin-proposal-object-rest-spread (ES2018 standard, handled by @babel/preset-env).
  • Async/Await: e.g., @babel/plugin-transform-async-to-generator (ES2017 standard, handled by @babel/preset-env).

To illustrate, let’s set up a modern Babel project and see some plugins (implicitly via @babel/preset-env) in action.

First, initialize a new Node.js project:

npm init -y

Next, install the necessary Babel packages. We’ll use Babel’s core, command-line interface (CLI), and the essential @babel/preset-env:

npm install --save-dev @babel/core @babel/cli @babel/preset-env

Now, create a Babel configuration file. You can use babel.config.json (or .js, .cjs, .mjs) in your project root:

babel.config.json:

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

This tells Babel to use @babel/preset-env for transformations. By default, @babel/preset-env transpiles to ES5 compatible code unless configured otherwise with specific target environments.

Create a JavaScript file, for example, main.js, to write your modern JS code.

Example 1: Class Properties (Public Class Fields)

Section titled “Example 1: Class Properties (Public Class Fields)”

Public class fields are a modern JavaScript feature (standardized in ES2022) that allows you to define properties directly on a class, outside the constructor.

class Person {
// Public class field
name = "Alex Johnson";
age = 30;
// Arrow function as a class method (binds 'this' correctly)
greet = () => {
return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
}
}
const person = new Person();
console.log(person.greet());

If you try to run this code directly in an older browser that doesn’t support class fields, it would cause an error. Let’s transpile it using Babel.

npx babel main.js --out-file main_compiled.js

The @babel/preset-env (which includes @babel/plugin-proposal-class-properties or its modern equivalent for this feature) will transform the code.

Transpiled Code (main_compiled.js - simplified example)

Section titled “Transpiled Code (main_compiled.js - simplified example)”
"use strict";
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });
} else {
obj[key] = value;
}
return obj;
}
class Person {
constructor() {
_defineProperty(this, "name", "Alex Johnson");
_defineProperty(this, "age", 30);
_defineProperty(this, "greet", () => {
return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
});
}
}
const person = new Person();
console.log(person.greet());

Notice how the class fields name, age, and the arrow function greet are moved into the constructor and defined using _defineProperty (a helper function Babel might inject) or similar mechanisms. This makes the code compatible with older JavaScript environments that understand ES5 classes (which are themselves syntactic sugar over constructor functions and prototypes).

Running node main_compiled.js would output:

Hello, my name is Alex Johnson and I am 30 years old.

The exponentiation operator (**) is an ES2016 feature for calculating powers.

let squared = 3 ** 2; // 3 to the power of 2
let cubed = 2 ** 3; // 2 to the power of 3
console.log(`3 squared is ${squared}`);
console.log(`2 cubed is ${cubed}`);

@babel/preset-env will include the necessary transform (like @babel/plugin-transform-exponentiation-operator).

npx babel exponentiation.js --out-file exponentiation_compiled.js

Transpiled Code (exponentiation_compiled.js)

Section titled “Transpiled Code (exponentiation_compiled.js)”
"use strict";
let squared = Math.pow(3, 2); // 3 to the power of 2
let cubed = Math.pow(2, 3); // 2 to the power of 3
console.log(`3 squared is ${squared}`);
console.log(`2 cubed is ${cubed}`);

Babel converts ** to Math.pow(), which is universally supported.

Output:

3 squared is 9
2 cubed is 8

The for...of loop (ES2015) iterates over iterable objects (like Arrays, Strings, Maps, Sets).

const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
console.log(fruit);
}
npx babel forof_example.js --out-file forof_example_compiled.js

Transpiled Code (forof_example_compiled.js - simplified)

Section titled “Transpiled Code (forof_example_compiled.js - simplified)”
"use strict";
const fruits = ["apple", "banana", "cherry"];
for (let _i = 0, _fruits = fruits; _i < _fruits.length; _i++) {
const fruit = _fruits[_i];
console.log(fruit);
}
// Note: The actual transformation for `for...of` can be more complex,
// especially for generic iterables, potentially involving try/catch and iterators.
// For simple arrays, it might optimize to a standard for loop.

Output:

apple
banana
cherry

Common Learning Obstacle: for...of relies on the Symbol.iterator protocol. If you’re targeting very old environments that don’t support Symbol or iterators on built-in types, you’ll also need a polyfill (like core-js) for Symbol and the relevant iterator methods. @babel/preset-env can help manage this with its useBuiltIns option. See the chapter on Polyfills for more details.

Example 4: Object Rest and Spread Properties

Section titled “Example 4: Object Rest and Spread Properties”

Object rest/spread (ES2018) allows for easily collecting remaining properties into a new object (rest) or merging objects (spread).

const originalObject = { x: 1, y: 2, a: 3, b: 4 };
// Object Rest
const { x, y, ...rest } = originalObject;
console.log(x); // 1
console.log(y); // 2
console.log(rest); // { a: 3, b: 4 }
// Object Spread
const anotherObject = { c: 5, ...rest };
console.log(anotherObject); // { c: 5, a: 3, b: 4 }
npx babel object_rest_spread.js --out-file object_rest_spread_compiled.js

Transpiled Code (object_rest_spread_compiled.js - simplified)

Section titled “Transpiled Code (object_rest_spread_compiled.js - simplified)”
"use strict";
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });
} else {
obj[key] = value;
}
return obj;
}
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
const originalObject = { x: 1, y: 2, a: 3, b: 4 };
// Object Rest
const { x, y } = originalObject,
rest = _objectWithoutProperties(originalObject, ["x", "y"]);
console.log(x); // 1
console.log(y); // 2
console.log(rest); // { a: 3, b: 4 }
// Object Spread
const anotherObject = _objectSpread({ c: 5 }, rest);
console.log(anotherObject); // { c: 5, a: 3, b: 4 }

Babel uses helper functions (_objectWithoutProperties for rest, and _objectSpread or Object.assign for spread) to replicate the functionality.

Async/await (ES2017) provides syntactic sugar for working with Promises, making asynchronous code look more synchronous.

const timer = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function run() {
console.log("Starting...");
await timer(2000); // Pauses here for 2 seconds
console.log("Finished after 2 seconds!");
return "Done";
}
run().then(result => console.log(result));

Async functions are transformed into generator functions by Babel, often using a plugin like @babel/plugin-transform-async-to-generator (included in @babel/preset-env).

npx babel async_await.js --out-file async_await_compiled.js

Transpiled Code (async_await_compiled.js - simplified)

Section titled “Transpiled Code (async_await_compiled.js - simplified)”
"use strict";
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function () {
var self = this, args = arguments;
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
const timer = (ms) => new Promise(resolve => setTimeout(resolve, ms));
function run() {
return _run.apply(this, arguments);
}
function _run() {
_run = _asyncToGenerator(function* () {
console.log("Starting...");
yield timer(2000); // Pauses here for 2 seconds
console.log("Finished after 2 seconds!");
return "Done";
});
return _run.apply(this, arguments);
}
run().then(result => console.log(result));

Common Learning Obstacle: Async/await relies on Promises. If your target environment doesn’t support Promises natively (e.g., very old browsers), you’ll need a Promise polyfill. Additionally, the transformation to generator functions might require regenerator-runtime. @babel/preset-env with core-js can manage these polyfills. See the chapter on Polyfills.

Real-world Application: Plugins are crucial when working with frameworks like React (which uses JSX, a syntax extension requiring @babel/preset-react) or when adopting cutting-edge JavaScript features in projects that need to maintain broad browser compatibility.

Further Resources: For a comprehensive list of official Babel plugins, visit the Babel documentation website.