Modern Javascript in GNOME – GUADEC 2017 talk

I gave a presentation at GUADEC 2017 this morning on modern Javascript in GNOME, the topic of the last few posts on this blog. As I promised during the talk, here are the slides. There is a beefy appendix after the questions slide, with details about all the new language features, that you are welcome to use as a reference.

Thanks to the GNOME Foundation for the travel sponsorship, to my employer Endless for paying for some of this work, and especially to Rob McQueen for the last-minute loan of a USB-C video adapter!

Official badge from the GUADEC website: "I'm going to The GNOME Conference GUADEC Manchester, United Kingdom"

Inventing GObject ES6 classes

Hello again! If you’re a GJS user, I’d like your opinion and ideas. After my last post where I talked about new features coming in GNOME 3.26 to GJS, GNOME’s Javascript engine, I’m happy to say that the patches are nearly ready to be landed. We just need to figure out how to build SpiderMonkey 52 consistently even though Mozilla hasn’t made an official standalone release of it yet.

A top view of a latte next to a notebook with a pen, with coffee beans strewed artfully around.

A better literal depiction of JAVA SCRIPT I could not ask for… (Public domain image courtesy of Engin_Akyurt)

As I reported last time:

After that is done, I will refactor GJS’s class system (Lang.Class and 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:


const MyClass = new Lang.Class({
    Name: 'MyClass',
    GTypeName: 'MyNamespaceMyClass',
    Extends: GObject.Object,
    Implements: [Gio.Initable, MyCustomInterface],
    Properties: {
        'prop': GObject.ParamSpec.int( /* etc., etc. */ ),
    },
    Signals: {
        'signal': { param_types: [ /* etc., etc. */ ] },
    },
    _init(props={}) {
        this.parent(props);
        // etc.
    },
get prop() { /* … */ },
method(arg) { /* … */ },
});

view raw

legacy.js

hosted with ❤ by GitHub

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 Properties and 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:


class MyClass extends GObject.Object {
    static get GTypeName { return 'MyNamespaceMyClass'; }
    static get Implements { return [Gio.Initable, MyCustomInterface]; }
    static get Properties {
        return {
            'prop': GObject.ParamSpec.int( /* etc., etc. */ ),
        };
    }
    static get Signals {
        return {
            'signal': { /* etc. */ },
        };
    }
    constructor(props={}) {
        super(props);
        // etc.
    }
get prop() { /* … */ }
    method(arg) { /* … */ }
}
GObject.registerClass(MyClass);

view raw

verbose.js

hosted with ❤ by GitHub

The fictional 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.

On the other hand, if we had decorators in the language we’d be able to make something much more satisfying. If you’re familiar with Python’s decorators, these are much the same thing: the decorator is a function which takes the object that it decorates as input, performs some action on the object, and returns it. There is a proposed decorator syntax for Javascript that allows you to decorate classes and class properties. This would be an example, with some more fictional API:


@GObject.Class('MyNamespaceMyClass')
@GObject.implements([Gio.Initable, MyCustomInterface])
@GObject.signal('signal', { /* etc. */ })
class MyClass extends GObject.Object {
    constructor(props={}) {
super(props);
// etc.
}
 
    @GObject.property.int('Short name', 'Blurb', GObject.ParamFlags.READABLE, 42)
    get prop() { /* etc. */ }
 
    method(arg) { /* etc. */ }
}

view raw

decorators.js

hosted with ❤ by GitHub

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:


var MyClass = GObject.registerClass({
    GTypeName: 'MyNamespaceMyClass',
    Implements: [Gio.Initable, MyInterface],
    Properties: { 'prop': GObject.ParamSpec.int( /* etc. */ ) },
    Signals: { 'signal': { /* etc. */ } },
}, class MyClass extends GObject.Object {
    constructor(props={}) {
super(props);
// etc.
}
 
    get prop() { /* … */ }
 
    method(arg) { /* … */ }
});

view raw

class.js

hosted with ❤ by GitHub


var MyInterface = GObject.registerInterface({
    GTypeName: 'MyNamespaceMyInterface',
    Requires: [Gio.Initable],
    Properties: { 'prop': GObject.ParamSpec.int( /* etc. */ ) },
    Signals: { 'signal': { /* etc. */ } },
}, class MyInterface {
    get prop() { /* … */ }
 
    method(arg) { /* … */ }
});

view raw

interface.js

hosted with ❤ by GitHub

Here, the body of the class is almost identical to what it would be with the decorator syntax. All the extra stuff for GObject is contained at the top of the class like it would be with the decorators. We don’t have the elegance of the property decorator, but this is quite an improvement on the first iteration. It’s not overly magical, it even acts like a decorator: it takes a class expression, and gives back a GObject-ized class. And when decorators eventually make it into standard Javascript, the basic idea is the same, so converting your code will be easy enough. (Or those who use transpiling tools can already go ahead and implement the decorator-based API.)

This is the best API I’ve been able to come up with so far. What do you think? Would you want to use it? Reply to this post or come talk to me in #javascript on GNOME IRC.

Next steps

Note first of all that none of this code exists yet. Depending on what feedback I get here, I hope to have a draft version working before GUADEC, and around the same time I’ll post a more detailed proposal to the javascript-list mailing list.

In addition, I will be speaking about this and more at GUADEC in my talk, “Modern Javascript in GNOME“. If you are attending, come talk to me there! Thanks to the GNOME Foundation for sponsoring my travel and accommodations.

Official badge from the GUADEC website: "I'm going to The GNOME Conference GUADEC Manchester, United Kingdom"