Gelectron

Gelectron

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

See the [benchmark results and comparisons](https://gelectron.milesallen.site/benchmarks) 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 ### Install from npm (recommended) 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: ```bash 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](https://www.npmjs.com/package/gelectron-core)). Once installed, run any Electron app with: ```bash gelectron-core /path/to/electron-app ``` ### Download a native installer (double-click, no tools needed) Every 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--arm64.dmg` / `-x64.dmg` | Contains `Install gelectron.pkg`, which installs the runtime + compat layer to `/usr/local/bin` | | Windows | `Gelectron--x64.exe` | NSIS installer → `C:\Program Files\Gelectron`, adds it to the system PATH, Start Menu + uninstaller | | Linux | `Gelectron--x86_64.AppImage` | Self-contained bundle (includes Node.js + compat), just run it | After installing, `gelectron /path/to/electron-app` works from anywhere. > 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. ### Install from GitHub Releases (no npm) 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: ```bash # 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 | iex ``` The installer downloads `gelectron---.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 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 # remove ``` Archives can also be downloaded directly from the [Releases page] and unpacked manually — the binary just needs the `compat/` folder next to it. > 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. [Releases page]: https://github.com/mileswolfallen2/gelectron/releases ### Prerequisites (for building from source) - Rust 1.75+ (`rustup.rs`) - Node.js 18+ - npm ### Build & Run (from source) ```bash 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: ```bash 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: ```bash 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`: ```javascript 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: ```bash # 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) ```bash cargo build --release -p gelectron ``` ### N-API addon (for Node.js integration) ```bash 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](https://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](https://www.npmjs.com/signup) 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 ```bash # 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`: ```bash # 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: ```bash # 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 ```bash # Run prepublish hook (generates napi artifacts metadata) npm run prepublishOnly # Publish the main package npm publish --access public ``` ### Using napi-rs CLI (recommended) The `@napi-rs/cli` handles cross-compilation and artifact management: ```bash # 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 ``` ### CI/CD Publishing (recommended) For multi-platform publishing, use GitHub Actions to build on each OS: ```yaml # .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: ```bash # 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: ```bash # 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 ```bash gelectron # Run an Electron app gelectron # 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=` | 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 - [x] JS Electron API compatibility layer - [x] Standalone native binary (tao + wry) - [x] Node.js fallback runtime - [x] JSON-line IPC between Rust and Node.js - [x] WebView-only mode (`--no-node`) - [x] `electron-updater` compatibility - [ ] Multi-window support - [ ] Custom protocol handlers (`gelectron://`) - [ ] DevTools integration - [ ] App sandboxing - [x] Package/distribution tooling - [x] 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](LICENSE)