As I reported last time:
After that is done, I will refactor GJS’s class system (
GObject.Class). I believe this needs to be done before GNOME 3.26. That’s because [we will] gain ES6 classes, and I want to avoid the situation where we have two competing, and possibly incompatible, ways to write classes.
That’s what I’m busy doing now, in the run-up to GUADEC later this month, and I wanted to think out loud in this blog post, and give GJS users a chance to comment.
First of all, the legacy
Lang.Class classes will continue to work. You will be able to write ES6 classes that inherit from legacy classes, so you can start using ES6 classes without refactoring all of your code at once.
That was the good news, now the bad
However, there is not an obvious way to carry over the ability to create GObject classes and interfaces from legacy classes to ES6 classes. The main problem is that
Lang.Class and its subclasses formed a metaclass framework. This was used to carry out certain activities at the time the class object itself was constructed, such as registering with the GType system.
ES6 classes don’t have a syntax for that, so we’ll have to get a bit creative. My goals are to invent something (1) that’s concise and pleasant to use, and (2) that doesn’t get in the way when classes gain more features in future ES releases; that is, not too magical. (
Lang.Class is pretty magical, but then again, there wasn’t really an alternative at the time.)
Here is how the legacy classes worked, with illustrations of all the possible bells and whistles:
The metaclass magic in
Lang.Class notices that the class extends
GObject.Object, and redirects the construction of the class object to
GObject.Class. There, the other magic properties such as
Signals are processed and removed from the prototype, and it calls a C function to register the type with the GObject type system.
Without metaclasses, it’s not possible to automatically carry out magic like that at the time a class object is constructed. However, that is exactly the time when we need to register the type with GObject. So, you pretty much need to remember to call a function after the class declaration to do the registering.
The most straightforwardly translated (fictional) implementation might look something like this:
GObject.registerClass() function would take the role of the metaclass’s constructor.
This is a step backwards in a few ways compared to the legacy classes, and very unsatisfying. ES6 classes don’t yet have syntax for fields, only properties with getters, and the resulting
static get syntax is quite unwieldy. Having to call the fictional
registerClass() function separately from the class is unpleasant, because you can easily forget it.
This is a lot more concise and natural, and the property decorators are similar to the equivalent in PyGObject, but unfortunately it doesn’t exist. Decorators are still only a proposal, and none of the major browser engines implement them yet. Nonetheless, we can take the above syntax as an inspiration, use a class expression, and move the
registerClass() function around it and the GObject stuff outside of it: