What is babeljs doing
For those wondering what is awesome Babel transpiler doing I propose to show it with an example (online transpiler can be found here for more testing).
Why Babel
Browsers or javascript engine as NodeJS not all “capable” of managing the new ES6 javascript version.
Thus Babel is in charge of compiling each new ES6 features and make it compliant with older version of JavaScript (ES5…).
Babel parses your JS source code to transform it into a legacy javascript source code :)
Class example
In this article I describe Babel’s work with new Class sugar syntax from ES6.
I wish it will help to figure out the kind of routines that Babel can produce.
Old school
Before diving into ES6 jungle we were doing.
// Constructor
function Person(name) {
this.type = 'Person';
this.name = name;
}
// Instance method
Person.prototype.hello = function() {
console.log('hello ' + this.name);
}
// Static method attached to Constructor and not instances
Person.fn = function() {
console.log('static');
};
usage
var julien = new Person('julien');
var darul = new Person('darul');
julien.hello(); // 'hello julien'
darul.hello(); // 'hello darul'
Person.fn(); // 'static'
// and this one on error
julien.fn(); //Uncaught TypeError: julien.fn is not a function
JSFiddle to play here
New school
You may have heard about new ES6 Class syntax applied to our previous example:
class Person {
type = 'person';
constructor(name) {
this.name = name;
}
hello() {
console.log('hello ' + this.name);
}
static fn = () => {
console.log('static');
};
}
It is a matter of style but according to me it is more efficient, less verbose… :)
Babel transformation
Then it could be interesting to see how Babel does transformation from ES6 to ES5.
But let’s do it step by step.
Step 1 : definition
class Person {}
is transpiled by Babel into
function _classCallCheck(instance, Constructor) {
// check if we create a new Object
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Person = function Person() {
_classCallCheck(this, Person);
};
Ok good, but why like that ? why a call to _classCallCheck method… I would say (specification) enhancement, safety.
Class keyword is really a “syntactical sugar over JavaScript’s existing prototype-based inheritance” and Class keyword will be translated as a function (constructor) allowing us to create new object instances.
Then, above code prevents you from calling generated Person function directly instead of creating a new object instance.
// fine
const p = new Person();
// Uncaught TypeError: Cannot call a class as a function
Person();
Step 2 : constructor
We could add a constructor with one parameter (and class property…)
class Person() {
type = 'person';
constructor(name) {
this.name = name;
}
}
Nothing very weird, our new Constructor function handles it pretty well.
var Person = function Person(name) {
_classCallCheck(this, Person);
this.type = 'person';
this.name = name;
};
As you can see Babel deals great with Class keyword :) easy.
Step 3 : prototype method
Add a prototype method named hello() and it gonna start to be more tricky.
Full
class Person {
constructor(name) {
this.name = name;
}
hello() {
console.log('hello ' + this.name);
}
}
Will be transpiled to
// SAME AS BEFORE HERE CONSTRUCTOR CHECK
function _classCallCheck....
// MAIN FUNCTION
var _createClass = (function () {
// APPLYING PROPERTIES ON TARGET OBJECT
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);
}
}
// ENHANCING OBJECT PROPS
return function (Constructor, protoProps, staticProps) {
if (protoProps)
defineProperties(Constructor.prototype, protoProps);
if (staticProps)
defineProperties(Constructor, staticProps);
return Constructor;
};
})();
var Person = (function () {
function Person(name) {
_classCallCheck(this, Person);
this.name = name;
}
_createClass(Person, [{
key: 'hello',
value: function hello() {
console.log('hello ' + this.name);
}
}]);
return Person;
})();
Whoua that is a lot of stuff to digest, calm down.
Light
When extracting relevant parts:
var _createClass = (function () {
function defineProperties(target, props) {
// for each props create descriptor and assign it to target object
}
return defineProperties(Constructor.prototype, protoProps);
})();
var Person = (function () {
function Person(name) { // same as before... }
_createClass(Person, [{
key: 'hello',
value: function hello() {
console.log('hello ' + this.name);
}
}]);
return Person;
})();
If you are not familiar with defineProperty function, it is not too late :)
Now you can explicitly figure out that
hello() {
console.log('hello ' + this.name);
}
has been translated to a call to
_createClass(
Person, [{
key: 'hello',
value: function hello() {
console.log('hello ' + this.name);
}
}]);
_createClass function expects 2 or 3 parameters:
Param 1 => the target object where to assign new properties.
Param 2 => a list of properties to attach to object’s prototype
Param 3 => a list of properties to attach to Class function
By this way new properties are attached to object’s prototype or its Class.
Conclusion
I hope it will give you a better understanding on how Babel compiles your ES6 source code into ES5.
I did not speak about syntax tree, AST etc…but just about resulting tranformation code.
Tags: ES6 Javascript Babel React