Combine the power of Typescript with the power of GJS
Fedora:
sudo dnf install meson vala gjs
Ubuntu:
sudo apt install meson valac gjs
NODE_OPTIONS=--max_old_space_size=9216 yarn run build
TypeScript type definition generator for GObject introspection interfaces
Home Page: https://gjsify.org/pages/projects
License: Apache License 2.0
Both system
and gettext
are currently available to be imported as ECMAScript Modules, I think since GJS supports ESM. It would be nice to be able to import them in such a way using ts-for-gir, since the default GNOME Builder template imports system
that way :)
The Idea is use ESLint's no-restricted-syntax for that.
See also #87
They're exposed in node-gtk without a prefix, the best test is the gtk_widget_measure example along with docs for gtk_widget_measure.
Note that the return value of virtual functions is similar to that of native functions, here is the logic:
- Sum all of the out arguments, plus 1 if the return value is non-void
- If the sum is equal to 1, the JS-retval is the simply the C-retval or the C-out-argument that needs to be returned
- If the sum is more than 1, the JS-retval is an array that contains first the C-retval, if any, then the C-out-args in order of declaration.
Original issue from romgrk/node-gtk#24 (comment)
when I typyed the npm install
I got this errors:
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! [email protected] validate:gjs: tsc --noEmit ./@types/Gjs/*.ts
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the [email protected] validate:gjs script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
my nodejs version is v15.3.0
how should I do?
Many Gtk Widgets properties have a C type gchar*
, ts-for-gir is assigning those properties string
type in the TypeScript declarations. However gchar*
is a pointer and can be assigned a null. This is something I've been doing before, for example with Gtk.Button.label
and now that I've started using types generated from ts-for-gir I am getting those errors:
error TS2322: Type 'string | null' is not assignable to type 'string'.
node-gtk.cjs file in @girs/node-gtk has esm format. File contains following:
import * as gi from 'node-gtk';
export { gi };
export default gi;
node-gtk.js is identical
Examples and documentation for gjs all seem to say that when extending a class derived from GObject you should use a method called _init
instead of constructors. But ts-for-gjs doesn't expose any _init
methods. GObject.registerClass
isn't exposed either. So does ts-for-gjs not support creating GObject subclasses? For most cases I suppose you can just extend the ts/js class without creating a new GObject class. In that case can a constructor parameter object be used to to initialise GObject properties? This system is limited though because you can't define your own properties, signals etc or implement additional GInterfaces. So I think we really need _init
and registerClass
.
It would be awesome to be able to just npx ts-for-gjs -m Atk-1.0
to get typescript definitions.
I can do it, if you'd like.
Also: the 1300 lines of code monolithic file is hard to follow - it would be great if it was multiple smaller files. Maybe even split out gir parsing and .d.ts writing into separate modules? I am also willing to do this and submit PRs.
I guess that this question is about what are your plans for this - do you want to continue working on it, or was it one-off project?
Hi,
I added this as a dev dependency to my project like below;
yarn add -D https://github.com/sammydre/ts-for-gjs
After that when I try to run yarn run ts-for-gir generate
I am getting the following error;
$ /home/alperen/code/extensions-sync-x/node_modules/.bin/ts-for-gir generate
› Error: command generate not found
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
As I checked node_modules/.bin/ts-for-gir
in my project is symlinked to node_modules/ts-for-gir/bin/run
. It seems correct but it cannot find the commands.
If i checkout this repository and link it, then it works. Any idea on this?
Thanks for this great project 🔥 🔥
Hello,
Currently, the generated definitions for Gio-2.0
on GJS has incorrect typing for the Cancellable.connect
method according to GJS's documentation and directly testing on its REPL:
It seems Gio-2.0.Cancellable
doesn't follow the GObject.connect
convention and implements a custom connect
as far as I could understand.
To solve this I tried to simply implement an injection into Gio-2.0.Cancellable
to override the connect
method, however that resulted in unresolved conflicts and ultimately all implementations of Gio-2.0.Cancellable.connect
ended up commented out of the type definitiondue to conflicts:
I then tried to understand why this was happening in conflict-resolver.ts
, and from what I could gather this seems to be a temporary solution:
https://github.com/sammydre/ts-for-gir/blob/bdfd98a5654a8ffcaafe623ae123981ad39429d1/packages/cli/src/type-definition-generator.ts#L545-L548
I don't know if the injection solution and solving the current method conflict shortcoming is the correct path for this particular problem, or if there is some other, simpler, way. Maybe this could be solved by adding an exception on commenting out conflicting injected methods? Any ideas?
I noticed that all GObject-based classes are exported without parent classes. Instead, all members etc of the parent GObject-class are copied into the definition of the new class. This causes problems when you want to pass one of these classes to a function that expects a parent class.
Is there an important reason you made it do this instead of using extends?
If there are more than one version installed for a library, gjs wants us to specify the version like below but versions
property does not exist in gi
. Can you also add it while generating?
imports.gi.versions.Gtk = '3.0';
Currently the repository has the name "ts-for-gjs", which is confusing since the program is called "ts-for-gir".
If you rename the repository on Github, then a redirect is automatically created, so no existing links should be broken.
sh: line 1: ../../../lib/start.js: No such file or directory
Seems like something's missing...
Thanks for making this! According to the README, cloning the repo and running npm install
is all it takes to use this project, and sure enough, opening the examples/browser folder in VS Code does seem to work. However, I'm still a little unsure how best to use this project from another project. I'm guessing I need to do something like this, at least to start:
But after that, what else do I need to do? Or is there a different way I'm supposed to use this package? And what to do for GObject libraries that aren't included by default, such as Clutter and St for GNOME Extension development?
Thanks again!
IIRC since GNOME 44, GJS incorporates a new override for Gio.Application
and GLib.MainLoop
: runAsync
.
runAsync
behaves similar to run
, with the exception that it returns a Promise<number>
instead of a number
. It serves to avoid situations like promises never ending if you didn't run the main loop or the application inside a callback.
@CharlieQLe says in #106
This might be out of scope since it requires the usage of another function, but including promisified functions would be nice, just to avoid regenerating the types.
The automatic generation of promisified functions could be a nice feature, but should be handled in a separate PR after #106 is merged.
Hey,
node-gtk maintainer here. I've reviewed some of the typings for node-gtk and here are some issues I've found. I'll refer to Gtk-3.0.d.ts
as an example. I'll use prefix g-
when using glib terms and ts-
when using typescript terms.
export class Editable {
/* Methods of Gtk.Editable */
copyClipboard(): void
// ...
}
g-interfaces can be implemented by g-classes, so I don't think ts-class is the correct mapping for g-interfaces. For example, classes Gtk.Entry
and Gtk.TextView
both implement Gtk.Editable
.
connect_after
method /* Signals of Gtk.Editable */
connect(sigName: "changed", callback: (($obj: Editable) => void)): number
connect_after(sigName: "changed", callback: (($obj: Editable) => void)): number
emit(sigName: "changed"): void
on(sigName: "changed", callback: (...args: any[]) => void): NodeJS.EventEmitter
once(sigName: "changed", callback: (...args: any[]) => void): NodeJS.EventEmitter
off(sigName: "changed", callback: (...args: any[]) => void): NodeJS.EventEmitter
.on
and friends also don't return the instance but I take that as a node-gtk issue as it should respect nodejs semantics, will be fixed soon.
Note however that there is an open PR that will add an optional argument to .on(signalName: string, cb: Function, after?: boolean)
(and .once
).
Note that I would also don't tend to promote usage of GObject#connect
. It's a lower-lever method and all it's use-cases are satisfied by GObject#on
. Just saying.
GObject#on
callbacks arguments are knownconnect(sigName: "delete-text", callback: (($obj: Editable, startPos: number, endPos: number) => void)): number
on(sigName: "delete-text", callback: (...args: any[]) => void): NodeJS.EventEmitter
The arguments for the .on
callback are the same as for the .connect
callback.
Note: node-gtk skips passing the instance as an argument to the callback because it's a leftover of the C paradigm where closures don't exist. So .on('delete-text', (editable, startPos, endPos) => { /* ... */ })
becomes .on('delete-text', (startPos, endPos) => { /* ... */ })
.
export class AccelLabel {
/* Properties of Gtk.AccelLabel */
accelClosure: Function
accelWidget: Widget
/* Properties of Gtk.Label */
angle: number
attributes: Pango.AttrList
// ...
}
Typings seem to repeat all g-classes parents' props/signals/methods in their descendants. I don't know if there is an implementation reason for that but here would be a good moment to make use of extends
.
node-gtk overrides certain API:s to expose a more pleasant API surface when it makes sense. For example, Gtk.Widget#getSizeRequest()
is normally typed as (): [number, number]
, but node-gtk exposes it as (): { width: number, height: number }
. All required JSDoc annotations to get the types right are present in the override files (eg Gtk.Widget#getSizeRequest) though I understand it might be too much work for you.
That's it for now, I understand if you can't implement everything, just wanted to mention it for the sake of correctness.
/cc @JumpLink
The cairo
module is a special case because it has many custom overrides.
See
This is not properly documented in node-gtk, but it is basically the title.
This happens since #91
Each time it completes processing a module ts-for-gjs prints "undefined" on the console. I haven't managed to find out what's causing this.
@JumpLink: I think you're maintaining the gjsify fork? In that fork I've found a problem in GirModule.traverseInheritanceTree()
. It has a check for circular dependencies, and this gets triggered multiple times while processing Gst-1.0 (gstreamer). AFAICT this is because gstreamer has its own Object type (Gst.Object
) which depends on GObject.InitiallyUnowned
which depends in turn on GObject.Object
. So there are two "Object"s in the same tree which are actually different classes. I think this can be fixed reasonably easily by comparing qualified names. For girClass
we can use _fullSymName
, and I think we can reliably assume parentPtr
will have a _module
field.
BTW, I've been working on rebasing my fork off yours, adding the command line --inheritance
. I believe they're ready to merge apart from this issue (and any other issues I might find in the Gst-1.0 output once this is fixed).
These types are all overloaded, I think that all overloaded methods must be copied to the base class/interface if they should be available.
Original issue from romgrk/node-gtk#24 (comment)
According to #21 point 6, not every function with multiple return values are overridden to return object instead of array. For example, currently GstSdp 1.0 has this type in the typing:
export function sdpMediaNew(): { returnType: SDPResult, media: SDPMedia }
However, this function is not overridden within node-gtk:
$ node
Welcome to Node.js v16.13.2.
Type ".help" for more information.
> const gi = require('node-gtk')
undefined
> const Gst = gi.require('Gst', '1.0')
undefined
> Gst.init()
[]
> const GstSdp = gi.require('GstSdp', '1.0')
undefined
> GstSdp.sdpMediaNew()
[ 0, SDPMedia {} ]
>
From bisect, this is introduced by by commit 30670c7 ("[node-gtk] Fix return type overwrites, see #21 point 6").
When I run the test npm run test:girs
Node.js 12 crashes when trying to validate the generated types with tsc --noEmit ./test/girs/@types/Gjs/*.ts
.
This does not happen with Node.js 14, so maybe we should make Node.js 14 the minimum version.
(I have not tested Node.js 13 yet)
GObject interfaces being exported as TS classes instead of interfaces can be an inconvenience, requiring one to override type safety with explicit casts.
For usability it would be best if the TS definition referred to a TS interface, ie simply change class
to interface
. However, that would hide the JS object we need to use to initialise classes that implement GInterfaces. But they could still be accessed by looking up the names as strings in imports.gi.*. When a new decorator-based API is designed to replace GObject.registerClass
there's an opportunity to make this a non-issue by making the g_implements
(or whatever it ends up being called) decorator accept a string as an alternative to a class.
I put together an example repo here to reproduce the issue I'm running into: sdellysse/scrolltiler@fcf619b
I'm trying to follow the example in this repo to get a gnome shell extension up and going but either I'm doing something wrong, my base OS (Fedora 37) or something is not right in general.
The generated @types/Gjs/index.d.ts
has errors. The line import type * as Gjs from './Gjs.js'
gives the error File '/home/notroot/src/scrolltiler/@types/Gjs/Gjs.d.ts' is not a module.ts(2306)
, as the file @types/Gjs/Gjs.d.ts
exists but is blank.
Later down the file, the declaration of
interface BooleanConstructor {
$gtype: GObject20.GType<boolean>
}
gives the error: Cannot find namespace 'GObject20'.ts(2503)
I admit I don't know too much about GTK/GLib so I'm not sure what more to do
Hello,
Currently, when trying to install this package on any node version greater than 16, the user is presented with the following message from npm:
$> npm i -D ./ts-for-gjs
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE package: '[email protected]',
npm WARN EBADENGINE required: { node: '^16' },
npm WARN EBADENGINE current: { node: 'v18.2.0', npm: '8.7.0' }
npm WARN EBADENGINE }
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE package: '[email protected]',
npm WARN EBADENGINE required: { node: '^16' },
npm WARN EBADENGINE current: { node: 'v18.2.0', npm: '8.7.0' }
npm WARN EBADENGINE }
This is due to the engine
entry in package.json
only declaring supports for node version 16:
https://github.com/sammydre/ts-for-gir/blob/97553ad65bf1116a6e8a66d1d676cf8a73ec8267/package.json#L8-L10
Is there any specific reason for such strict engine versioning?
I am currently using node 18 on a personal project and was able to generate definitions for multiple GObject packages without any clear problem.
From what I could gather from the commit that bumped the version to 16 (6e40132), this is only to enforce compatibility with node versions that support ESM. If that is the only reason could this be changes to:
"engines": {
"node": ">16"
},
Here's the full output
$ npm i --save
npm WARN deprecated @types/[email protected]: This is a stub types definition for change-case (https://github.com/blakeembrey/change-case). change-case provides its own type definitions, so you don't need @types/change-case installed!
npm WARN deprecated @types/[email protected]: This is a stub types definition for commander (https://github.com/tj/commander.js). commander provides its own type definitions, so you don't need @types/commander installed!
> [email protected] prepublish
> npm run lint && npm run regress && npm run test && npm run build
> [email protected] lint
> npm run clear && npm run build:ts -- --noEmit && eslint . --ext .ts,.tsx --fix
> [email protected] clear
> npm run clear:build && npm run clear:types
> [email protected] clear:build
> rm -rf ./lib
> [email protected] clear:types
> rm -rf ./@types
> [email protected] build:ts
> tsc "--noEmit"
src/index.ts:30:21 - error TS2571: Object is of type 'unknown'.
30 console.log(ex.stack)
~~
Found 1 error.
npm ERR! code 2
npm ERR! path /home/user/Desktop/devs/js/GUIbot/ts-for-gjs
npm ERR! command failed
npm ERR! command sh -c npm run lint && npm run regress && npm run test && npm run build
npm ERR! A complete log of this run can be found in:
npm ERR! /home/user/.npm/_logs/2021-10-06T16_16_29_613Z-debug.log
node.js v16.10.0
npm v7.24.0
OS: Linux x86_64
The repository includes generated files, such as JS compiled from TS, and the contents of the out
directory. I've been overlooking them in my PRs and I think having generated files in a git repository should be avoided if possible, and these should be listed in .gitignore
.
If you want to include these files in the repository to make it more convenient for your own workflow, please could you consider adding a special branch for that?
GJS supports camel cased property accessors, except when used as strings. Using them seems to even be encouraged in the style guide, see: https://gjs.guide/guides/gjs/style-guide.html#properties.
It would be nice to have typings available for them as well.
Currently the dependencies in the package.json
of each generated GIR module use the workspace:^
version string, which will be replaced by yarn with the current version in the workspace as soon as this is published on NPM. However, if you want to generate the packages yourself in your own monorepo and don't want to use yarn for this, you will have a problem. Therefore there should be the possibility to use the current version instead of workspace:^
.
Is it possible to use GObject.TYPE_BOOLEAN
or gtk.gdk.Pixbuf
? If not, is there another way to get these types?
Original issue from romgrk/node-gtk#24 (comment)
When running the tool I noticed lots of warning messages like:
Could not find type Gio.filename for commandline
I think this is because you haven't provided a mapping for gir's filename
type which is a char *
that isn't necessarily UTF-8. Is that a deliberate decision to treat such strings make sure that gjs doesn't trip up by trying to decode non-unicode strings? If so it would be a good idea to add 'filename': 'any'
to podTypeMap
to get rid of the annoying messages. Or would it be better to treat non-unicode filenames as an obscure and deprecated corner case, and map them to strings?
The Gjs guide shows how to use DBus and GVariant but the type for Gio.DBus
is not available in the generated types.
Hello @sammydre,
I hope this message finds you well. I have been working on a number of TypeScript tools for Gjs under the Gjsify organization, and I was wondering if you would be open to moving the project "ts-for-gir" to this organization as well.
I understand that you have been the owner to this project so far, and I would be more than happy for you to retain full access if you want. However, having the project under the Gjsify organization would allow me to add more contributors, update the project's description, and make other changes that would help to promote the project and attract more users.
I understand that this request may not align with your plans for the project, and if that is the case, I understand and respect your decision. If you are open to this proposal, please let me know and we can discuss next steps.
Thank you for your time and consideration.
Best regards,
JumpLink
This configuration:
{
"environments": ["gjs"],
"modules": ["Gio-2.0"],
"prettify": true,
"buildType": "types",
"useNamespaces": true,
"outdir": "./types/gir"
}
generates a Gio-2.0.d.ts
file which in turn declares a namespace Gio
. The generated index.d.ts
then roughly does this
import type Gio20 from "./Gio-2.0.js";
[…]
declare global {
const imports = typeof Gjs & {
gi {
Gio: typeof Gio20
[…]
}
}
Due to the additional namespace in Gio-2.0.d.ts
this declaration puts the Gio members under imports.gi.Gio.Gio
, when in Gjs they should actually be in imports.gi.Gio
. imports.gi.Gio.Gio
just never exists in Gjs.
With buildType: lib
it works.
What am I doing wrong?
Hello! I am recently writing a type definition for an application written in gjs. I really appreciate your work and they helped me really a lot.
However, one part is missing: the application uses version 3.0 of Gnome Bluetooth, and I only found @girs/gnomebluetooth-1.0. Am I missing something? If so, please tell me where I can find types for gnome Bluetooth 3.0? If not, could you please provide types for gnome Bluetooth 3.0?
This is more a feature re quest than a bug: currently, most GNOME API's and GJS built-in modules have types through this package, but (unless I'm missing something), GNOME Shell imports are not typed (i.e. imports.misc
and imports.ui
). I would love to see this implemented! This has made working with GJS infinitely better.
I'd like to understand what is the difference between this project and https://gitlab.gnome.org/ewlsh/gi.ts/-/tree/master/?
Are these two separate projects that tackle the same problem?
There is a ticket here for Vala that we can follow, but we still need to work out what of it makes sense for GJS / TypeScript.
We should make use of GitHub Actions to run the tests on each commit. Running tests with it seems to be quite simple.
node-gtk doesn't seem to have a special case for guint8[] -> Uint8Array.
$ node
Welcome to Node.js v18.16.0.
Type ".help" for more information.
> const gi = require('node-gtk')
> const GLib = gi.require('GLib', '2.0')
> let b = new GLib.Bytes("AA")
> b.getData()
[ 65, 65 ]
> Array.isArray(b.getData())
true
I'm not sure if this is a good idea for node-gtk, but since it's how it is in node-gtk, ts-for-gir should produce a correct type.
Hi! I'm having an issue with ts-for-gjs.
I need typings for a small project made with node-gtk, but when I try to run ts-for-gir generate '*' -e node
, it says done, but then I don't have any IntelliSense in Visual Studio Code.
When I require node-gtk, i have IntelliSense on that, but when i do gi.require("Gtk", "3.0")
, it doesn't provide any IntelliSense.
OS: Fedora 32
NodeJS version: v12.18.2
ts-for-gjs version: 1.0.0
ts-for-gjs commit: 8ee0173
init
CLI command with which a new project can be created instead.
The init
command then ask:
To keep the command from getting too complex we should concentrate on the most important for now, if there is a need for more configuration options we can always introduce them, so e.g. I would make esbuild to the default bundler and not ask for another.
The init
command then does the following:
npm init
package.json
by
package.json
(like build:types
, build:app
, ..)esbuild
to the devDependencies
ts-for-gir
to the devDependencies
(as soon as I have create a new npm package (I think I will name it @ts-for-gir/cli
))tsconfig.json
(with your suggestion to exclude the DOM types)esbuild.js
.ts-for-girrc.js
with the chosen
environments
: "gjs" | "node"
moduleType
: "esm" | "commonjs"
src/index.ts
as the starting point with a simple exampleOriginally posted by @JumpLink in #72 (comment)
Hello,
Currently, the type definition for the global console
is commented out in:
https://github.com/sammydre/ts-for-gir/blob/979d2a016f182ca54f14047483d2b29cac126ade/templates/Gjs/index.d.ts#L201-L202
This is due to conflicts with the global definitions shipped with typescript, that are used by most IDEs (such as VSCode):
https://github.com/microsoft/TypeScript/blob/93f2d2b9a1b2f8861b49d76bb5e58f6e9f2b56ee/lib/lib.dom.d.ts#L17750
However, this results in gjs code being linted with lib.dom.d.ts
type definitions, which is not ideal, as gjs implementation of those interfaces has slight differences and incompatibilities with the DOM implementation present in browsers, and the vast majority of the DOM API is not implemented in gjs (and probably will not be, according to #265).
This can be worked around by using a custom tsconfig.json / jsconfig.json that doesn't contain DOM in the lib property:
{
"compilerOptions": {
"lib": ["esnext"],
"allowJs": true,
"checkJs": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"],
"$schema": "https://json.schemastore.org/tsconfig"
}
So far in my tests with VSCode, this prevents the lib.dom.d.ts
from being used, and allows the local definitions generated by ts-for-gir to work without error, even with console
commented out and my, yet not upstreamed, additions of typing definition for gjs recently implemented Text(D)Encoder .
IMHO, ts-for-gir could attempt to solve this automatically. The logic I am considering would be something along the lines of:
tsconfig.json
than generate one following the example abovetsconfig.json
, check if DOM is included in the lib
property:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.