<div align="center">
  <img src="logo.png" alt="Gelectron" width="180">
  <h1>Gelectron</h1>
  <p>A drop-in replacement for Electron using native web views (WKWebView / WebView2 / WebKitGTK) instead of Chromium</p>
</div>

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

### Prerequisites

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

### Build & Run

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

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

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

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