@gi.ts/lib
v2.0.0-alpha.3
Published
This package contains the core of `gi.ts`' logic. It is written to only depend directly on `@gi.ts/parser` and contains no dependencies on a specific JavaScript environment. It is typically run on Node.js, but could be run directly on GJS or in a browser.
Downloads
1,170
Readme
@gi.ts/lib
This package contains the core of gi.ts
' logic. It is written to only depend directly on @gi.ts/parser
and contains no dependencies on a specific JavaScript environment. It is typically run on Node.js, but could be run directly on GJS or in a browser.
How It Works
@gi.ts/lib
is split up into 6 distinct "stages" which convert a parsed XML GIR file into an output format (most commonly .d.ts
TypeScript definitions).
- Loading: loads XML strings from a file source
- Parsing: converts XML strings into a traversable tree
- Injection: injects additional types from GJS into the GIR data
- Transformation: applies automated and manual changes like adding inferred generics
- Generation: converts the tree into a desired format (e.g. a
.d.ts
file) - Formatting: optionally makes the output look pretty (
.d.ts
usesprettier
)
Loading
Currently implemented by @gi.ts/node-loader
for @gi.ts/cli
. The loader simply loads relevant XML files from the filesystem.
Parsing (fromXML
)
Parsing has two stages: literally parsing XML strings and then transforming those parsed strings into a traversable tree.
XML string parsing
This is currently implemented by @gi.ts/parser
using fast-xml-parser
.
Tree API
In the gir/
directory there are "parsers" for every GObject Introspection type. Each parser is prefixed with Gir
(e.g. GirClass
, GirEnum
) and they all implement a static function, fromXML
which takes a parsed XML file and constructs the relevant type.
The goal of this stage is to transform XML definitions into a tree of JavaScript objects which can be manipulated and traversed in the later stages.
Collectively, gir/
forms the tree API of gi.ts
.
At the heart of all of this is GirNSRegistry
. This class holds all the state for a single "tree" of libraries. GirNSRegistry
is also where transformers, generators, injections, and
other modifications are registered.
Injecting Overrides
Next, gi.ts
"injects" definitions for GJS bindings which aren't included in the autogenerated GIR files. GJS typically calls these "overrides" but we also inject core GJS class' like ParamSpec
.
There are two types of injections: injections/[library]
and generators/[format]/[library]
. The injections in injections/[library]
are written using gi.ts
' tree API and are exposed to all output formats (e.g. .d.ts
, .json
, .html
). Some injections only make sense for specific formats, however, so they are implemented alongside the generator. Currently only .d.ts
has generator-specific injections. Some of these are implemented as generator-specific injections because it would simply be too complicated to express the TypeScript constructions using gi.ts
' tree API.
Calling registry.transform
on a GirNSRegistry
triggers the injections.
Transformation
While a GirNSRegistry
tree is mutable, transformations are typically done immutably so a reproducible tree exists before and after.
Transformations are registered using registry.registerTransformation
which accepts a visitor. The tree API frequently uses the visitor pattern, with some modifications.
Calling registry.transform
on a GirNSRegistry
registers the default transformations, which are applied to the tree prior to generation.
Generics
There are both manual and automatic generic transformations.
Automatic Generification
In generics/visitor.ts
, an automatic transformation looks for functions similar to:
function do_something(input: string): GObject.Object;
And transforms them to...
function do_something<T extends GObject.Object = GObject.Object>(input: string): T;
This is because often GObject Introspected libraries express "generics" as the base class GObject.Object
, this transformation allows users to correctly type these calls.
A great example of this is Gtk.Builder
and get_object
which returns GObject.Object
.
const builder = new Gtk.Builder(...);
const button = builder.get_object<Gtk.Button>('my_button_id');
Manual Generics
In addition to the automated transformations, gi.ts
also adds generics to common libraries like GTK, St, Clutter, Gio, and more to ease the use of common APIs.
These changes can be found in generics/[library].ts
for each respective library.
Generation
To generate an output for a given namespace you must acquire the relevant generator from GirNSRegistry
by calling registry.getGenerator(format)
. registry.getGenerator('dts')
and registry.getGenerator('json')
are built-in. If you specify a different format GirNSRegistry
uses this resolution strategy:
- Look for a package with the name
@gi.ts/generator-[format]
...with the name `gi-ts-generator-[format]`
...with the name `[format]`
It expects the given package to have a default CommonJS export =
set to a subclass of FormatGenerator
(see generators/generator.ts
).
The generator for .d.ts
is at generators/dts.ts
and the generator for .json
is found at generators/json.ts
.
The generateNamespace
function of a generator must return a string (it gets printed to a file), but the individual generate[Type]
functions can return any format the generator desires. .json
returns Json types, .d.ts
returns strings, and .html
returns React render functions.
Formatting
The final (and simplest) step is formatting. @gi.ts/lib
comes bundled with a formatter for .json
files based on JavaScript's JSON.stringify
utility. Parsing for other outputs must be implemented in external packages to keep @gi.ts/lib
from depending on Node-specific APIs. Parsing for .d.ts
files is implemented by @gi.ts/cli
using Prettier's API.
You can register a formatter by calling registry.registerFormatter
on GirNSRegistry
.
A formatter is not required, if a formatter is not found gi.ts
simply outputs whatever the generator provides from generateNamespace
.