gjsify
The full JavaScript ecosystem, native on GNOME.
Use Node.js APIs, Web APIs, and DOM interfaces in GNOME desktop applications. gjsify provides native implementations backed by GNOME libraries (GLib, Gio, Soup, Cairo, GTK) — so you can use the npm packages and patterns you already know to build native Linux apps.
Features
- 43 Node.js modules — fs, net, http, http2 (h2c + ALPN + native dispatcher), crypto, streams, child_process, sqlite, ws, sab-native (cross-process SharedBuffer), terminal-native, and more
- 19 Web API packages — fetch, XMLHttpRequest, WebSocket, WebCrypto, WebRTC, WebAudio, Streams (BYOB), EventSource, AbortController, DOMParser, Gamepad
- 8 DOM / bridge packages — Canvas2D (Cairo), Canvas2D-core (headless), WebGL (OpenGL ES), DOM elements, event bridge, iframes (WebKit), video (gtk4paintablesink), bridge-types
- 3 Adwaita packages for browser targets — Web Components, Adwaita Sans fonts, symbolic icons
- 8 integration test suites — webtorrent, socket.io, streamx, Autobahn (RFC 6455 fuzzing), mcp-typescript-sdk, mcp-inspector-cli, axios, worker-stress
- Flatpak toolchain —
gjsify flatpak initscaffolds manifest + MetaInfo + .desktop + flathub.json from one config block;gjsify flatpak checkruns Flathub linters locally - Node-free CLI bootstrap —
curl … install.mjs | gjs -m -installs@gjsify/cliwithout npm/Node;gjsify self-update/gjsify uninstall -ground out the lifecycle - ESM-only, TypeScript-first, Rolldown-based build system
- Native GNOME library bindings:
Giofor I/O,Soup 3.0for HTTP,GLibfor crypto/process,Cairofor 2D,GTK 4for UI,GStreamerfor media + WebRTC,libgdafor SQLite,Manettefor gamepads,WebKitfor iframes,nghttp2for native HTTP/2 - Every unit test runs on both Node.js and GJS
Quick Start
Install the CLI
Node-free bootstrap (recommended, requires only gjs ≥ 1.86 and curl):
curl -fsSL https://github.com/gjsify/gjsify/releases/latest/download/install.mjs \
-o /tmp/g.mjs && gjs -m /tmp/g.mjs && rm /tmp/g.mjs
This lays down @gjsify/cli under ~/.local/share/gjsify/global/ and a launcher
at ~/.local/bin/gjsify — no npm / node required on the machine. Run
gjsify self-update to refresh in place, or gjsify uninstall -g @gjsify/cli
to remove.
Optional Node-managed install (escape hatch — only needed if you already
manage CLIs through Node and want to install @gjsify/cli from npm):
gjsify install -g @gjsify/cli # once the CLI is bootstrapped
# or, from a Node-only machine:
npm install -g @gjsify/cli
Create a new project
gjsify create my-app
cd my-app
gjsify install --immutable
gjsify build
gjsify run start
gjsify create scaffolds a GTK 4 application with TypeScript, ready to build
and run. gjsify install resolves and installs npm dependencies via the
committed Node-free dist/cli.gjs.mjs bundle — no npm / yarn invocation
required. Pass --immutable for reproducible CI installs (gjsify-lock.json must
match package.json).
Prerequisites
The runtime requirement is GJS ≥ 1.86 (SpiderMonkey 140 / ES2024, ships with Fedora 43+ and Ubuntu 25.10+). Plus GNOME development libraries for the features you plan to use:
Fedora:
sudo dnf install gjs glib2-devel gobject-introspection-devel gtk4-devel \
libsoup3-devel webkitgtk6.0-devel libadwaita-devel gdk-pixbuf2-devel \
libepoxy-devel libgda libgda-sqlite meson vala gcc pkgconf
Ubuntu:
sudo apt install gjs libglib2.0-dev libgirepository1.0-dev libgtk-4-dev \
libsoup-3.0-dev libwebkitgtk-6.0-dev libadwaita-1-dev libgdk-pixbuf-2.0-dev \
libepoxy-dev libgda-6.0-dev meson valac gcc pkg-config
Node.js 24+ is optional — needed only if you run the legacy test:node
cross-validation suites (every unit test is mirrored on Node + GJS) or if you
prefer to manage @gjsify/cli via npm install -g.
Using the CLI directly
# Build a TypeScript file for GJS (default target)
gjsify build src/index.ts --outfile dist/app.js
# Run it (sets LD_LIBRARY_PATH + GI_TYPELIB_PATH for native @gjsify/* prebuilds)
gjsify run dist/app.js
# Try a published GJS app without installing it
gjsify dlx <pkg>
Ship your own GJS app with a one-line installer
gjsify generate-installer scaffolds an install.mjs for your package,
parameterised to your npm name and bin name:
cd my-gjs-app
gjsify generate-installer
git add install.mjs && git commit -m "chore: add gjsify-based installer"
Users of your app then install with:
curl -fsSL https://github.com/<you>/<repo>/raw/main/install.mjs \
-o /tmp/i.mjs && gjs -m /tmp/i.mjs && rm /tmp/i.mjs
No npm / Node required on the user's machine.
Ship as a Flatpak
gjsify flatpak init scaffolds the full Flathub asset set — manifest +
MetaInfo XML + .desktop + flathub.json — from one package.json
config block. --kind app (default) for GTK/Adwaita desktop apps;
--kind cli for headless tools.
gjsify flatpak init # generate everything
gjsify flatpak check # run appstreamcli + flatpak-builder-lint locally
gjsify flatpak build # wrap flatpak-builder
See the Flatpak app guide and Flatpak CLI guide for end-to-end submission.
Usage
Write standard Node.js code — the bundler resolves node:* imports to @gjsify/* implementations when targeting GJS:
import { readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { createHash } from 'node:crypto';
const content = readFileSync('/etc/hostname', 'utf8');
const hash = createHash('sha256').update(content).digest('hex');
writeFileSync(join('/tmp', 'hostname-hash.txt'), hash);
console.log(`Hostname hash: ${hash}`);
Web APIs work too:
const response = await fetch('https://api.github.com/zen');
const text = await response.text();
console.log(text);
Versioning & compatibility
All @gjsify/* packages ship as one coherent release train: every release
publishes the whole set at a single version, tested against each other at
exactly that version. Compatibility between @gjsify/* packages is guaranteed
only within the same release version — mixing versions (e.g.
@gjsify/fetch@0.14.x with @gjsify/http@0.13.x) is unsupported. Upgrade them
together:
gjsify upgrade --latest --filter @gjsify # bump every @gjsify/* dep to the latest train
gjsify upgrade --align # monorepos: re-align deps drifted across workspaces
gjsify upgrade --check # CI gate: fail on drifted ranges
Full rationale: ADR 0008 — Release-train versioning policy.
Package Status
Node.js Modules
| Status | Packages |
|---|---|
| Full (36) | assert, async_hooks, buffer, child_process, console, constants, crypto, dgram, diagnostics_channel, dns, events, fs, globals, http, http2, https, module, net, os, path, perf_hooks, process, querystring, readline, stream, string_decoder, sys, timers, tls, tty, url, util, zlib + native: terminal-native, sab-native |
| Partial (5) | sqlite (libgda-backed subset), ws (no perMessageDeflate/ping-pong events), worker_threads (subprocess-based + cross-process SAB via sab-native), vm (eval-based, no realm isolation), v8 (stub) |
| Stub (3) | cluster, domain, inspector |
Plus @gjsify/node-polyfills meta package for scaffolding.
Web APIs
abort-controller, compression-streams, dom-events, dom-exception, domparser, eventsource, fetch, formdata, gamepad, web-globals, web-streams, webaudio, webcrypto, webrtc (+ webrtc-native Vala prebuild), websocket, webstorage, xmlhttprequest. Plus @gjsify/web-polyfills meta.
Adwaita for browser targets: adwaita-web, adwaita-fonts, adwaita-icons.
DOM / Bridges
| Package | Backed by | Provides |
|---|---|---|
| canvas2d-core | Cairo, PangoCairo | Headless CanvasRenderingContext2D, CanvasGradient, CanvasPattern, Path2D, ImageData |
| canvas2d | canvas2d-core, Gtk 4 | Re-exports canvas2d-core + FontFace + Canvas2DBridge → Gtk.DrawingArea |
| dom-elements | GdkPixbuf, canvas2d-core | Node, Element, HTMLCanvasElement (auto-registers '2d'), HTMLImageElement, Document |
| event-bridge | GTK 4, Gdk 4 | GTK → DOM event mapping (Mouse, Pointer, Keyboard, Wheel, Focus) |
| iframe | WebKit 6.0 | HTMLIFrameElement, IFrameBridge → WebKit.WebView |
| video | Gst 1.0, Gtk 4 | HTMLVideoElement, VideoBridge → Gtk.Picture (gtk4paintablesink) |
| webgl | gwebgl (Vala) | WebGL 1.0/2.0, WebGLBridge → Gtk.GLArea |
| bridge-types | — | Shared BridgeEnvironment / BridgeWindow interfaces |
GNOME Library Mappings
| Node.js / Web / DOM | GNOME |
|---|---|
| fs | Gio.File, Gio.FileIOStream |
| net | Gio.SocketClient, Gio.SocketService |
| http/https | Soup.Server, Soup.Session |
| crypto | GLib.Checksum, GLib.Hmac |
| child_process | Gio.Subprocess |
| dns | Gio.Resolver |
| tls | Gio.TlsClientConnection |
| url | GLib.Uri |
| process | GLib (env, pid, cwd) |
| sqlite | Gda (SQLite provider) |
| fetch / XMLHttpRequest / WebSocket / EventSource | Soup 3.0 |
| WebRTC | GStreamer (webrtcbin) + @gjsify/webrtc-native Vala bridges |
| WebAudio | GStreamer (decodebin, appsrc, volume, autoaudiosink) |
| Video | Gtk.Picture + gtk4paintablesink (GStreamer) |
| Canvas 2D | Cairo + PangoCairo |
| WebGL | Gtk.GLArea + libepoxy via gwebgl Vala |
| Iframe | WebKit.WebView |
| Gamepad | libmanette |
Project Structure
packages/
node/ # 42 Node.js API packages (@gjsify/<name>) + node-polyfills meta
web/ # 19 Web API packages (fetch, XHR, WebSocket, WebRTC, WebAudio, …) + web-polyfills meta
dom/ # 8 DOM / bridge packages (canvas2d-core, canvas2d, webgl, dom-elements, event-bridge, iframe, video, bridge-types)
gjs/ # GJS utilities, types, test framework (@gjsify/unit)
infra/ # Build tools, Rolldown / Vite plugins, CLI, create-app
examples/
dom/ # DOM-pillar examples (WebGL tutorials, WebRTC, WebTorrent, three.js, canvas2d, video, iframe, gamepad)
node/ # Node-pillar examples (Express, Hono, Koa, socket.io, SSE chat, WS chat, Deepkit, CLI tools)
showcases/
dom/ # Polished DOM showcases consumed by `gjsify showcase`
node/ # Polished Node showcases
tests/
e2e/ # End-to-end build/test runner
integration/ # Curated upstream test suites (webtorrent, socket.io, streamx, Autobahn)
refs/ # 59 read-only reference submodules (Node.js, Deno, Bun, WebKit, GStreamer, …)
Development
# Install dependencies (workspace-wide, reproducible from gjsify-lock.json)
gjsify install --immutable
# Full build (every workspace package, in topological order)
gjsify foreach -A -t build
# Type check (all packages, parallel)
gjsify check
# Run all unit tests (every package's src/test.mts on GJS + Node, aggregated)
gjsify foreach -A test
# Run opt-in integration suites (webtorrent, socket.io, streamx, Autobahn)
gjsify workspace @gjsify/integration-webtorrent test
gjsify workspace @gjsify/integration-socket.io test
gjsify workspace @gjsify/integration-streamx test
gjsify workspace @gjsify/integration-autobahn test
# Per-package testing
cd packages/node/fs
gjsify test # Build + run src/test.mts on GJS and Node, aggregate
gjsify test --runtime gjs # Only the GJS run
gjsify test --runtime node # Only the Node run (test-correctness validation)
gjsify test replaces the per-package build:test:{gjs,node} + test:{gjs,node}
script boilerplate: it locates src/test.mts, builds it in-process for each
requested runtime, runs each output, and aggregates exit codes. Add --rebuild
to force a rebuild or --no-build to run an already-built bundle.
Testing Philosophy
Every test runs on both Node.js and GJS. Node.js validates test correctness; GJS validates the implementation. Tests use @gjsify/unit (describe/it/expect). Node.js is therefore required for development of the polyfills themselves, but not for downstream consumers — they only need GJS.
Target Environment
- GJS 1.86+ (SpiderMonkey 140 / ES2024) — runtime
- Node.js 24.x — optional, only for the cross-validation
test:nodetrack - Rolldown target:
firefox140 - ESM-only, TypeScript 6.x
License
See individual package licenses. Most packages are MIT.