Gluon lets you ship a desktop app that runs your web frontend inside the user's already-installed browser — Chrome, Firefox, Edge, Brave, or half a dozen others — with Node.js handling the backend. No Chromium bundle. No Rust toolchain. No webview2 dependency.
Why I starred it
The Electron approach has one fundamental problem: you ship an entire browser with your app. That's ~220MB for a todo list. Tauri swings to the other extreme — native Rust backend, system webview. Gluon sits in an interesting middle: Node.js backend (familiar stack) + system browser (not a webview). The bundle ends up under 1MB using system Node, under 10MB if you bundle it.
What caught my eye wasn't the pitch — it was that it actually supports Firefox, not just Chromium. That's genuinely rare. Most "use the system browser" tools immediately fall back to a Chromium webview because implementing CDP for Firefox is painful. Gluon did it anyway.
How it works
Open src/index.js and the core flow is straightforward: find a browser binary, spawn it in app mode, connect via CDP or a local HTTP server (for Firefox), inject the IPC bridge.
Browser discovery is in src/lib/browserPaths.js. It maintains explicit path tables for Windows, Linux, and macOS covering Chrome, Chromium, Edge, Firefox, Brave, Vivaldi, LibreWolf, Waterfox, and Thorium. On Windows it auto-generates paths across PROGRAMFILES, LOCALAPPDATA, and PROGRAMFILES(x86). On Linux it checks $PATH directly. The findBrowserPath function walks the list in priority order and returns the first one that exists — but you can override with --chrome, --firefox, --browser=name,/path/to/binary, etc.
The trickiest engineering is in src/lib/ipc.js. The IPC layer works differently depending on the engine. For Chromium, Gluon injects a window.Gluon object via Runtime.evaluate and Page.addScriptToEvaluateOnNewDocument. Messages flow through a polling loop that calls window.Gluon.ipc._get() — a promise-based queue that resolves when the web side has something to say:
// From src/lib/ipc.js — the backend polling loop
(async () => {
while (!isClosed()) {
const msg = await CDP.sendMessage('Runtime.evaluate', {
expression: 'window.Gluon.ipc._get()',
awaitPromise: true
}, sessionId);
if (msg.result) {
onWindowMessage(JSON.parse(msg.result.value));
}
}
})();
On the web side, _get() is a promise that parks until the queue has something, then resolves with the first item. It's a long-poll loop through Runtime.evaluate — not a WebSocket, not a native bridge. Slightly unorthodox but it works for both engines since it's all CDP.
The shared state layer (ipc.store) is a Proxy on both sides. Writes go through IPC automatically — set a key on the backend store and it syncs to the web, and vice versa.
For Firefox, Chromium's --app=url flag doesn't exist, so Gluon spins up a local HTTP server (src/lib/local/http.js) and serves your local files from there. Firefox gets pointed at http://localhost:<random-port>. Chromium uses a fake https://app.gluon origin instead, which avoids the local server entirely.
The Chromium launcher (src/browser/chromium.js) passes a dense set of flags including performance presets borrowed from OpenAsar — GPU rasterization, zero-copy, canvas OOP rasterization. It also lets you disable devtools by pointing Chrome at a fake devtools URL that shows "DevTools is disabled".
Using it
import * as Gluon from '@gluon-framework/gluon';
const Window = await Gluon.open('https://example.com');
// Backend listens for messages from the webpage
Window.ipc.on('ping', data => {
return { pong: true, received: data };
});
// In your web code — window.Gluon is injected automatically
const reply = await Gluon.ipc.send('ping', { hello: 'world' });
console.log(reply); // { pong: true, received: { hello: 'world' } }
Shared state without explicit messages:
// Backend
Window.ipc.store.counter = 0;
// Web — reads the synced value
console.log(Gluon.ipc.store.counter); // 0
Gluon.ipc.store.counter = 1; // syncs back to Node
Rough edges
Last commit was July 2023. The repo was started in December 2022, had active development for about seven months, then went quiet. There are no tests anywhere in the source tree. The IPC polling approach — repeated Runtime.evaluate calls with awaitPromise: true — is functional but not what I'd call production-hardened. Under load it could be a bottleneck.
Firefox support is marked experimental in the README, and the code backs that up — the Firefox path is meaningfully different from Chromium (local HTTP server, different CDP attach flow) and gets less coverage in commits.
macOS support was listed as WIP at the time. The darwin paths in browserPaths.js are there, but the README explicitly flags it.
The expose API, which lets Node functions be called directly from the web via Gluon.ipc.exposedFunctionName(args), is implemented through a Proxy that intercepts property access and routes it to IPC. Clever, but undocumented.
Bottom line
Gluon's core idea is sound and the IPC implementation is more complete than most hobby-tier projects. If you want a tiny Electron-ish app and can accept "requires Chrome/Firefox installed," it's worth cloning and reading. For anything production, the maintenance gap since mid-2023 is the real blocker.
