See the benchmark results and comparisons for the latest performance data.
Why Gelectron?
Electron bundles Chromium — ~150–300 MB per app with large memory overhead. Gelectron uses Servo (Mozilla's embeddable browser engine) to share Gecko's CSS engine (Stylo) and GPU compositor (WebRender), producing smaller binaries with lower memory usage.
| Feature | Electron | Gelectron |
|---|---|---|
| Rendering Engine | Chromium | Servo (Gecko CSS / WebRender GPU) |
| Language | C++ / Node.js | Rust / Node.js |
| API Compatibility | Native | Drop-in replacement |
| Memory Usage | High | Lower |
| Binary Size | ~150 MB+ | Smaller (Rust static linking) |
| Node.js Integration | Built-in | Spawned child process / N-API addon |
| Auto Updater | Built-in | Stub (no-update-safe fallback) |
Quick Start
Prerequisites
- Rust 1.75+ (
rustup.rs) - Node.js 18+
- npm
Build & Run
git clone https://github.com/mileswolfallen2/gelectron.git
cd gelectron
npm install
# Build the standalone native binary
cargo build --release -p gelectron
# Run the demo app
cargo run --release -p gelectron -- demo/
# Or run any Electron app
cargo run --release -p gelectron -- /path/to/electron-app
CLI (Node.js fallback)
If you don't want to build the Rust binary, the CLI can fall back to a pure-Node.js shim:
node cli/gelectron.js /path/to/electron-app
In fallback mode no real window is created — only the JS API layer loads. Use the native binary for actual rendering.
How It Works
Gelectron has two execution paths:
1. Native Binary (gelectron-app crate)
A standalone Rust binary using tao (windowing) and wry (WebView). It:
- Reads the target app's
package.jsonto find the main script - Generates a Node.js setup script that patches
require('electron')to point at Gelectron's JS compatibility layer - Spawns Node.js as a child process with piped stdin/stdout
- Runs a tao event loop with wry WebView windows
- Communicates with Node.js via JSON-line IPC (
create-window,load-url,ipc-message, …)
2. Node.js Fallback (cli/gelectron.js)
When the native binary is not built, the CLI falls back to pure Node.js:
- Patches
Module._resolveFilenamesorequire('electron')resolves to Gelectron's shim - Loads the app's main script — the app runs against the JS compatibility layer
- No real window is created (API-only mode)
Architecture
┌──────────────────────────────────────────────────┐
│ Gelectron App │
│ (HTML / CSS / JS + package.json) │
│ (Same code as Electron apps) │
└────────────────────┬─────────────────────────────┘
│
┌────────────────────▼─────────────────────────────┐
│ Gelectron Runtime │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ electron compat layer (JavaScript) │ │
│ │ app · BrowserWindow · Menu · Tray │ │
│ │ ipcMain · ipcRenderer · contextBridge │ │
│ │ dialog · shell · notification │ │
│ └────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ gelectron-app (Rust standalone binary) │ │
│ │ tao · windowing │ │
│ │ wry · WebView (WebKit on macOS) │ │
│ └────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
Supported Electron APIs
Main Process
| Module | Status |
|---|---|
app |
Full lifecycle, paths, command line, dock (macOS), whenReady() |
BrowserWindow |
Create, show/hide, resize, loadURL, loadFile, events, webContents |
ipcMain |
handle(), on(), removeHandler(), event emission |
Menu |
buildFromTemplate(), popup(), setApplicationMenu() |
MenuItem |
All types (normal, checkbox, separator, submenu, role) |
Tray |
Create, tooltip, context menu, click events |
dialog |
showOpenDialog(), showSaveDialog(), showMessageBox(), showErrorBox() |
shell |
openExternal(), showItemInFolder(), openPath() |
Notification |
Create, show, close, urgency levels |
nativeImage |
Create from path/buffer, resize, crop, PNG/JPEG export |
safeStorage |
Encrypt/decrypt via system keyring |
contextBridge |
exposeInMainWorld() for secure preload |
webContents |
send(), executeJavaScript(), openDevTools(), navigation |
Renderer Process
| Module | Status |
|---|---|
ipcRenderer |
invoke(), send(), on(), removeListener() |
Compatibility Shims
| Module | Status |
|---|---|
screen |
getPrimaryDisplay() (stub) |
clipboard |
readText(), writeText() (stub) |
systemPreferences |
Basic stubs |
powerMonitor |
Event stubs |
globalShortcut |
Register/unregister stubs |
session |
Cookies, protocol, permissions (stub) |
net |
fetch() proxy |
autoUpdater |
No-op stub (reports "no update available") |
Demo App
A minimal demo that renders HTML/CSS/JS in a real window:
cargo run --release -p gelectron -- demo/
The demo includes:
- Interactive counter (DOM updates via JS)
- Live clock driven by
requestAnimationFrame - Animated canvas with moving shapes
- CSS grid, gradients, transitions, and flexbox
Demo source
demo/
├── package.json # { "main": "main.js" }
├── main.js # Creates BrowserWindow, loads index.html
└── index.html # HTML + CSS + JavaScript
Running the demo
demo/main.js:
const { app, BrowserWindow } = require('electron');
const path = require('path');
app.whenReady().then(() => {
const win = new BrowserWindow({ width: 900, height: 680 });
win.loadFile(path.join(__dirname, 'index.html'));
});
app.on('window-all-closed', () => app.quit());
Project Structure
gelectron/
├── Cargo.toml # Rust workspace root
├── package.json # npm package
├── cli/
│ └── gelectron.js # CLI entry point (Node.js fallback)
├── src/
│ └── electron/ # JS Electron compatibility layer
│ ├── index.js # Main exports (require('electron'))
│ ├── app.js # app lifecycle
│ ├── browser-window.js # BrowserWindow + WebContents
│ ├── ipc-main.js # ipcMain
│ ├── ipc-renderer.js # ipcRenderer
│ ├── context-bridge.js # contextBridge
│ ├── menu.js # Menu + MenuItem
│ ├── tray.js # Tray
│ ├── dialog.js # File/message dialogs
│ ├── shell.js # Shell operations
│ ├── notification.js # Notifications
│ ├── native-image.js # Image handling
│ ├── safe-storage.js # Encryption
│ ├── web-contents.js # webContents utilities
│ ├── auto-updater.js # autoUpdater stub
│ ├── native-bridge.js # IPC to Rust binary
│ ├── preload-loader.js # Preload injection
│ └── runtime.js # Node.js fallback runtime
├── crates/
│ ├── gelectron-core/ # N-API addon (Rust → Node.js)
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs # N-API entry: init(), get_platform()
│ │ ├── app.rs # App lifecycle (native)
│ │ ├── browser_window.rs # Window management (native)
│ │ ├── servo_host.rs # Servo engine (stub)
│ │ ├── event_loop.rs # Event loop bridge
│ │ ├── ipc.rs # IPC bridge
│ │ ├── protocol.rs # Custom protocol handler
│ │ ├── menu.rs # Native menus (muda)
│ │ ├── tray.rs # System tray (tray-icon)
│ │ ├── dialog.rs # File dialogs (rfd)
│ │ ├── shell.rs # Shell operations
│ │ ├── notification.rs # Notifications (notify-rust)
│ │ ├── native_image.rs # Image processing (image)
│ │ ├── safe_storage.rs # Secure storage (keyring)
│ │ ├── context_bridge.rs # Context bridge (native)
│ │ └── web_contents.rs # WebContents (native)
│ └── gelectron-app/ # Standalone native binary
│ ├── Cargo.toml
│ └── src/
│ └── main.rs # tao + wry event loop, Node.js spawner
├── demo/ # Demo app
│ ├── package.json
│ ├── main.js
│ └── index.html
├── packager/ # gelectron-packager CLI
│ ├── package.json
│ └── bin/
│ └── gelectron-packager.js # Packaging tool
└── npm/
└── darwin-arm64/ # Platform-specific npm packages
Packaging for Distribution
Use gelectron-packager to build standalone executables for Mac, Windows, and Linux:
# Install the packager
cd packager && npm link && cd ..
# Package for current platform
gelectron-packager --dir ./demo --name MyApp
# Package for a specific platform
gelectron-packager --dir ./my-app --name MyApp --platform darwin --arch arm64
gelectron-packager --dir ./my-app --name MyApp --platform win32 --arch x64
gelectron-packager --dir ./my-app --name MyApp --platform linux --arch x64
What the packager does
- Finds your built gelectron binary (
target/release/gelectron) - Downloads a bundled Node.js runtime (~20 MB) for the target platform
- Copies your app source and
node_modules - Includes the Electron compatibility layer (
src/electron/) - Creates a self-contained, standalone distributable — no additional files needed at runtime:
- macOS:
.appbundle (double-click to run, can be moved anywhere) - Windows: Directory with
.exe+.batlauncher - Linux: Directory with launcher script +
.desktopfile
- macOS:
The packaged app bundles the Rust binary, Node.js runtime, your source code,
node_modules, and the Electron compat layer. You can delete the original project files and the packaged app will still run.
macOS .app bundle structure
MyApp.app/
Contents/
MacOS/
MyApp # Bash launcher (sets PATH, calls gelectron-bin)
gelectron-bin # Rust binary (tao + wry)
node # Bundled Node.js
compat/ # Electron compatibility layer
node_modules/ # Production dependencies
Resources/
app/ # Your app source
Info.plist
Building for Production
Standalone binary (recommended)
cargo build --release -p gelectron
N-API addon (for Node.js integration)
cargo build --release -p gelectron-core
The N-API addon compiles to a .node file that can be loaded directly into Node.js.
Testing with OmniEmu2.0
OmniEmu2.0 is a full Electron app used to validate Gelectron compatibility:
# From the gelectron directory
cargo run --release -p gelectron -- /path/to/OmniEmu2.0
Key APIs exercised by OmniEmu2.0:
app,BrowserWindow,Tray,Menu,nativeImage,dialogelectron-updater(autoUpdater stub)contextBridge,ipcRenderer- File loading (
loadFile), window events
CLI Options
gelectron <path-to-app> # Run an Electron app
gelectron <file.js> # Run a main process script directly
gelectron --version # Print version
gelectron --help # Show help
Environment Variables
| Variable | Description |
|---|---|
GELECTRON_DEV=1 |
Enable development mode |
GELECTRON_LOG=1 |
Enable verbose logging |
VITE_DEV_SERVER_URL=<url> |
Connect to a Vite dev server |
RUST_LOG=info |
Enable Rust-side logging |
Known Limitations
- Servo is not yet fully embedded — current WebView uses platform native (WebKit on macOS, WebView2 on Windows, webkit2gtk on Linux)
- Auto-updater is a no-op stub (returns "no update available")
- Some Electron APIs are stubs (marked in compatibility table)
- Single-window only in the standalone binary
- Preload scripts are injected via WebView init scripts, not true Electron preload isolation
Roadmap
- JS Electron API compatibility layer
- Standalone native binary (tao + wry)
- Node.js fallback runtime
- JSON-line IPC between Rust and Node.js
electron-updatercompatibility- Full Servo embedding (replacing wry)
- Multi-window support
- Custom protocol handlers (
gelectron://) - DevTools integration
- App sandboxing
- Package/distribution tooling
- Performance benchmarks vs Electron
- Performance good benchmarks vs Electron
Contributing
- Fork the repo
- Create a feature branch
- Make your changes
- Run
cargo build --release -p gelectronand test withcargo run --release -p gelectron -- demo/ - Submit a PR
License
MIT — see LICENSE
