A minimalist ES6 class babel transformer
Transpiler are very powerful, but can be frustrating when generated code do not fit to what we'd like.
Use this transform plugin if you like what it generates for you. The generated code is ES3 compliant as long as you also add the 2 required polyfills.
So, this transform plugin purely concentrate itself on the ES6 class notation.
It uses only one traverse visitor and... It does not inject any helper function
The class
Declaration & its constructor
, become function declarations
function MyClass(args) {/* code */}
The class methods are assigned to the constructor prototype using Object.assign(MyClass.prototype, {/* methods */});
. The method shortand notation foo() {/* code */}
becomes foo: function foo() {/* code */}
(to get function name support)
Object.assign(MyClass.prototype, {
foo: function foo() {/* code */}
});
The statics
methods are assigned to the constructor using Object.assign
.
Object.assign(MyClass, {/* static methods */});
The prototype inheritance declared by the ES6 extends
instruction use Object.create()
to create the prototype
of the class. The class constructor is fixed back using Object.assign()
along the method definitions.
MyClass.prototype = Object.create(ParentClass.prototype);
Object.assign(MyClass.prototype, {
foo: function foo() {/* code */},
constructor: MyClass
});
The new.target
property, is transpiled to this.constructor
. As, with pure function constructor based prototype inheritance, the this
instance is created by the child class, and given to the parent class constructor & methods (see super
), this.constructor
refers to the child class prototype constructor property, itself fixed to the child class function itself, matching to the constructor invoked by new
.
The constructor super(arg1, arg2)
call is transpiled to ParentClass.call(this, arg1, arg2)
, the method super.methodName(arg1, arg2)
call, to ParentClass.prototype.methodName.call(this, arg1, arg2)
, and the static
method super.methodName(arg1, arg2)
call, to ParentClass.methodName.call(this, arg1, arg2)
.
To get a better idea of the result, you can play with it there: https://astexplorer.net/#/gist/0178b41edea28820b2452e3422059cbd/latest
- Sourcemap tests (to check impact of transformations approaches on babel sourcemap generation)
- ECMAScript stage 2 class properties notation and decorators may be potentially added as they make sense to complete class definition
In
class AbstractClass {
constructor(a) {}
publicMethod(foo, bar) {
console.log('Abstract', new.target);
this.bar = bar;
}
}
class MyClass extends AbstractClass {
static staticMethod(a, b) {
console.log('static', a, b);
}
constructor(a) {
super(a);
this.bar = 51;
}
publicMethod(foo, bar) {
super.publicMethod(foo, bar);
console.log('MyClass', new.target);
}
}
Out
function AbstractClass(a) {}
Object.assign(AbstractClass.prototype, {
publicMethod: function publicMethod(foo, bar) {
console.log('Abstract', this.constructor);
this.bar = bar;
}
});
function MyClass(a) {
AbstractClass.call(this, a);
this.bar = 51;
}
MyClass.prototype = Object.create(AbstractClass.prototype);
Object.assign(MyClass.prototype, {
publicMethod: function publicMethod(foo, bar) {
AbstractClass.prototype.publicMethod.call(this, foo, bar);
console.log('MyClass', this.constructor);
},
constructor: MyClass
});
Object.assign(MyClass, {
staticMethod: function staticMethod(a, b) {
console.log('static', a, b);
}
});
This transform plugin requires the JS target environment to at least support Object.create()
& Object.assign()
, either natively or via a polyfill (polyfills are intentionally not included, use the one of your choice).
The Object.create(prototype, properties)
call currently only use the first parameter (prototype), so their is no need to include polyfill support of its second argument (the properties object definitions) for now.
Many ES6 class features may either be checked at edit time instead of runtime or lead to dangerous and/or not reviewer friendly code patterns.
Here an example of some class related ESLint options:
- require constructor names to begin with a capital letter (new-cap)
- Verify calls of super() in constructors (constructor-super)
- Disallow modifying variables of class declarations (no-class-assign)
- Disallow duplicate name in class members (no-dupe-class-members)
- Disallow use of this/super before calling super() in constructors. (no-this-before-super)
- Disallow unnecessary constructor (no-useless-constructor)
and the chosen limitations that allows lighter, more readable, and faster code:
- It does not support expressions as parent class, only class/constructor names
- It does not support getter/setter
- It does not throw errors if you invoke the constructor without the new keyword (see rule
"capIsNew": true
) - It does not check if you use
this
before callingsuper()
(see ruleno-this-before-super: "error"
) - It does not support nested classes (classes defined inside a class method)
Last limitation
- This transform plugin do not properly extends native Objects such as
Date
,Array
,Function
, etc
$ npm install babel-plugin-transform-class
.babelrc
{
"plugins": ["transform-class"]
}
$ babel --plugins transform-class script.js
require("babel-core")
.transform("class A extends B { constructor() { super(); }}", {
plugins: ["transform-class"]
});