Welcome! Please see the About page for a little more info on how this works.

+2 votes
in ClojureScript by

Support generation of ES6 classes

ES6 introduced a class syntax to Javascript. Before recent versions a lot of the work done around this were using polyfills, but with the advance of browsers more and more libraries are starting to use this feature natively, and that brings a problem. With the "hacked" ways to get classes you could always call the original constructor from a subclass, this is not true with native ES6 classes. To demonstrate the problem let's take a look at a simple class extension case in JS:

`
class A {
constructor(x) {

this.x = x;

}
}

class B extends A {
constructor(foo) {

super(foo);

}
}
`

An attempt to replicate this without using classes can go as:

`
function B(foo) {
A.prototype.constructor.call(this, foo);
}

Reflect.setPrototypeOf(B.prototype, A.prototype)
`

But trying to call this new constructor with new B("bar") triggers a browser exception: Uncaught TypeError: Class constructor A cannot be invoked without 'new'.

The root problem is that constructors can't be called as functions, this limits the reach of ClojureScript because it prevents the ability to extend these classes directly, and instead have to fall back to regular JS to handle these cases.

So this makes seems like we need some way to generate actual ES6 classes from Clojurescript (maybe a defclass macro?).

Some resources used in this exploration:

https://rete.js.org - library that makes use of native classes and depend on it for its usage
https://esdiscuss.org/topic/extending-an-es6-class-using-es5-syntax - some discussions around simulating es6 classes with es5
https://medium.com/@robertgrosse/how-es6-classes-really-work-and-how-to-build-your-own-fd6085eb326a - more docs around how classes works

But all the resources seems to deal with a time when native classes were not a thing, so they didn't have to deal with native constructor calls.

3 Answers

0 votes
by

Comment made by: dnolen

We will probably need a design page for this ticket enumerating the options and tradeoffs. This ticket isn't going to proceed until that is done.

0 votes
by

Comment made by: filipematossilva

There are ways of generating ES5 constructors that behave has ES2015 classes, complete with extension. A good example is what TypeScript does.

This original code:

`
class A {
constructor(x) {

this.x = x;

}
}

class B extends A {
constructor(foo) {

super(foo);

}
}

const b = new B("bar")
`

Is transpiled to:

`
var extends = (this && this.extends) || (function () {

var extendStatics = function (d, b) {
    extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return extendStatics(d, b);
};
return function (d, b) {
    extendStatics(d, b);
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};

})();
var A = /* @class / (function () {

function A(x) {
    this.x = x;
}
return A;

}());
var B = /* @class / (function (_super) {

__extends(B, _super);
function B(foo) {
    return _super.call(this, foo) || this;
}
return B;

}(A));
var b = new B("bar");
`

Where {{__extends}} is a helper generated by TS, and can be either inlined or imported from a helper library called {{tslib}}.

So it doesn't seem like a way to make ES2015 classes from inside CLJS is absolutely necessary.

0 votes
by
Reference: https://clojure.atlassian.net/browse/CLJS-3084 (reported by wilkerlucio)
...