Recently, I tried my hands on GJS, or should I say GNOME JavaScript? I decided to rewrite Fedy's UI in GJS, which is currently written in a mix of Bash and Yad and as you can imagine has less than ideal UI. Now, I didn't go all guns blazing giddy on power with the new GJS UI, but I got a chance to take my first look at GJS.
GJS is also a JavaScript interpreter, but it's different in a way from node/io.js that it's based on Mozilla's Spidermonkey engine, while node and io.js are based on the V8 engine which powers Google Chrome. Apart from that, there are quite a few visible differences, due to the different conventions followed in GNOME, and the differences in the engine itself.
Syntax
While the syntax is mostly the same, node still uses old ECMAScript 5, while with GJS you can take advantages of some newer ES6 features (though all features aren't supported yet). The unsupported features include classes, enhanced object initializer, template strings, spread operator etc.As far as I've tried, the following features are supported,
Variable declaration
Variables can be defined using thelet
keyword, which introduces lexical (block-level) scoping.function makeEven(num) {
let even;
if (num % 2) {
let odd = num;
even = odd + 1;
} else {
even = num;
}
print(odd); // undefined
return even;
}
Constants can be defined using the
const
keyword, which is a one time assignment.const PI = 3.14159;
Variables can also be declared by using matching arrays or objects.
let [ a, b, c ] = [ 1, 2, 3 ];
let { a, b } = { a: "apple", b: "banana" };
Arrow functions
Arrow functions are shorter to write and can maintain the samethis
variable in the callback.let squares = [ 1, 2, 3 ].map(n => n * n);
For...Of
Looping over arrays usingfor...of
is much more convenient.let fruits = [ "apple", "orange", "banana" ];
for (let fruit of fruits) {
print(fruit);
} // apple, orange, banana
Default and rest parameters
Function parameters can accept default values,function add(x, y = 0) {
return x + y;
}
The trailing parameters can be bound to an array, eliminating the need for the
arguments
object,function largest(...args) {
let num = args[0];
for (let n of args) {
if (n > num) {
num = n;
}
}
return num;
}
Unfortunately, while rest parameters work, array spread doesn't work. So you still have to do stuff like
fun.apply(this, args)
.Common differences
The basic operations in GJS such as importing modules, logging etc, are different vary a lot from node.Modules
Like node, you can use modules in GJS. But the syntax differs from node. Instead ofrequire
statements, you import objects from the imports
object instead.const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
If you want to include your own files, you've to add the current directory to the search path.
imports.searchPath.unshift(".");
const Awesome = imports.awesome; // import awesome.js from current directory
If your file is under a sub-directory, you can use
.
instead of /
for the separator.const Awesome = imports.lib.awesome; // import lib/awesome.js
All variables, constants and functions declared inside the global scope of the file
lib/awesome.js
are now available under the constant Awesome
.Logging
We all useconsole.log
a lot. But Console APIs are not present in GJS. With GJS, you have the log
(for logging) and print
methods instead. You also have the logError
and printerr
to use with error objects.Timing events
In the browser and node, we're all used tosetTimeout
and setInterval
functions. But since setTimeout
and setInterval
are part of the browser's window object, and not JavaScript native methods, they don't exist in GJS. You can use GLib.timeout_add
instead.const GLib = imports.gi.GLib;
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 2000, function() {
print("This will print after 2 seconds");
return false; // Don't repeat
}, null);
Event listeners
In browser environments, we are used to adding event listeners with theaddEventListener
method, and for those who use jQuery, the on
method. Though this isn't strictly a GJS thing, when writing GTK apps, you can attach event listeners to the GTK widgets with the connect
method.const Gtk = imports.gi.Gtk;
let button = new Gtk.Button();
button.connect("clicked", () => print("Button clicked!"));
Linting
If you're using a linter to lint your GJS code, you can addlog
, logError
, print
, printerr
, imports
and ARGV
as globals. These methods are part of the global window
object.If you use ESLint, then you can selectively enable or disable ECMA6 features. Here are my tweaks for GJS,
{
"env": {
"es6": true
},
"ecmaFeatures": {
"arrowFunctions": true,
"binaryLiterals": false,
"blockBindings": true,
"classes": false,
"defaultParams": true,
"destructuring": true,
"forOf": true,
"generators": false,
"modules": false,
"objectLiteralComputedProperties": false,
"objectLiteralDuplicateProperties": false,
"objectLiteralShorthandMethods": false,
"objectLiteralShorthandProperties": false,
"octalLiterals": false,
"regexUFlag": false,
"regexYFlag": true,
"spread": false,
"superInFunctions": false,
"templateStrings": false
},
"globals": {
"log": true,
"logError": true,
"print": true,
"printerr": true,
"imports": true,
"ARGV": true
},
"rules": {
}
}
Doing stuff
So, we know the syntax, but there are more things we need to know before we could write something useful. We might want to access the file system, os functions, build GUI applications etc. To do these things, we will need to use the libraries provided by the system.For example,
GLib
provides a lot of system APIs, whereas GTK
provides various widgets to build UI applications. You can start by checking the samples on GNOME's website and Giovanni's GJS docs.I have setup a repo with polyfills for
setTimeout
, setInterval
and Promise
, a small promise based library for file operations etc. It's not complete yet, but hoping to make it better in the future. Feel free to use them if you need.When you're trying to write an app which accepts arguments, you have a
ARGV
array containing all the arguments passed to the app. Handy!One issue you might face with GJS apps is, when you try to get the path of current directory with
GLib.get_current_dir()
, what it actually returns is the directory from where the application was called, not where the application files exist. It might be undesirable in many cases, for example where you're reading some files in the application directory. What I ended up doing is, write a small bash script to launch the app, which switches to the application directory and then launches the app.#!/bin/bash
cd $(dirname "${BASH_SOURCE[0]}") && gjs app.js $@
Note that the GJS docs are far from complete. While you'll be able find the docs for most simple stuff, it might be difficult to find and understand the docs for complex tasks, as most of them are generated and include
C
terminology.If you have anything to add, please leave a comment. It's much appreciated.
No comments :
Post a Comment