Files
gelectron/README.md
T

20 KiB

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

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-core

This 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 with:

gelectron-core /path/to/electron-app

The gelectron-core npm 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.

Prerequisites (for building from source)

  • Rust 1.75+ (rustup.rs)
  • Node.js 18+
  • npm

Build & Run (from source)

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 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

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

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.

Publishing to npm

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.

Prerequisites

  • 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)

Step 1: Build the native addon for your platform

# Build the N-API addon (produces crates/gelectron-core/*.node)
npm run build

# Or build with debug symbols for development
npm run build:debug

This compiles the Rust N-API addon (gelectron-core) into a .node file that Node.js can load.

Step 2: Create platform-specific npm packages

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

Step 3: Publish platform packages first

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

Step 4: Prepare and publish the main package

# Run prepublish hook (generates napi artifacts metadata)
npm run prepublishOnly

# Publish the main package
npm publish --access public

The @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 artifacts

For 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 }}

Quick publish (single platform)

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 public

Tip: Use npm pack to create a tarball locally and inspect it before publishing. Run npm pack and then tar -tzf gelectron-0.1.0.tgz to verify the contents.

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