Skip to content

Repository files navigation

Gelectron

Gelectron

A drop-in replacement for Electron using native web views (WKWebView / WebView2 / WebKitGTK) instead of Chromium

See the benchmark results and comparisons for the latest performance data.

Why Gelectron?

Electron bundles Chromium — ~300 MB per app with 500+ MB RSS. Gelectron uses the OS-native web view (WKWebView on macOS, WebView2 on Windows, WebKitGTK on Linux) via the wry and tao Rust crates, producing smaller binaries with dramatically lower memory usage.

Feature Electron Gelectron
Rendering Engine Chromium Native WebView (WKWebView / WebView2 / WebKitGTK)
Total RSS (process tree) ~588 MB ~131 MB
Binary Size ~300 MB ~3 MB
Language C++ / Node.js Rust / Node.js
API Compatibility Native Drop-in replacement
Node.js Integration Built-in Spawned child process or WebView-only
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 has two modes:

Node.js mode (default):

  1. Reads the target app's package.json to find the main script
  2. Generates a Node.js setup script that patches require('electron') to point at Gelectron's JS compatibility layer
  3. Spawns Node.js as a child process with piped stdin/stdout
  4. Runs a tao event loop with wry WebView windows
  5. Communicates with Node.js via JSON-line IPC (create-window, load-url, ipc-message, …)

WebView-only mode (--no-node):

  1. Loads the JS compatibility layer directly inside the WebView
  2. The app's main script runs inside the WKWebView JavaScript context
  3. No Node.js process is spawned — saves ~50 MB RSS
  4. Some APIs (native dialogs, clipboard, screen info) communicate directly from the WebView to the Rust binary via window.ipc.postMessage()

2. Node.js Fallback (cli/gelectron.js)

When the native binary is not built, the CLI falls back to pure Node.js:

  1. Patches Module._resolveFilename so require('electron') resolves to Gelectron's shim
  2. Loads the app's main script — the app runs against the JS compatibility layer
  3. 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 (WKWebView / WebView2 /    │
│   │           WebKitGTK)                       │  │
│   └────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────┘

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 Full API (readText/writeText/readHTML/readRTF/readImage/readBookmark/readFindText/clear/availableFormats/has)
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 hooks (stub, not in use)
│   │       ├── 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

  1. Finds your built gelectron binary (target/release/gelectron)
  2. Downloads a bundled Node.js runtime (~20 MB) for the target platform
  3. Copies your app source and node_modules
  4. Includes the Electron compatibility layer (src/electron/)
  5. Creates a self-contained, standalone distributable — no additional files needed at runtime:
    • macOS: .app bundle (double-click to run, can be moved anywhere)
    • Windows: Directory with .exe + .bat launcher
    • Linux: Directory with launcher script + .desktop file

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, dialog
  • electron-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

  • Auto-updater is a no-op stub (returns "no update available")
  • Some Electron APIs are stubs (marked in compatibility table)
  • Preload scripts are injected via WebView init scripts, not true Electron preload isolation
  • Native menu rendering is macOS-only (Windows/Linux fall back to JS-only menus)

Roadmap

  • JS Electron API compatibility layer
  • Standalone native binary (tao + wry)
  • Node.js fallback runtime
  • JSON-line IPC between Rust and Node.js
  • WebView-only mode (--no-node)
  • electron-updater compatibility
  • Multi-window support
  • Custom protocol handlers (gelectron://)
  • DevTools integration
  • App sandboxing
  • Package/distribution tooling
  • Performance benchmarks vs Electron
  • Cross-platform verification (Windows, Linux)

Contributing

  1. Fork the repo
  2. Create a feature branch
  3. Make your changes
  4. Run cargo build --release -p gelectron and test with cargo run --release -p gelectron -- demo/
  5. Submit a PR

License

MIT — see LICENSE

About

A drop-in replacement for Electron using native web views (WKWebView / WebView2 / WebKitGTK) instead of Chromium

Topics

Resources

Contributing

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages