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.
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 | Full (electron-updater compatible) |
The built native binary and the Electron compatibility layer are published to npm. This is the main and easiest way to get Gelectron — no Rust toolchain required:
npm install -g gelectron-coreThis installs the gelectron-core package, which bundles the pre-built native binary and the JS compatibility layer, and automatically pulls in the platform-specific addon for your operating system and CPU architecture (see the package on npm).
Once installed, run any Electron app exactly like Electron — the gelectron command works out of the box (the package also exposes gelectron-core and gelectron-packager):
gelectron /path/to/electron-app
gelectron . # app in the current directory (reads package.json "main")
gelectron main.js # a bare main-process script
gelectron --versionEvery new release tag (pushed as v*) is pre-compiled in CI and published as
platform-native, double-clickable installers on the Releases page:
| Platform | Installer | What it does |
|---|---|---|
| macOS | Gelectron-<version>-arm64.dmg / -x64.dmg |
Contains Install gelectron.pkg, which installs the runtime + compat layer plus a private Node.js to /usr/local/lib/gelectron and symlinks gelectron into /usr/local/bin |
| Windows | Gelectron-<version>-x64.exe / -arm64.exe |
NSIS installer → C:\Program Files\Gelectron (runtime + compat + private Node.js), adds it to the system PATH, Start Menu + uninstaller |
| Linux | Gelectron-<version>-x86_64.AppImage / -aarch64.AppImage |
Fully self-contained (runtime + compat + private Node.js), just run it |
After installing, gelectron /path/to/electron-app works from anywhere, and no
system Node.js install is required — every installer bundles its own runtime.
Apps packaged with gelectron-packager are equally self-contained: double-click
a packaged app and it runs with no gelectron and no Node.js installed.
macOS installers are ad-hoc signed (no Developer ID), so on another Mac the first launch shows a Gatekeeper "unidentified developer" warning — right-click → Open to run it.
Every new release tag (pushed as v*) is pre-compiled in CI and published as
a self-contained installer archive. Install the latest pre-built runtime with
a single command — no Rust toolchain, no npm:
# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/mileswolfallen2/gelectron/main/scripts/install-release.sh | bash
# Windows (PowerShell)
irm https://raw.githubusercontent.com/mileswolfallen2/gelectron/main/scripts/install-release.ps1 | iexThe installer downloads gelectron-<version>-<platform>-<arch>.tar.gz (or
.zip) from the latest GitHub Release and puts the gelectron binary plus
the JS compatibility layer into ~/.local/bin (macOS/Linux) or
%LOCALAPPDATA%\gelectron\bin (Windows). Customize with:
bash scripts/install-release.sh --version v0.1.1 # specific tag
bash scripts/install-release.sh --prefix ~/bin # custom location
bash scripts/install-release.sh --uninstall # removeArchives can also be downloaded directly from the Releases page and
unpacked manually — the binary just needs the compat/ folder next to it.
The
gelectron-corenpm package is the recommended distribution channel. Building from source (below) is only needed if you're developing Gelectron itself or want the bleeding-edge version.
- Rust 1.75+ (
rustup.rs) - Node.js 18+
- npm
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-appThe gelectron command automatically locates the native runtime — the release
build, an npm/installer-installed binary (PATH, /usr/local/bin,
/usr/local/lib/gelectron, Program Files\Gelectron, etc.) or a fresh
target/{release,debug}/gelectron. If none is found it falls back to a
pure-Node.js shim:
node cli/gelectron.js /path/to/electron-appIn fallback mode no real window is created — only the JS API layer loads. Use the native binary for actual rendering.
Gelectron has two execution paths:
A standalone Rust binary using tao (windowing) and wry (WebView). It has two modes:
Node.js mode (default):
- 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, …)
WebView-only mode (--no-node):
- Loads the JS compatibility layer directly inside the WebView
- The app's main script runs inside the WKWebView JavaScript context
- No Node.js process is spawned — saves ~50 MB RSS
- Some APIs (native dialogs, clipboard, screen info) communicate directly from the WebView to the Rust binary via
window.ipc.postMessage()
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)
┌──────────────────────────────────────────────────┐
│ 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) │ │
│ └────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
| 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 |
Full API + native OS notifications (macOS Notification Center, Windows Toasts, Linux D-Bus); click/action/reply/close/failed events |
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 |
| Module | Status |
|---|---|
ipcRenderer |
invoke(), send(), on(), removeListener() |
| Module | Status |
|---|---|
screen |
Native display enumeration (getAllDisplays, getPrimaryDisplay, getDisplayMatching, …) |
clipboard |
Full API (readText/writeText/readHTML/writeHTML/readBookmark/readFindText/availableFormats) |
nativeTheme |
shouldUseDarkColors, themeSource, themes (native-backed when available) |
systemPreferences |
Basic stubs |
powerMonitor |
Event stubs |
globalShortcut |
Register/unregister stubs |
session |
Cookies, protocol, permissions (stub) |
net |
fetch() proxy |
autoUpdater |
Full (sha512-verified, atomic apply) |
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/
├── package.json # { "main": "main.js" }
├── main.js # Creates BrowserWindow, loads index.html
└── index.html # HTML + CSS + JavaScript
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());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
│ ├── clipboard.js # Clipboard
│ ├── screen.js # Display enumeration
│ ├── nativeTheme.js # Dark mode / theme
│ ├── safe-storage.js # Encryption
│ ├── web-contents.js # webContents utilities
│ ├── auto-updater.js # autoUpdater (electron-updater compatible)
│ ├── 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
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- Finds your gelectron runtime — the built binary (
target/release/gelectron), or any gelectron installed through the installers/npm (PATH,/usr/local/lib/gelectron,Program Files\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.
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
cargo build --release -p gelectroncargo build --release -p gelectron-coreThe N-API addon compiles to a .node file that can be loaded directly into Node.js.
Gelectron uses napi-rs to produce platform-specific native addons. The main gelectron npm package ships platform-specific optional packages so that npm install gelectron automatically pulls the right binary for the user's OS.
- Rust 1.75+ (
rustup.rs) - Node.js 18+
- npm
- An npm account with publish access
- Each target platform needs to be built on that platform (or via CI)
# Build the N-API addon (produces crates/gelectron-core/*.node)
npm run build
# Or build with debug symbols for development
npm run build:debugThis compiles the Rust N-API addon (gelectron-core) into a .node file that Node.js can load.
For each platform you want to support, create a directory under npm/ with a package.json:
# Example for macOS ARM64
mkdir -p npm/darwin-arm64
cat > npm/darwin-arm64/package.json << 'EOF'
{
"name": "gelectron-darwin-arm64",
"version": "0.1.0",
"description": "Gelectron native addon for macOS ARM64",
"main": "index.darwin-arm64.node",
"files": ["index.darwin-arm64.node"],
"os": ["darwin"],
"cpu": ["arm64"],
"license": "MIT"
}
EOF
# Copy the built .node file
cp crates/gelectron-core/gelectron_core.darwin-arm64.node npm/darwin-arm64/Repeat for each platform:
| Directory | os | cpu |
|---|---|---|
npm/darwin-arm64/ |
darwin |
arm64 |
npm/darwin-x64/ |
darwin |
x64 |
npm/win32-x64-msvc/ |
win32 |
x64 |
npm/win32-arm64-msvc/ |
win32 |
arm64 |
npm/linux-x64-gnu/ |
linux |
x64 |
npm/linux-arm64-gnu/ |
linux |
arm64 |
Each platform package must be published before the main package:
# Publish each platform package
npm publish npm/darwin-arm64 --access public
npm publish npm/darwin-x64 --access public
npm publish npm/win32-x64-msvc --access public
# ... etc for each platform# Run prepublish hook (generates napi artifacts metadata)
npm run prepublishOnly
# Publish the main package
npm publish --access publicThe @napi-rs/cli handles cross-compilation and artifact management:
# Install napi-rs CLI globally (if not already installed)
npm install -g @napi-rs/cli
# Build for all configured targets
napi build --platform --release
# Generate artifact metadata for npm publishing
napi prepublish -t npm
# Create a GitHub release with platform binaries
napi artifactsFor multi-platform publishing, use GitHub Actions to build on each OS:
# .github/workflows/publish.yml
name: Publish to npm
on:
push:
tags: ['v*']
jobs:
build:
strategy:
matrix:
include:
- os: macos-latest
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: windows-latest
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm ci
- run: napi build --platform --release --target ${{ matrix.target }}
- run: napi prepublish -t npm
- uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.target }}
path: npm/
publish:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}If you only need to publish for your current platform:
# Build
npm run build
# Preview what will be published
npm pack --dry-run
# Publish
npm run prepublishOnly
npm publish --access publicTip: Use
npm packto create a tarball locally and inspect it before publishing. Runnpm packand thentar -tzf gelectron-0.1.0.tgzto verify the contents.
OmniEmu2.0 is a full Electron app used to validate Gelectron compatibility:
# From the gelectron directory
cargo run --release -p gelectron -- /path/to/OmniEmu2.0Key APIs exercised by OmniEmu2.0:
app,BrowserWindow,Tray,Menu,nativeImage,dialogelectron-updater(autoUpdater stub)contextBridge,ipcRenderer- File loading (
loadFile), window events
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| 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 |
- 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)
- 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-updatercompatibility - Multi-window support
- Custom protocol handlers (
gelectron://) - DevTools integration
- App sandboxing
- Package/distribution tooling
- Performance benchmarks vs Electron
- Cross-platform verification (Windows, Linux)
- 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
MIT — see LICENSE