Prologue
When I started writing GTK apps (GTK is a cross-platform toolkit for creating graphical user interfaces. Particularly common with Linux app developers), I noticed that writing apps was noticeably more difficult.
From the perspective of a web developer: Writing desktop apps is hard. Not entirely because the languages used in writing desktop apps are harder, but I'd argue that it's because of the accomodations and tools web developers have at their disposal.
TypeScript
One of such tools that help massively with developer experience in the web development world is TypeScript. It's a programming language introduced by Microsoft in 2012. It's a superset of JavaScript, but adds static types with optional type annotations.
This means at a basic level it's just JavaScript, with the added possibility of adding types to your variables and parameters. So if you had code like this
function sayHello(name) {
console.log(`Hello ${name}. Your name is ${name.length} characters long.`);
}
It would instead become:
// Notice the `: string`
function sayHello(name: string) {
console.log(`Hello ${name}. Your name is ${name.length} characters long.`);
}
For platform developers, this looks seemingly normal, as most of the languages they use are probably already typed (C, C++, Rust, and more). But for JavaScript developers, this was a very novel idea and as you can probably guess, it reduces the bugs you write in the first place by a huge margin.
In the example above, in the JavaScript code you could pass a number instead of
a string to the sayHello
function. JavaScript will happily execute that, but
then you will run into bugs sooner or later.
sayHello(12);
// Hello 12. Your name is undefined characters long.
TypeScript will easily catch these errors, though:
sayHello(12);
// ts: Argument of type 'number' is not assignable to parameter of type 'string'.
This is just the surface of what's available with TypeScript, but at this point I think you and me both can see how it massively improves the developer's experience.
Developing Desktop apps is hard
Now writing desktop apps is hard. Usually, developers use languages like C to write apps. One of the first shortcomings I found is that when you are writing apps this way, you first need to write the desired changes, compile your app and then run the app to see your changes. This is in sharp contrast with the way web development works. There is a neat feature called Hot Module Replacement where the compiler will replace the changed modules in your code while the application is running, without reloading anything. We don't (AFAIK) have this feature in GTK app development yet (this was actually supposed to be my other proposal) but we can achieve something similar with a neat little app called Workbench.
Workbench
Workbench is an app that simply allows you to write the UI interface of your app, write some accompanying behavior code and with a simple click of the "Run" button, the UI you are iterating on becomes readily available. When you are done working on this particular piece of UI, you are supposed to copy the code you've written into your application, knowing the code will work and hence skipping the complex iteration loops.
I've used Workbench quite consistently while working on my GTK applications. GTK (and the underlying libraries) are quite awesome because they allow you to write apps in a number of different languages (called language bindings). And you can write GTK apps using your favourite language, whether it's JavaScript, Rust, C, C++, Python and more. Since I was coming from web development, I chose to use JavaScript for writing GTK apps as it was quite familiar to me already.
TypeScript with GTK
But in the GTK app development ecosystem, there was a growing interest to write apps in TypeScript instead of JavaScript where possible, and I was intrigued as well. I started using TypeScript instead of JavaScript for my apps, and loved it. There was one piece missing however, Workbench, the code playground, still did not support TypeScript. To make my TypeScript code work in Workbench, I used to compile the TypeScript code into JavaScript, put it in Workbench, then rewrite the code back into TypeScript for my app. Very inconvinient.
At some time, I began talking with Sonny Piers, and we began talking about possibly adding TypeScript support to Workbench, to allow developers to use the language rapidly gaining foothold in the GNOME/GTK app development arena. This would also help welcome new developers into GNOME, as more and more web developers are familiar with TypeScript and would provide a familiar platform language to them.
GSoC
With that in mind, I wrote a Google Summer of Code proposal titled "Add TypeScript Support to Workbench". As early as hitting the "Send" button, I started working on the project.
Adding the TypeScript View to Workbench
The first task I ever did was to add an option for "TypeScript" in the Workbench app. This was relatively simple enough in the sense that all it did was add an option to the dropdown at the top allowing the user to select "TypeScript" as their preferred language.
I used the pre-existing TypeScript SDK Extension written
Christopher Davis. This SDK Extension provided the tsc
(TypeScript Compiler)
and typescript-language-server
executables to the Flatpak environment
Workbench works in.
Compiling the TypeScript to JavaScript
Even though there was a TypeScript View, the TypeScript code you entered was simply interpreted as Javascript. This means that you were not able to use TypeScript language features just yet, and hence the next step to implement would be to compile TypeScript into JavaScript and then execute the JavaScript code when you clicked the "Run" button.
This was all trivial thanks to the aforementioned TypeScript SDK Extension. All we had to do was:
- Write the TypeScript code into a
main.ts
file. - Compile the TypeScript code into a
main.js
file usingtsc
. - Import the
main.js
file and then execute it.
With this in place, we had real support for running TypeScript in Workbench.
Adding GObject modules types
With that set in place, there was still an issue, and that, the types of GObject modules.
GObject, or the GLib Object System is is a library providing a portable object system and transparent cross-language interoperability. GObject Introspection (GI or GIR) is a related library providing access to typelibs and introspection data which describes C APIs.
Essentialy, this system provides a way to import and use "modules" in many languages. Gtk and many other related libraries like GLib, Pango, Gio are all GI modules and you import them to use them. For example, here is how you create a Gtk Label in GJS (GNOME JavaScript):
import Gtk from "gi://Gtk?version=4.0";
const label = new Gtk.Label({
label: "Hello, World!",
});
// present the label
For TypeScript to be useful, we need to provide "TypeScript definition files"
(.d.ts
) that will essentially tell us the type of the gi://Gtk?version=4.0
and other imports. Because these files must be statically available, we need a
way to source them.
gi.ts
Thankfully for us, there was a project named gi.ts by Evan Welsh that did
exactly that. It was a binary that you would execute and point it to a GIR file,
say /usr/share/gir-1.0/Gtk-4.0.gir
and then it would generate the TypeScript
definition files (gtk4.d.ts
) and all their dependencies as we needed.
gi-typescript-definitions
Christopher Davis then created a repository called gi-typescript-definitions where they would regenerate the typescript definition files regularly, and we would consume the definition files instead of regenerating them ourselves.
Adding the TypeScript generation files to Workbench
I hence added gi-typescript-definitions
as a submodule in Workbench, and
pointed the typescript compiler to these definition files. And with everything
all together, we now had Typechecking in Workbench and as a side effect,
we had diagnostics and completions too!!
Typechecking with JavaScript??
As it turns out, we could use the new features (diagnostics and completions)
from TypeScript in JavaScript! This is by using a special jsconfig.json
file
and using the aforementioned typescript-language-server
for JavaScript files
too, which it supports.
This means we got similar diagnostics and completions even for JavaScript!!
ts-for-gir
Remember the tool named gi.ts used to generate typescript definition files I talked about earlier, turns out there is an exceedingly similar tool named ts-for-gir by Pascal Garber that does the same thing!!
After the developers of both tools noticed the other one, they started working
on marrying the two projects together and called
the resulting project ts-for-gir
v4, which is still in beta at the time of
writing.
I knew that I had to start working on a way to use types generated by this new
version of ts-for-gir
instead of relying on gi.ts
, which has now been
archived and hence not receiving updates. So I now have a
draft PR that switches from gi.ts
to ts-for-gir
, but the question of
how we generate the typescript definition files is still unsolved
because if you remember correctly, we were using pregenerated typescript
definition files from gi-typescript-definitions
and now we will need to
generate them ourselves in Workbench. Unfortunately,
there is currently an issue blocking this, but hopefully it will be
resolved soon.
Porting the demos
Workbench has a huge library of demos, which I would also like to recommend the next time you need to familiarise yourself with how a certain platform feature works. The demos library has different code samples in various languages: JavaScript, Vala, Rust and Python.
My work would hence be incomplete without porting all the JavaScript demos to TypeScript, a noticeably complex task that is currently ongoing.
Because TypeScript compiles into JavaScript, we chose to remove all JavaScript demos and instead add TypeScript demos, and this way we can compile all the TypeScript demos into JavaScript at build time, and this way we get both TS and JS demos for the price of one. Pretty cool, right?
Wrapping up
I hope you enjoy the TypeScript support in Workbench that will hopefully be available in the next release, and start writing GNOME apps!! I also hope you liked this write-up I made about all this and don't hesitate to reach out to me if you have any questions or feedback.
Acknowledgements
I would like to thank Google Summer of Code for providing me with the opportunity of working on this, and the GNOME Foundation which administered the programme.
I would like to thank my mentors, Sonny Piers and Andy Holmes for providing me with unceasing feedback and guidance, I really appreaciate them!!
I would also like to thank Christopher Davis, for their early experimentations
on working with TypeScript on GNOME, gi-typescript-definitions
, the TypeScript
SDK Extension and many many more. You're awesome!
I would also like to extend my thanks to Pascal Garber, for working on
ts-for-gir
, their feedback and putting up with me on my wild feature requests
😅.
Finally, let me thank all the other people that helped me in the internship, from Bharat, a fellow GSoCer to the whole GNOME community for helping me out when stuck.
Thank you everyone!