8. Deploying
Your Notes app is complete. Let's package it for distribution as a native Linux application.
Overview
Distributing a GTKX application involves three steps:
- Bundle the JavaScript code with
gtkx build - Re-bundle for SEA — convert the ESM output to CJS for Node.js Single Executable Applications
- Package with Flatpak, Snap, or distro-specific tools
Bundle with gtkx build
Run the production bundler to create a single minified ESM bundle:
npx gtkx buildThis produces:
dist/bundle.js— all dependencies inlineddist/gtkx.node— the native GTK4 bindings (copied automatically)dist/gschemas.compiled— compiled GSettings schemas (if any.gschema.xmlfiles are imported)
Re-Bundle for SEA
Node.js SEA requires a CommonJS entry point. Use esbuild to re-bundle the ESM output, shimming the native module to load from the executable's directory:
// scripts/bundle.ts
import { join, resolve } from "node:path";
import * as esbuild from "esbuild";
const projectRoot = resolve(import.meta.dirname, "..");
const nativeShim = `
const { createRequire } = require("node:module");
const { dirname, join } = require("node:path");
const execDir = dirname(process.execPath);
const require2 = createRequire(join(execDir, "package.json"));
module.exports = require2("./gtkx.node");
`;
await esbuild.build({
entryPoints: [join(projectRoot, "dist/bundle.js")],
bundle: true,
platform: "node",
target: "node22",
format: "cjs",
outfile: join(projectRoot, "dist/bundle.cjs"),
minify: true,
banner: {
js: 'var __import_meta_url = require("url").pathToFileURL(__filename).href;',
},
define: {
"import.meta.url": "__import_meta_url",
},
plugins: [
{
name: "native-shim",
setup(build) {
build.onResolve({ filter: /\.\/gtkx\.node$/ }, (args) => ({
path: args.path,
namespace: "native-shim",
}));
build.onLoad({ filter: /.*/, namespace: "native-shim" }, () => {
return { contents: nativeShim, loader: "js" };
});
},
},
],
});The native-shim plugin replaces the gtkx.node import with a runtime loader that finds the native module relative to process.execPath — this is critical for SEA binaries where the bundle is embedded in the executable.
Single Executable Application (SEA)
SEA Configuration
Create sea-config.json:
{
"main": "dist/bundle.cjs",
"output": "dist/sea-prep.blob",
"disableExperimentalSEAWarning": true,
"useCodeCache": true
}Build Script
#!/bin/bash
set -e
# Generate SEA blob
node --experimental-sea-config sea-config.json
# Copy node binary
cp $(which node) dist/app
# Inject blob into binary
npx postject dist/app NODE_SEA_BLOB dist/sea-prep.blob \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
chmod +x dist/appThe final distribution includes:
dist/app— The executabledist/gtkx.node— The native GTK4 bindingsdist/gschemas.compiled— Compiled settings schemas (if using GSettings)
Flatpak Packaging
Flatpak is the recommended format for Linux desktop applications. The manifest builds the SEA binary inside the Flatpak sandbox using the Node.js SDK extension:
# flatpak/com.gtkx.tutorial.yaml
app-id: com.gtkx.tutorial
runtime: org.gnome.Platform
runtime-version: "48"
sdk: org.gnome.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.node22
command: gtkx-tutorial
finish-args:
- --share=ipc
- --socket=fallback-x11
- --socket=wayland
- --device=dri
build-options:
append-path: /usr/lib/sdk/node22/bin
env:
npm_config_nodedir: /usr/lib/sdk/node22
no-debuginfo: true
strip: false
modules:
- name: gtkx-tutorial
buildsystem: simple
build-commands:
- node --experimental-sea-config sea-config.json
- cp /usr/lib/sdk/node22/bin/node app
- node vendor/postject.cjs app NODE_SEA_BLOB dist/sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
- install -Dm755 app /app/bin/gtkx-tutorial
- install -Dm755 dist/gtkx.node /app/bin/gtkx.node
- install -Dm644 dist/gschemas.compiled /app/bin/gschemas.compiled
- install -Dm644 flatpak/com.gtkx.tutorial.desktop /app/share/applications/com.gtkx.tutorial.desktop
- install -Dm644 flatpak/com.gtkx.tutorial.metainfo.xml /app/share/metainfo/com.gtkx.tutorial.metainfo.xml
- install -Dm644 assets/icon.png /app/share/icons/hicolor/256x256/apps/com.gtkx.tutorial.png
sources:
- type: dir
path: ..
skip:
- .flatpak-builder
- build-dir
- flatpak-repo
- node_modulesGSettings in Flatpak
The gtkx build step compiles imported .gschema.xml files into dist/gschemas.compiled. The manifest installs this file next to the binary at /app/bin/gschemas.compiled. At runtime, the GTKX GSettings plugin sets GSETTINGS_SCHEMA_DIR to the binary's directory automatically — no additional configuration needed.
Building
# Bundle the app and create the CJS re-bundle
gtkx build && tsx scripts/bundle.ts
# Build the Flatpak
flatpak-builder \
--force-clean \
--user \
--install-deps-from=flathub \
--repo=flatpak-repo \
build-dir \
flatpak/com.gtkx.tutorial.yaml
# Create an installable bundle
flatpak build-bundle \
flatpak-repo \
dist/com.gtkx.tutorial.flatpak \
com.gtkx.tutorialSnap Packaging
Snap is an alternative format, popular on Ubuntu:
# snap/snapcraft.yaml
name: gtkx-tutorial
version: "0.1.0"
base: core24
confinement: strict
apps:
gtkx-tutorial:
command: bin/gtkx-tutorial
extensions: [gnome]
parts:
gtkx-tutorial:
plugin: dump
source: dist/
organize:
app: bin/gtkx-tutorial
gtkx.node: bin/gtkx.node
gschemas.compiled: bin/gschemas.compiledFor complete Snap setup, see the Snapcraft Documentation.
What's Next?
Congratulations! You've built a complete Notes application that follows the GNOME Human Interface Guidelines, covering:
- Compound components —
AdwToolbarView.AddTopBar,AdwHeaderBar.PackStart, and more - Slot props —
titleWidget,popover, and other widget properties - CSS-in-JS styling —
@gtkx/csswith GTK CSS variables - Virtualized lists —
GtkListView,GtkGridView, andGtkColumnViewwith tree support - Menus and shortcuts —
GtkMenuButton.MenuItemandGtkShortcutController.Shortcut - Navigation —
AdwNavigationSplitView,AdwNavigationView,AdwViewStack - Search —
GtkSearchBarandGtkSearchEntryfor filtering content - Dialogs —
AdwAlertDialogandAdwAboutDialogwith portals - Toast notifications —
AdwToastOverlaywith undo support - Animations —
AdwTimedAnimationandAdwSpringAnimation - Empty states —
AdwStatusPagefor placeholder views - Settings —
useSettingwith GSettings schemas - Deployment — SEA bundling, Flatpak, and Snap packaging
Explore the API Reference for the complete API surface, or check out the CLI Reference and MCP Integration docs.