Runtimes
gjsify targets four JavaScript runtimes, and one flag decides which build you get:
gjsify build src/index.ts --app gjs # GJSgjsify build src/index.ts --app node # Node.js, Bun and Denogjsify build src/index.ts --app browser # a web build of the same sourceYour source stays the same. What changes is which implementation each import resolves to.
Every code sample on this site follows that rule, so read them as runtime-neutral
even where they name one. The TypeScript in the widget gallery
is written for GJS, and it runs unchanged on Node.js, Bun and Deno. gi://Adw is
the same import on all four.
Two further targets answer those same Adw.* and Gtk.* names from a port rather
than from libadwaita. Add --gi-renderer to the build, and --app browser
resolves gi://Adw out of @gjsify/adwaita-web, --app nativescript out of
@gjsify/adwaita-nativescript, on a phone. So the widget you learn once keeps its
name in five places. What changes is the host around it.
Start from the runtime you already have:
| You run | Build target | gi:// is resolved by | Operating systems |
|---|---|---|---|
| GJS | --app gjs | GJS itself, no bridge | Linux, macOS in part |
| Node.js | --app node | @gjsify/node-gi | Linux, macOS, Windows |
| Bun | --app node, the same bundle | @gjsify/node-gi | Linux, macOS, Windows |
| Deno | --app node, the same bundle | @gjsify/node-gi | Linux, macOS, Windows |
Node, Bun and Deno share one bundle because Node-API is their common
native-addon ABI, so moving --runtime between those three changes the launcher
and nothing else. Crossing between gjs and any of the three is a different
build: gjsify storybook rebuilds with the other --app, and
gjsify showcase resolves the other artifact.
Every template gjsify create ships covers all four. Each one builds both
bundles and carries a start script per runtime: start runs the --app gjs
build, start:node, start:bun and start:deno run the --app node build on
the runtime they name.
GJS is GNOME’s own JavaScript runtime, SpiderMonkey plus GObject introspection.
It resolves gi://Gtk?version=4.0 itself, so a GTK call from your code enters
libgtk with nothing bridging in between. GNOME ships GJS, so a GNOME desktop
already has the runtime installed.
The build rewrites node:* imports and Web globals to gjsify’s implementations,
each backed by a GNOME library: node:fs by Gio, fetch and WebSocket by
libsoup, <canvas> by Cairo, node:sqlite by libgda.
gjsify build src/index.ts --app gjs --outfile dist/index.gjs.jsgjsify run dist/index.gjs.jsYou need gjs 1.86 or newer. Linux distributions ship it. On macOS it comes
from Homebrew, where only part of it is verified, and there is no GJS build for
Windows. Packages lists what is implemented.
Two things are specific to this target. A .deb or .rpm built from a GJS app
depends on the distribution’s own gjs (>= 1.86) rather than carrying an
interpreter, which is what keeps those packages small; see
Ship your app. And a showcase’s published artifact is its
--app gjs bundle (gjsify.main), which is why gjsify showcase runs on GJS
when a gjs binary is on PATH and follows the host runtime when there is none.
Node.js, Bun and Deno
Section titled “Node.js, Bun and Deno”The same GObject code runs on all three through
@gjsify/node-gi, a native addon that resolves
gi://Gtk?version=4.0 imports and supports GObject.registerClass, signals,
virtual functions with chain-up, boxed structs, Cairo drawing and the GLib main
loop. One prebuilt binary and one --app node bundle serve all three.
Add the bridge to the project that needs it, then build and run:
gjsify install @gjsify/node-gigjsify build src/index.ts --app node --outfile dist/index.node.mjsgjsify run dist/index.node.mjs --runtime node # or bun, or denoThese three are the runtimes that reach macOS and Windows.
@gjsify/gtk-runtime-win32-x64, @gjsify/gtk-runtime-darwin-arm64 and
@gjsify/gtk-runtime-darwin-x64 carry the GTK 4 and Adwaita closure that
@gjsify/node-gi loads its gi:// namespaces from, so there is no gvsbuild or
Homebrew GTK to install first.
What differs between the three:
- Node.js is the one with a declared engine floor:
@gjsify/node-giasks for Node 20 or newer. It is also the widest tested of the three. On Linux it carries the full GTK, Adwaita, windowing, widget, GtkSourceView and template checks. - Bun installs with
bun addand writes its own module layout. Tracked at Bun’s latest release, with the GTK and Adwaita checks run on every release. - Deno loads the addon through the prebuild path, because it runs no
postinstall build, and
gjsify run --runtime denolaunches with--node-modules-dir=manualso the already linked dependency is used as it stands. Tracked at Deno v2.x, with the same checks as Bun.
On macOS, all three are checked without a display, and the windowed proof runs under Node. On Windows, Node is the runtime we exercise; Bun and Deno are not verified there today.
Devtools works over this path as well: you can
inspect, drive and screenshot a GTK app that is running on Node, Bun or Deno, and
gjsify storybook --runtime node hosts the whole GTK storybook there.
A --app node bundle that never touches a gi:// import stays free of node-gi
and runs on stock Node.js, so you are not paying for the bridge unless you use
it.
The browser
Section titled “The browser”The same source can be built as a web app. @gjsify/adwaita-web carries the
Adwaita design system over as Web Components, and the bridge widgets have
DOM-native counterparts, so a <canvas> that was a Gtk.DrawingArea on GJS is
an ordinary canvas here. The showcases embedded on this site
are exactly these builds.
gjsify build src/index.ts --app browser --outfile dist/browser.jsBrowser builds carry no native code, so they run wherever a browser does, and
there is no bridge and no engine floor to check. Every package declares what it
provides here in its gjsify.runtimes.browser slot, the same way it declares its
gjs, node and nativescript slots, and that declaration is held against what
the source actually imports rather than taken on trust. The bundles themselves
are driven on Firefox, which shares the SpiderMonkey engine with GJS. What that
checks is our implementation claims against the real browser platform, not our
GJS packages inside a browser.
Mobile with NativeScript (experimental)
Section titled “Mobile with NativeScript (experimental)”gjsify build --app nativescript produces bundles for the NativeScript
toolchain, and @gjsify/adwaita-nativescript implements the Adwaita widget set,
the storybook renderer and the devtools agent as real native Android and iOS
views, not a WebView. The widget packages ship with every gjsify release. The
runtime target itself is still experimental, so treat it as something to try
rather than something to ship.
What keeps the runtimes in step
Section titled “What keeps the runtimes in step”The same small gi:// programs are run unchanged on gjs, node, bun and deno, and
every runtime’s output has to match GJS’s byte for byte. GJS is the reference
because it is the one implementation we did not write: a drift on either side
fails the release, whether it came from the bridge or from a GJS change. Nothing
is quietly excluded. The combinations known not to match are written down, by
name, with the reason.
Above that, each package’s own test bundle is built once, engine-agnostic, and run as that same file on Node, Bun and Deno. The three cannot drift apart without the release stopping.
The web and NativeScript ports are held a second way, because they are ports rather than the same code: they share one behaviour layer with each other, and both assert against the same recorded vectors, so a fix to a toast queue or a spin-button clamp lands in both at once.
Pick a runtime for a single command
Section titled “Pick a runtime for a single command”gjsify build defaults --app to the target of the runtime hosting the CLI:
gjs under GJS, node under Node, Bun or Deno. gjsify storybook follows the
host the same way. gjsify run follows the host too, except that an --app gjs
bundle is always launched on GJS, since its gi:// imports are externalised and
there is no bridge to stand in. gjsify showcase uses GJS when a gjs binary is
on PATH and the host runtime otherwise, because the --app gjs bundle is the
artifact every showcase publishes.
Set it explicitly whenever you want something else:
gjsify run dist/index.node.mjs --runtime bungjsify showcase canvas2d-fireworks --runtime denoOr fix it per project with gjsify.app in package.json. The full flag list is
in the CLI Reference; which runtime hosts the CLI itself
is covered on Install & Update.
Choosing between them
Section titled “Choosing between them”The four are not interchangeable in every direction. These are the differences that usually decide it:
- Operating system. There is no GJS for Windows, and on macOS the GJS side is partial. Node, Bun and Deno reach all three, taking GTK and Adwaita from the system on Linux and from the platform runtime packages on macOS and Windows. Platform Support has the per-target matrix.
- What the target machine already has. GNOME ships GJS, so a GNOME desktop needs no extra runtime. A machine already set up for npm-based tooling has one of the other three.
- The bridge. On GJS,
gi://is the runtime’s own import path. On Node, Bun and Deno it goes through@gjsify/node-gi, which means a native addon in your dependency tree and a prebuilt binary per platform. - Distribution.
gjsify shipturns a built app into an installable artifact with no packaging files in your repo. Linux packages come from a GJS build, and the macOS and Windows artifacts from a--app nodeone, since those two carry their own interpreter and GTK. Ship your app has the formats. - The web.
--app browserdrops native code entirely, which is what makes it portable and also what rules out the GNOME libraries: anything backed by GTK, Gio or libsoup needs a DOM counterpart, and@gjsify/adwaita-webis where the widget set comes from. - Stability. node-gi is younger than the GJS side. It is tested and released with every gjsify release, but a breaking change can still land in a minor version. No gjsify package depends on it at runtime, so it cannot destabilise a build that does not use it. See Versioning for the stability model.
Related
Section titled “Related”- Platform Support: Linux, macOS and Windows, per target
- Packages: what is implemented on each runtime
- Coverage: live dashboards of what is implemented
- node-gi: the bridge that puts GObject on Node.js
- napi: the other direction, native
.nodeaddons inside GJS - How It Works: the build pipeline behind the
--appflag