Imagine if there was GTK bindings for a modern JS engine

As a web developer, I don’t particularly like GJS because it seems to be soo distant to pure/raw/standard ECMAScript (Javascript). It seems like some entire new language.

I was thinking it’d be a cool idea if GNOME had bindings for a modern language. ECMAScript would be nice because we could use it anywhere and run in most JS engines (Deno, Node, Bun etc…) without very major changes. As a web developer, I don’t know how it can be done (probably would involve Rust to convert GObject Introspection code to wasm, then run it with JS). Types (Typescript) would be essential too as it boosts the productivity and efficiency.

It would be novel to see GObject concepts translated to JavaScript ones, such as signals → object listeners.

Here’s some sample syntax I was thinking would be amazing. It would also allow building very fast and maintenable code with JSX (if needed).

import { ApplicationWindow, Button, Label } from "gi/gtk4";

const AppModelJSX = () => {
  const counter = 0;

  const increment = () => {
    counter++;
  };

  const decrement = () => {
    counter--;
  };

  return <ApplicationWindow
    title="Simple app"
    defaultWidth={300}
    defaultHeight={100}
  >
    <Box
      orientation="vertical"
      marginAll={5}
      spacing={5}
    >
      <Button
        label="Increment"
        onClicked={increment}
      />
      <Button
        label="Decrement"
        onClicked={decrement}
      />
      <Label
        marginAll={5}
        label={`Counter: ${counter}`}
      />
    </Box>
  </ApplicationWindow>
}

const AppModel = () => {
  const counter = 0;

  const increment = () => {
    counter++;
  };

  const decrement = () => {
    counter--;
  };

  const window = new ApplicationWindow("Simple app");
  window.set_default_width(300);
  window.set_default_height(100);

  const box = new Box("vertical" // or Gtk.Orientation.VERTICAL
    , 5);

  const button1 = new Button("Increment");
  button1.addEventListener("clicked", increment);

  const button2 = new Button("Decrement");
  button2.addEventListener("clicked", decrement);

  const label = new Label(`Counter: ${counter}`);
  label.set_margin_all(5);

  box.append(button1);
  box.append(button2);
  box.append(label);

  window.child = box;

  return box;
}

const main = () => {
  const app = new Application();
  app.attach(AppModel);
  app.run();
}

export default main;

1 Like

GJS is ECMAScript, in fact it is powered by SpiderMonkey, the JavaScript engine of Firefox.

What you are referring to in your code example isn’t ECMAScript but JSX, DOM and reactivity.

Beside all the work accomplished by GJS maintainers and contributors such as keeping up with new SpiderMoney/ECMAScript, supporting ES modules, and so on, there are ongoing efforts to make GJS more friendly to web developers:

(Help welcome)

There were also experimens for reactivity but it will probably take time. In the meantime GObject property/settings binding helps.

2 Likes

Yeah I saw that GJS “says” it’s ECMAScript but it’s not. By ECMAScript, many people mean ES2015 (ES6) and onwards, the Javascript version that brought many awesome features that we love nowadays such as Classes, ES modules, for … of, let, const, promises, arrow functions and boosts the ability to run in the browser (without needing compilation). I know GJS supports that, but they are not even used and GJS has weird syntax that even pushed me (fairly experienced web dev) to learn Vala rather than use it. Here are some of the things I despise:

  • weird imports: import.gi.Something or pkg.require instead of ES Modules (import { Button } from "gi://Gtk") if i remember correctly
  • weird classes Object.registerClass instead of This extends GObject (again, if I remember correctly)
  • unanavailability of types (I think this is being worked on)
  • non-portability (personally, I prefer the speed and functionality of V8 (isolates etc…) to good ol’ SpiderMonkey)
    - .connect instead of. addEventListener (extending the EventTarget class)

GJS feels like an off brand JS engine that is outdated (respectfully, don’t mean to be rude). It would be amazing if it just conformed to regular JavaScript and reused it’s features instead of going it’s own path. RN it’s like learning an entirely new language and that is bad since JavaScript is meant to be standard**. Even NodeJS has started using ECMA standards instead of building it’s own stuff like CommonJS vs ES Imports or callback functions vs Promises.

In the example, I showed 2 examples, one using regular old classes and the other using JSX, it’s very easy to write a jsx-runtime that converts jsx code to regular JS (using factories). I mean… HTML is more complex than GTK4 (by comparing number of tags/disrepancies) but still there exists many jsx runtimes such as React, Preact, Solid, htm etc etc.

If GJS was also just ECMA and didn’t rely to Spidermonkey, this could make it possible to use GJS without respect to underlying engine and engine specific optimisations can make it even faster (V8 is faster than Spidermonkey and is more embeddable while JavaScriptCore is even more faster than both, with the downside of being more reluctant to adopt some ECMA standards).

What you want is a reactive abstraction over GTK. The most successful attempt at this that I’m aware of is Relm4. It implements the Elm architecture rather than React’s, but you should try it if you can learn some Rust.

I want that too but that’s not my main concern. I just would like if GJS was standard ECMAScript. Relm4 looks cool but I don’t know Rust.

Again, GJS is standard ECMAScript so it is unclear what you are actually referring to. ES modules are already supported. GObject.registerClass is not strictly necessary but is used in addition to extends to register the class with the GType FFI. And EventTarget is a DOM API, not ECMAScript.

You keep using the word ECMAScript; I don’t think it means what you think it means.

The issue, here, is that you’re expecting a pure JavaScript framework, like you’d expect on the Web. GJS exposes the GNOME platform, which also means GObject and all the “weird” stuff that you don’t seem to like.

Sure: given time and effort and resources, you could write a “web-like” framework that exposes the same kind of API that web browsers provide; of course, you’re absolutely free to do that. You’ll probably notice that there’s a lot of impedance mismatch between the sandbox provided by a web browser and what a GUI application written using GTK is going to provide, but it’s software: it can do anything.

I’ve been thinking the same and I was working in having instead possible to use something like import {GLib, GObject, Foo as Bar, 'Gtk-3.0' as Gtk3, Gtk_40 as Gtk4} from 'gi://repository';

While I agree using import { Gtk } from 'gi' is also technically possible, having import { Window } from 'gi://Gtk' is hard to implement because we should make the gi “modules” to expose all the top-level symbols of the module, and this can’t be done without doing some slow initialization phase.

This is something we had a Merge proposal for, and that is now discussed in this issue.

In any case, GObject is way more complex than a JS Object, so wrapping can be done as easily without loosing features.

That’s part of the Web standard, so not pure JS anyways. It would be possible to wrap it probably, but not really something that would lead any advantage IMHO.

(V8 is faster than Spidermonkey and is more embeddable while JavaScriptCore is even more faster than both, with the downside of being more reluctant to adopt some ECMA standards)

Mh, I’m not sure if that’s true in all the cases… For example, I was testing the usage of Map and Set in Spindermonkey few days ago and indeed the same code in node works faster when using such types, but using pure object’s in SpiderMonkey is WAY faster than in node (like 7787.400 milliseconds vs 49.581 seconds), so I’m not sure that when embedded V8 is actually more performant (see also others who switched to spidermonkey because of performances).

I also come from the web, and to be honest, in recent times, the GJS bindings have really improved and are very close to writing “pure” JavaScript (especially with the support of ESM and constructor support for GObjects).

To me, the last friction points are the boilerplate code to start the application, and registering GObject classes.

Maybe the documentation doesn’t do the bindings justice and use an old code style, but I guess contributions are welcome. If you want an example of what it looks like to code in GJS nowadays, you can look at my application. Sonny might have some examples too.

But to give a concise example, what used to be:

const { GObject } = imports.gi;

var MyClass = GObject.registerClass({
    GTypeName: 'MyClass',
}, class extends GObject.Object {
    _init(params = {}) {
        super._init(params);
        // ...
    }
});

Can now be written as:

import GObject from 'gi://GObject';

export class MyClass extends GObject.Object {
    static {
        GObject.registerClass({
            GTypeName: 'MyClass',
        }, this);
    }

    constructor(params = {}) {
        super(params);
        // ...
    }
}

Which, apart from the mandatory GObject.registerClass call in the static block, looks like idiomatic JS to me.

It seems a lot of what’s happening here is confusion between what ECMAScript is, vs what ‘JavaScript’ is, and how DOM et al relate to them.

This seems compounded by the fact that when GJS began ES didn’t have ‘classes’ per se, lacked an import system and so on, and thus had to event its own solutions. Is imports. especially nice? No, did it get things done before import existed? Certainly.

Yet, as ES has grown these capabilities, GJS has been fairly quick to adopt them. It’s not always happened overnight, and the transition from ‘old-style’ code can take a while, but with every release the ‘custom-ness’ is reduced — you can even consume async methods as if they were native ES Promise functions etc, isn’t that neat?

I’d further note that no matter if we’d used MozJS, JSC, or V8, as the base we’d have had the same issues: Just look at node and it’s require() :slight_smile: . Perhaps MozJS isn’t perfect, perhaps it would make sense to use something else, but I think it’s rather disingenuous to say it isn’t ‘modern’.

With regards to signals: You are using GObject, not DOM, and those are fairly radically different environments and I can’t see much point trying to graft a DOM API (EventTarget) onto GObject — would you also expect various onevent attributes? What would Event look like here? etc. I suspect the result would be a confusing mess that doesn’t behave like DOM or GObject, leaving everyone unhappy.

1 Like

thanks for correcting me y’all, i understand everyhing a lil bit better.

It would definitely be possible to make an addon for JSX similar to how relm works, someone just has to write it.

does anyone know how I can create bindings for gobject introspection? I want to be able to run gobject introspected libraries using a other language.

It was discussed previously in this topic:

thanks. y’all are amazing people actually